diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-03-09 12:34:08 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-09 12:34:08 +0900 |
| commit | d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e (patch) | |
| tree | c0c87a30037d3ffc11784627e67a1965b262c336 /packages/frontend/src/pages | |
| parent | [skip ci] Update CHANGELOG.md (prepend template) (diff) | |
| download | sharkey-d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e.tar.gz sharkey-d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e.tar.bz2 sharkey-d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e.zip | |
Refine preferences (#15597)
* wip
* wip
* wip
* test
* wip rollup pluginでsearchIndexの情報生成
* wip
* SPDX
* wip: markerIdを自動付与
* rollupでビルド時・devモード時に毎回uuidを生成するように
* 開発サーバーでだけ必要な挙動は開発サーバーのみで
* 条件が逆
* wip: childrenの生成
* update comment
* update comment
* rename auto generated file
* hashをパスと行数から決定
* Update privacy.vue
* Update privacy.vue
* wip
* Update general.vue
* Update general.vue
* wip
* wip
* Update SearchMarker.vue
* wip
* Update profile.vue
* Update mute-block.vue
* Update mute-block.vue
* Update general.vue
* Update general.vue
* childrenがduplicate key errorを吐く問題をいったん解決
* マーカーの形を成形
* loggerを置きかえ
* とりあえず省略記法に対応
* Refactor and Format codes
* wip
* Update settings-search-index.ts
* wip
* wip
* とりあえず不確定要因の仮置きidを削除
* hashの生成を正規化(絶対パスになっていたのを緩和)
* pathの入力を省略可能に
* adminでもパス生成できるように
* Update settings-search-index.ts
* Update privacy.vue
* wip
* build searchIndex
* wip
* build
* Update general.vue
* build
* Update sounds.vue
* build
* build
* Update sounds.vue
* 🎨
* 🎨
* Update privacy.vue
* Update privacy.vue
* Update security.vue
* create-search-indexを多少改善
* build
* Update 2fa.vue
* wip
* 必ずtransformCodeCacheを利用するように, キャッシュの明確な受け渡しを定義
* キャッシュはdevServerでなくても更新
* Revert "wip"
This reverts commit 41bffd3a13f55618bf939dc1c9acb2a77ead4054.
* inlining
* wip
* Update theme.vue
* 🎨
* wip normalize
* Update theme.vue
* キャッシュのパス変換
* build
* wip
* wip
* Update SearchMarker.vue
* i18n.ts['key'] の形式が取り出せない問題のFix
* build
* 仮でpath入れ
* 必ず絶対パスが使われるように
* wip
* 🎨
* storybookビルド時はcreateSearchIndexをしない
* inliningの構造化
* format code
* Update index.vue
* wip
* wip
* 🎨
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* clean up
* wip
* wip
* wip
* Update rollup-plugin-unwind-css-module-class-name.test.ts
* Update navbar.vue
* clean up
* wip
* wip
* wip
* wip
* wip
* Update preferences-backups.vue
* Update common.ts
* Update preferences.ts
* wip
* wip
* wip
* wip
* Update MkPreferenceContainer.vue
* Update MkPreferenceContainer.vue
* Update MkPreferenceContainer.vue
* enhance: 検索で上下矢印を使用することで検索結果を移動できるように
* Update main-boot.ts
* refactor
* wip
* Update sounds.vue
* fix(frontend): PageWindowでSearchMarkerが動作するように
* enhance(frontend): SearchMarkerの点滅を一定時間で止める
* wip
* lint fix
* fix: 子要素監視が抜けていたのを修正
* アニメーションの回数はCSSで制御するように
* refactor
* enhance(frontend): 検索インデックス作成時のログを削減
* revert
* fix
* fix
* Update preferences.ts
* Update preferences.ts
* wip
* Update preferences.ts
* wip
* 🎨
* wip
* Update MkPreferenceContainer.vue
* wip
* Update preferences.ts
* wip
* Update preferences.ts
* Update preferences.ts
* wip
* wip
* Update preferences.ts
* wip
* wip
* Update preferences.ts
* Update CHANGELOG.md
* Update preferences.ts
* Update deck-store.ts
* deckStoreをdefaultStoreに統合
* wip
* defaultStore -> store
* Update profile.ts
* wip
* refactor
* wip: plugin
* plugin
* plugin
* plugin
* Update plugin.ts
* wip
* Update plugin.vue
* Update preferences.ts
* Update main-boot.ts
* wip
* fix test
* Update plugin.vue
* Update plugin.vue
* Update utility.ts
* wip
* wip
* Update utility.ts
* wip
* wip
* clean up
* Update utility.ts
---------
Co-authored-by: tai-cha <dev@taichan.site>
Co-authored-by: taichan <40626578+tai-cha@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
Diffstat (limited to 'packages/frontend/src/pages')
49 files changed, 746 insertions, 1070 deletions
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue index f09a8e4285..9e3a6777f1 100644 --- a/packages/frontend/src/pages/_error_.vue +++ b/packages/frontend/src/pages/_error_.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkLoading v-if="!loaded"/> -<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear> +<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear> <div v-show="loaded" :class="$style.root"> <img :src="serverErrorImageUrl" class="_ghost" :class="$style.img"/> <div class="_gaps"> @@ -27,15 +27,15 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed } from 'vue'; import * as Misskey from 'misskey-js'; +import { version } from '@@/js/config.js'; import MkButton from '@/components/MkButton.vue'; import MkLink from '@/components/MkLink.vue'; -import { version } from '@@/js/config.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { unisonReload } from '@/scripts/unison-reload.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { miLocalStorage } from '@/local-storage.js'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; import { serverErrorImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index 29772ae00a..6e5b52ea8a 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -143,7 +143,7 @@ import MkInfo from '@/components/MkInfo.vue'; import { physics } from '@/scripts/physics.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import * as os from '@/os.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js'; @@ -406,7 +406,7 @@ const easterEggEngine = ref<{ stop: () => void } | null>(null); const containerEl = shallowRef<HTMLElement>(); function iconLoaded() { - const emojis = defaultStore.state.reactions; + const emojis = store.state.reactions; const containerWidth = containerEl.value.offsetWidth; for (let i = 0; i < 32; i++) { easterEggEmojis.value.push({ diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 22173bb888..c7a45a502e 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton> </div> - <MkInfo v-if="!defaultStore.reactiveState.abusesTutorial.value" closable @close="closeTutorial()"> + <MkInfo v-if="!store.reactiveState.abusesTutorial.value" closable @close="closeTutorial()"> {{ i18n.ts._abuseUserReport.resolveTutorial }} </MkInfo> @@ -68,7 +68,7 @@ import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkButton from '@/components/MkButton.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; const reports = shallowRef<InstanceType<typeof MkPagination>>(); @@ -93,7 +93,7 @@ function resolved(reportId) { } function closeTutorial() { - defaultStore.set('abusesTutorial', false); + store.set('abusesTutorial', false); } const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue index d6ee8ea49c..6537b844c3 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.register.vue @@ -78,6 +78,11 @@ SPDX-License-Identifier: AGPL-3.0-only /* eslint-disable @typescript-eslint/no-non-null-assertion */ import * as Misskey from 'misskey-js'; import { onMounted, ref, useCssModule } from 'vue'; +import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js'; +import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js'; +import type { DroppedFile } from '@/scripts/file-drop.js'; +import type { GridSetting } from '@/components/grid/grid.js'; +import type { GridRow } from '@/components/grid/row.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { emptyStrToEmptyArray, @@ -88,7 +93,6 @@ import MkGrid from '@/components/grid/MkGrid.vue'; import { i18n } from '@/i18n.js'; import MkSelect from '@/components/MkSelect.vue'; import MkSwitch from '@/components/MkSwitch.vue'; -import { defaultStore } from '@/store.js'; import MkFolder from '@/components/MkFolder.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; @@ -99,11 +103,7 @@ import { extractDroppedItems, flattenDroppedFiles } from '@/scripts/file-drop.js import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue'; import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js'; -import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js'; -import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js'; -import type { DroppedFile } from '@/scripts/file-drop.js'; -import type { GridSetting } from '@/components/grid/grid.js'; -import type { GridRow } from '@/components/grid/row.js'; +import { prefer } from '@/preferences.js'; const MAXIMUM_EMOJI_REGISTER_COUNT = 100; @@ -244,8 +244,8 @@ function setupGrid(): GridSetting { const uploadFolders = ref<FolderItem[]>([]); const gridItems = ref<GridItem[]>([]); -const selectedFolderId = ref(defaultStore.state.uploadFolder); -const keepOriginalUploading = ref(defaultStore.state.keepOriginalUploading); +const selectedFolderId = ref(prefer.s.uploadFolder); +const keepOriginalUploading = ref(prefer.s.keepOriginalUploading); const directoryToCategory = ref<boolean>(false); const registerButtonDisabled = ref<boolean>(false); const requestLogs = ref<RequestLogItem[]>([]); diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue index 79dd6fd5fd..1a72f9050c 100644 --- a/packages/frontend/src/pages/admin/overview.active-users.vue +++ b/packages/frontend/src/pages/admin/overview.active-users.vue @@ -17,7 +17,7 @@ import { onMounted, shallowRef, ref } from 'vue'; import { Chart } from 'chart.js'; import gradient from 'chartjs-plugin-gradient'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; import { initChart } from '@/scripts/init-chart.js'; @@ -54,7 +54,7 @@ async function renderChart() { const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' }); - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const colorRead = '#3498db'; const colorWrite = '#2ecc71'; diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue index 570fcddc07..197f792755 100644 --- a/packages/frontend/src/pages/admin/overview.ap-requests.vue +++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue @@ -27,7 +27,7 @@ import isChromatic from 'chromatic'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { alpha } from '@/scripts/color.js'; import { initChart } from '@/scripts/init-chart.js'; @@ -68,7 +68,7 @@ onMounted(async () => { const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' }); - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const succColor = '#87e000'; const failColor = '#ff4400'; diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue index 292e2e1dbc..67c0297dec 100644 --- a/packages/frontend/src/pages/admin/overview.instances.vue +++ b/packages/frontend/src/pages/admin/overview.instances.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div> - <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in"> + <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in"> <MkLoading v-if="fetching"/> <div v-else :class="$style.instances"> <MkA v-for="(instance, i) in instances" :key="instance.id" v-tooltip.mfm.noDelay="`${instance.name}\n${instance.host}\n${instance.softwareName} ${instance.softwareVersion}`" :to="`/instance-info/${instance.host}`" :class="$style.instance"> @@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; -import { misskeyApi } from '@/scripts/misskey-api.js'; import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; const instances = ref<Misskey.entities.FederationInstance[]>([]); const fetching = ref(true); diff --git a/packages/frontend/src/pages/admin/overview.moderators.vue b/packages/frontend/src/pages/admin/overview.moderators.vue index f0691534c8..4d503c0035 100644 --- a/packages/frontend/src/pages/admin/overview.moderators.vue +++ b/packages/frontend/src/pages/admin/overview.moderators.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div> - <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in"> + <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in"> <MkLoading v-if="fetching"/> <div v-else :class="$style.root" class="_panel"> <MkA v-for="user in moderators" :key="user.id" class="user" :to="`/admin/user/${user.id}`"> @@ -18,9 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, ref } from 'vue'; -import { misskeyApi } from '@/scripts/misskey-api.js'; import * as Misskey from 'misskey-js'; -import { defaultStore } from '@/store.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; +import { prefer } from '@/preferences.js'; const moderators = ref<Misskey.entities.UserDetailed[] | null>(null); const fetching = ref(true); diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue index 2efc17c888..7b83641fbd 100644 --- a/packages/frontend/src/pages/admin/overview.queue.chart.vue +++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; import { alpha } from '@/scripts/color.js'; @@ -67,7 +67,7 @@ const color = '?' as never; onMounted(() => { - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; chartInstance = new Chart(chartEl.value, { type: 'line', diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue index 222e9f4673..72b93ee97c 100644 --- a/packages/frontend/src/pages/admin/overview.stats.vue +++ b/packages/frontend/src/pages/admin/overview.stats.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div> - <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in"> + <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in"> <MkLoading v-if="fetching"/> <div v-else :class="$style.root"> <div class="item _panel users"> @@ -68,7 +68,7 @@ import MkNumberDiff from '@/components/MkNumberDiff.vue'; import MkNumber from '@/components/MkNumber.vue'; import { i18n } from '@/i18n.js'; import { customEmojis } from '@/custom-emojis.js'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; const stats = ref<Misskey.entities.StatsResponse | null>(null); const usersComparedToThePrevDay = ref<number>(); diff --git a/packages/frontend/src/pages/admin/overview.users.vue b/packages/frontend/src/pages/admin/overview.users.vue index 8c9d7a8197..8de4f298ce 100644 --- a/packages/frontend/src/pages/admin/overview.users.vue +++ b/packages/frontend/src/pages/admin/overview.users.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div :class="$style.root"> - <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in"> + <Transition :name="prefer.s.animation ? '_transition_zoom' : ''" mode="out-in"> <MkLoading v-if="fetching"/> <div v-else class="users"> <MkA v-for="(user, i) in newUsers" :key="user.id" :to="`/admin/user/${user.id}`" class="user"> @@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; -import { misskeyApi } from '@/scripts/misskey-api.js'; import * as Misskey from 'misskey-js'; import { useInterval } from '@@/js/use-interval.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null); const fetching = ref(true); diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue index cc18898172..debb48a15a 100644 --- a/packages/frontend/src/pages/admin/queue.chart.chart.vue +++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, shallowRef } from 'vue'; import { Chart } from 'chart.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; import { alpha } from '@/scripts/color.js'; @@ -67,7 +67,7 @@ const color = '?' as never; onMounted(() => { - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; chartInstance = new Chart(chartEl.value, { type: 'line', diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue index 56c10fb292..b8903a8ea6 100644 --- a/packages/frontend/src/pages/announcement.vue +++ b/packages/frontend/src/pages/announcement.vue @@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="800"> <Transition - :enterActiveClass="defaultStore.state.animation ? $style.fadeEnterActive : ''" - :leaveActiveClass="defaultStore.state.animation ? $style.fadeLeaveActive : ''" - :enterFromClass="defaultStore.state.animation ? $style.fadeEnterFrom : ''" - :leaveToClass="defaultStore.state.animation ? $style.fadeLeaveTo : ''" + :enterActiveClass="prefer.s.animation ? $style.fadeEnterActive : ''" + :leaveActiveClass="prefer.s.animation ? $style.fadeLeaveActive : ''" + :enterFromClass="prefer.s.animation ? $style.fadeEnterFrom : ''" + :leaveToClass="prefer.s.animation ? $style.fadeLeaveTo : ''" mode="out-in" > <div v-if="announcement" :key="announcement.id" class="_panel" :class="$style.announcement"> @@ -56,7 +56,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { $i, updateAccountPartial } from '@/account.js'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; const props = defineProps<{ announcementId: string; diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 4a91165d50..d333204ff8 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo> <!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる --> - <MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/> + <MkPostForm v-if="$i && prefer.r.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/> <MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/> </div> @@ -75,6 +75,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, watch, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { url } from '@@/js/config.js'; +import type { PageHeaderItem } from '@/types/page-header.js'; import MkPostForm from '@/components/MkPostForm.vue'; import MkTimeline from '@/components/MkTimeline.vue'; import XChannelFollowButton from '@/components/MkChannelFollowButton.vue'; @@ -85,16 +87,14 @@ import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { deviceKind } from '@/scripts/device-kind.js'; import MkNotes from '@/components/MkNotes.vue'; -import { url } from '@@/js/config.js'; import { favoritedChannelsCache } from '@/cache.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; import MkNote from '@/components/MkNote.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; -import type { PageHeaderItem } from '@/types/page-header.js'; import { isSupportShare } from '@/scripts/navigator.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { notesSearchAvailable } from '@/scripts/check-permissions.js'; diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 10099e6291..55c984e1bf 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove"> - <img v-if="defaultStore.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/> + <img v-if="store.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/> <img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/> <canvas ref="canvasEl" :class="$style.canvas"/> <Transition @@ -195,6 +195,8 @@ import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch import * as Matter from 'matter-js'; import * as Misskey from 'misskey-js'; import { DropAndFusionGame } from 'misskey-bubble-game'; +import { useInterval } from '@@/js/use-interval.js'; +import { apiUrl } from '@@/js/config.js'; import type { Mono } from 'misskey-bubble-game'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; @@ -203,15 +205,14 @@ import MkNumber from '@/components/MkNumber.vue'; import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; import MkButton from '@/components/MkButton.vue'; import { claimAchievement } from '@/scripts/achievements.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { useInterval } from '@@/js/use-interval.js'; -import { apiUrl } from '@@/js/config.js'; import { $i } from '@/account.js'; import * as sound from '@/scripts/sound.js'; import MkRange from '@/components/MkRange.vue'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; +import { prefer } from '@/preferences.js'; type FrontendMonoDefinition = { id: string; @@ -586,8 +587,8 @@ const showConfig = ref(false); const replaying = ref(false); const replayPlaybackRate = ref(1); const currentFrame = ref(0); -const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume); -const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume); +const bgmVolume = ref(prefer.s['game.dropAndFusion'].bgmVolume); +const sfxVolume = ref(prefer.s['game.dropAndFusion'].sfxVolume); watch(replayPlaybackRate, (newValue) => { game.replayPlaybackRate = newValue; @@ -623,7 +624,7 @@ function loadMonoTextures() { if (renderer.textures[mono.img]) return; let src = mono.img; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (monoTextureUrls[mono.img]) { src = monoTextureUrls[mono.img]; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition @@ -649,7 +650,6 @@ function loadMonoTextures() { function getTextureImageUrl(mono: Mono) { const def = monoDefinitions.value.find(x => x.id === mono.id)!; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (monoTextureUrls[def.img]) { return monoTextureUrls[def.img]; @@ -853,13 +853,13 @@ function exportLog() { } function updateSettings< - K extends keyof typeof defaultStore.state.dropAndFusion, - V extends typeof defaultStore.state.dropAndFusion[K], + K extends keyof typeof prefer.s['game.dropAndFusion'], + V extends typeof prefer.s['game.dropAndFusion'][K], >(key: K, value: V) { const changes: { [P in K]?: V } = {}; changes[key] = value; - defaultStore.set('dropAndFusion', { - ...defaultStore.state.dropAndFusion, + prefer.set('game.dropAndFusion', { + ...prefer.s['game.dropAndFusion'], ...changes, }); } @@ -909,8 +909,8 @@ function getGameImageDriveFile() { formData.append('name', `bubble-game-${Date.now()}.png`); formData.append('isSensitive', 'false'); formData.append('i', $i.token); - if (defaultStore.state.uploadFolder) { - formData.append('folderId', defaultStore.state.uploadFolder); + if (prefer.s.uploadFolder) { + formData.append('folderId', prefer.s.uploadFolder); } window.fetch(apiUrl + '/drive/files/create', { diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 6294a3f4a2..532ec87dac 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="700"> - <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in"> + <Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in"> <div v-if="flash" :key="flash.id"> - <Transition :name="defaultStore.state.animation ? 'zoom' : ''" mode="out-in"> + <Transition :name="prefer.s.animation ? 'zoom' : ''" mode="out-in"> <div v-if="started" :class="$style.started"> <div class="main _panel"> <MkAsUi v-if="root" :component="root" :components="components"/> @@ -79,7 +79,7 @@ import { registerAsUiLib } from '@/scripts/aiscript/ui.js'; import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js'; import MkFolder from '@/components/MkFolder.vue'; import MkCode from '@/components/MkCode.vue'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; import { $i } from '@/account.js'; import { isSupportShare } from '@/scripts/navigator.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index feb4c60611..7e1b7be16c 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="1000" :marginMin="16" :marginMax="32"> <div class="_root"> - <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in"> + <Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in"> <div v-if="post" class="rkxwuolj"> <div class="files"> <div v-for="file in post.files" :key="file.id" class="file"> @@ -65,6 +65,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, watch, ref, defineAsyncComponent } from 'vue'; import * as Misskey from 'misskey-js'; +import { url } from '@@/js/config.js'; +import type { MenuItem } from '@/types/menu.js'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; @@ -72,15 +74,13 @@ import MkContainer from '@/components/MkContainer.vue'; import MkPagination from '@/components/MkPagination.vue'; import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; -import { url } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; import { $i } from '@/account.js'; import { isSupportShare } from '@/scripts/navigator.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { useRouter } from '@/router/supplier.js'; -import type { MenuItem } from '@/types/menu.js'; const router = useRouter(); diff --git a/packages/frontend/src/pages/install-extensions.vue b/packages/frontend/src/pages/install-extensions.vue index 58f8b865bb..423bd611e6 100644 --- a/packages/frontend/src/pages/install-extensions.vue +++ b/packages/frontend/src/pages/install-extensions.vue @@ -45,18 +45,18 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue'; +import type { Extension } from '@/components/MkExtensionInstaller.vue'; +import type { AiScriptPluginMeta } from '@/plugin.js'; import MkLoading from '@/components/global/MkLoading.vue'; import MkExtensionInstaller from '@/components/MkExtensionInstaller.vue'; -import type { Extension } from '@/components/MkExtensionInstaller.vue'; import MkButton from '@/components/MkButton.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkUrl from '@/components/global/MkUrl.vue'; import FormSection from '@/components/form/section.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js'; -import type { AiScriptPluginMeta } from '@/scripts/install-plugin.js'; -import { parseThemeCode, installTheme } from '@/scripts/install-theme.js'; +import { parsePluginMeta, installPlugin } from '@/plugin.js'; +import { parseThemeCode, installTheme } from '@/scripts/theme.js'; import { unisonReload } from '@/scripts/unison-reload.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index 69e404bd85..acedeeeb7d 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -67,15 +67,15 @@ import MkFolder from '@/components/MkFolder.vue'; import MkInput from '@/components/MkInput.vue'; import { userListsCache } from '@/cache.js'; import { signinRequired } from '@/account.js'; -import { defaultStore } from '@/store.js'; import MkPagination from '@/components/MkPagination.vue'; import { mainRouter } from '@/router/main.js'; +import { prefer } from '@/preferences.js'; const $i = signinRequired(); const { enableInfiniteScroll, -} = defaultStore.reactiveState; +} = prefer.r; const props = defineProps<{ listId: string; diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index 0791c1343b..eaeef2e566 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="800"> <div> - <Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in"> + <Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in"> <div v-if="note"> <div v-if="showNext" class="_margin"> <MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/> @@ -61,7 +61,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { dateString } from '@/filters/date.js'; import MkClipPreview from '@/components/MkClipPreview.vue'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; import { pleaseLogin } from '@/scripts/please-login.js'; import { getAppearNote } from '@/scripts/get-appear-note.js'; import { serverContext, assertServerContext } from '@/server-context.js'; diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index d9ad7babb7..3444ef49aa 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="800"> <Transition - :enterActiveClass="defaultStore.state.animation ? $style.fadeEnterActive : ''" - :leaveActiveClass="defaultStore.state.animation ? $style.fadeLeaveActive : ''" - :enterFromClass="defaultStore.state.animation ? $style.fadeEnterFrom : ''" - :leaveToClass="defaultStore.state.animation ? $style.fadeLeaveTo : ''" + :enterActiveClass="prefer.s.animation ? $style.fadeEnterActive : ''" + :leaveActiveClass="prefer.s.animation ? $style.fadeLeaveActive : ''" + :enterFromClass="prefer.s.animation ? $style.fadeEnterFrom : ''" + :leaveToClass="prefer.s.animation ? $style.fadeLeaveTo : ''" mode="out-in" > <div v-if="page" :key="page.id" class="_gaps"> @@ -100,11 +100,12 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, watch, ref, defineAsyncComponent } from 'vue'; import * as Misskey from 'misskey-js'; +import { url } from '@@/js/config.js'; +import type { MenuItem } from '@/types/menu.js'; import XPage from '@/components/page/page.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { url } from '@@/js/config.js'; import MkMediaImage from '@/components/MkMediaImage.vue'; import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; @@ -113,7 +114,7 @@ import MkPagination from '@/components/MkPagination.vue'; import MkPagePreview from '@/components/MkPagePreview.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { pageViewInterruptors, defaultStore } from '@/store.js'; +import { pageViewInterruptors } from '@/store.js'; import { deepClone } from '@/scripts/clone.js'; import { $i } from '@/account.js'; import { isSupportShare } from '@/scripts/navigator.js'; @@ -121,7 +122,7 @@ import { instance } from '@/instance.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { useRouter } from '@/router/supplier.js'; -import type { MenuItem } from '@/types/menu.js'; +import { prefer } from '@/preferences.js'; const router = useRouter(); diff --git a/packages/frontend/src/pages/settings/accessibility.vue b/packages/frontend/src/pages/settings/accessibility.vue index b703be1fe1..f464f728ff 100644 --- a/packages/frontend/src/pages/settings/accessibility.vue +++ b/packages/frontend/src/pages/settings/accessibility.vue @@ -8,49 +8,63 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_m"> <div class="_gaps_s"> <SearchMarker :keywords="['animation', 'motion', 'reduce']"> - <MkSwitch v-model="reduceAnimation"> - <template #label><SearchLabel>{{ i18n.ts.reduceUiAnimation }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="animation"> + <MkSwitch v-model="reduceAnimation"> + <template #label><SearchLabel>{{ i18n.ts.reduceUiAnimation }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif']"> - <MkSwitch v-model="disableShowingAnimatedImages"> - <template #label><SearchLabel>{{ i18n.ts.disableShowingAnimatedImages }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="disableShowingAnimatedImages"> + <MkSwitch v-model="disableShowingAnimatedImages"> + <template #label><SearchLabel>{{ i18n.ts.disableShowingAnimatedImages }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['mfm', 'enable', 'show', 'animated']"> - <MkSwitch v-model="animatedMfm"> - <template #label><SearchLabel>{{ i18n.ts.enableAnimatedMfm }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="animatedMfm"> + <MkSwitch v-model="animatedMfm"> + <template #label><SearchLabel>{{ i18n.ts.enableAnimatedMfm }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['swipe', 'horizontal', 'tab']"> - <MkSwitch v-model="enableHorizontalSwipe"> - <template #label><SearchLabel>{{ i18n.ts.enableHorizontalSwipe }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="enableHorizontalSwipe"> + <MkSwitch v-model="enableHorizontalSwipe"> + <template #label><SearchLabel>{{ i18n.ts.enableHorizontalSwipe }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['keep', 'screen', 'display', 'on']"> - <MkSwitch v-model="keepScreenOn"> - <template #label><SearchLabel>{{ i18n.ts.keepScreenOn }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="keepScreenOn"> + <MkSwitch v-model="keepScreenOn"> + <template #label><SearchLabel>{{ i18n.ts.keepScreenOn }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['native', 'system', 'video', 'audio', 'player', 'media']"> - <MkSwitch v-model="useNativeUIForVideoAudioPlayer"> - <template #label><SearchLabel>{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="useNativeUiForVideoAudioPlayer"> + <MkSwitch v-model="useNativeUiForVideoAudioPlayer"> + <template #label><SearchLabel>{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> </div> <SearchMarker :keywords="['contextmenu', 'system', 'native']"> - <MkSelect v-model="contextMenu"> - <template #label><SearchLabel>{{ i18n.ts._contextMenu.title }}</SearchLabel></template> - <option value="app">{{ i18n.ts._contextMenu.app }}</option> - <option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option> - <option value="native">{{ i18n.ts._contextMenu.native }}</option> - </MkSelect> + <MkPreferenceContainer k="contextMenu"> + <MkSelect v-model="contextMenu"> + <template #label><SearchLabel>{{ i18n.ts._contextMenu.title }}</SearchLabel></template> + <option value="app">{{ i18n.ts._contextMenu.app }}</option> + <option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option> + <option value="native">{{ i18n.ts._contextMenu.native }}</option> + </MkSelect> + </MkPreferenceContainer> </SearchMarker> </div> </SearchMarker> @@ -60,18 +74,19 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, ref, watch } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; import { reloadAsk } from '@/scripts/reload-ask.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; -const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v)); -const animatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm')); -const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages')); -const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn')); -const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe')); -const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer')); -const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu')); +const reduceAnimation = prefer.model('animation', v => !v, v => !v); +const animatedMfm = prefer.model('animatedMfm'); +const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages'); +const keepScreenOn = prefer.model('keepScreenOn'); +const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe'); +const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer'); +const contextMenu = prefer.model('contextMenu'); watch([ keepScreenOn, diff --git a/packages/frontend/src/pages/settings/appearance.vue b/packages/frontend/src/pages/settings/appearance.vue index 465c2a38c2..b23f32aec4 100644 --- a/packages/frontend/src/pages/settings/appearance.vue +++ b/packages/frontend/src/pages/settings/appearance.vue @@ -10,73 +10,85 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_m"> <div class="_gaps_s"> <SearchMarker :keywords="['blur']"> - <MkSwitch v-model="useBlurEffect"> - <template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="useBlurEffect"> + <MkSwitch v-model="useBlurEffect"> + <template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['blur', 'modal']"> - <MkSwitch v-model="useBlurEffectForModal"> - <template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="useBlurEffectForModal"> + <MkSwitch v-model="useBlurEffectForModal"> + <template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail']"> - <MkSwitch v-model="highlightSensitiveMedia"> - <template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="highlightSensitiveMedia"> + <MkSwitch v-model="highlightSensitiveMedia"> + <template #label><SearchLabel>{{ i18n.ts.highlightSensitiveMedia }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['avatar', 'icon', 'square']"> - <MkSwitch v-model="squareAvatars"> - <template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="squareAvatars"> + <MkSwitch v-model="squareAvatars"> + <template #label><SearchLabel>{{ i18n.ts.squareAvatars }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']"> - <MkSwitch v-model="showAvatarDecorations"> - <template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="showAvatarDecorations"> + <MkSwitch v-model="showAvatarDecorations"> + <template #label><SearchLabel>{{ i18n.ts.showAvatarDecorations }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['note', 'timeline', 'gap']"> - <MkSwitch v-model="showGapBetweenNotesInTimeline"> - <template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template> - </MkSwitch> - </SearchMarker> - - <SearchMarker :keywords="['font', 'system', 'native']"> - <MkSwitch v-model="useSystemFont"> - <template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="showGapBetweenNotesInTimeline"> + <MkSwitch v-model="showGapBetweenNotesInTimeline"> + <template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['effect', 'show']"> - <MkSwitch v-model="enableSeasonalScreenEffect"> - <template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="enableSeasonalScreenEffect"> + <MkSwitch v-model="enableSeasonalScreenEffect"> + <template #label><SearchLabel>{{ i18n.ts.seasonalScreenEffect }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> </div> <SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']"> - <MkSelect v-model="menuStyle"> - <template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template> - <option value="auto">{{ i18n.ts.auto }}</option> - <option value="popup">{{ i18n.ts.popup }}</option> - <option value="drawer">{{ i18n.ts.drawer }}</option> - </MkSelect> + <MkPreferenceContainer k="menuStyle"> + <MkSelect v-model="menuStyle"> + <template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template> + <option value="auto">{{ i18n.ts.auto }}</option> + <option value="popup">{{ i18n.ts.popup }}</option> + <option value="drawer">{{ i18n.ts.drawer }}</option> + </MkSelect> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['emoji', 'style', 'native', 'system', 'fluent', 'twemoji']"> - <div> - <MkRadios v-model="emojiStyle"> - <template #label><SearchLabel>{{ i18n.ts.emojiStyle }}</SearchLabel></template> - <option value="native">{{ i18n.ts.native }}</option> - <option value="fluentEmoji">Fluent Emoji</option> - <option value="twemoji">Twemoji</option> - </MkRadios> - <div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div> - </div> + <MkPreferenceContainer k="emojiStyle"> + <div> + <MkRadios v-model="emojiStyle"> + <template #label><SearchLabel>{{ i18n.ts.emojiStyle }}</SearchLabel></template> + <option value="native">{{ i18n.ts.native }}</option> + <option value="fluentEmoji">Fluent Emoji</option> + <option value="twemoji">Twemoji</option> + </MkRadios> + <div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div> + </div> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['font', 'size']"> @@ -88,6 +100,12 @@ SPDX-License-Identifier: AGPL-3.0-only <option value="3"><span style="font-size: 17px;">Aa</span></option> </MkRadios> </SearchMarker> + + <SearchMarker :keywords="['font', 'system', 'native']"> + <MkSwitch v-model="useSystemFont"> + <template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template> + </MkSwitch> + </SearchMarker> </div> </FormSection> @@ -97,46 +115,56 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_m"> <SearchMarker :keywords="['reaction', 'size', 'scale', 'display']"> - <MkRadios v-model="reactionsDisplaySize"> - <template #label><SearchLabel>{{ i18n.ts.reactionsDisplaySize }}</SearchLabel></template> - <option value="small">{{ i18n.ts.small }}</option> - <option value="medium">{{ i18n.ts.medium }}</option> - <option value="large">{{ i18n.ts.large }}</option> - </MkRadios> + <MkPreferenceContainer k="reactionsDisplaySize"> + <MkRadios v-model="reactionsDisplaySize"> + <template #label><SearchLabel>{{ i18n.ts.reactionsDisplaySize }}</SearchLabel></template> + <option value="small">{{ i18n.ts.small }}</option> + <option value="medium">{{ i18n.ts.medium }}</option> + <option value="large">{{ i18n.ts.large }}</option> + </MkRadios> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['reaction', 'size', 'scale', 'display', 'width', 'limit']"> - <MkSwitch v-model="limitWidthOfReaction"> - <template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="limitWidthOfReaction"> + <MkSwitch v-model="limitWidthOfReaction"> + <template #label><SearchLabel>{{ i18n.ts.limitWidthOfReaction }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height']"> - <MkRadios v-model="mediaListWithOneImageAppearance"> - <template #label><SearchLabel>{{ i18n.ts.mediaListWithOneImageAppearance }}</SearchLabel></template> - <option value="expand">{{ i18n.ts.default }}</option> - <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option> - <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option> - <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option> - </MkRadios> + <MkPreferenceContainer k="mediaListWithOneImageAppearance"> + <MkRadios v-model="mediaListWithOneImageAppearance"> + <template #label><SearchLabel>{{ i18n.ts.mediaListWithOneImageAppearance }}</SearchLabel></template> + <option value="expand">{{ i18n.ts.default }}</option> + <option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option> + <option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option> + <option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option> + </MkRadios> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation']"> - <MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker"> - <template #label><SearchLabel>{{ i18n.ts.instanceTicker }}</SearchLabel></template> - <option value="none">{{ i18n.ts._instanceTicker.none }}</option> - <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> - <option value="always">{{ i18n.ts._instanceTicker.always }}</option> - </MkSelect> + <MkPreferenceContainer k="instanceTicker"> + <MkSelect v-if="instance.federation !== 'none'" v-model="instanceTicker"> + <template #label><SearchLabel>{{ i18n.ts.instanceTicker }}</SearchLabel></template> + <option value="none">{{ i18n.ts._instanceTicker.none }}</option> + <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> + <option value="always">{{ i18n.ts._instanceTicker.always }}</option> + </MkSelect> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility']"> - <MkSelect v-model="nsfw"> - <template #label><SearchLabel>{{ i18n.ts.displayOfSensitiveMedia }}</SearchLabel></template> - <option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option> - <option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option> - <option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option> - </MkSelect> + <MkPreferenceContainer k="nsfw"> + <MkSelect v-model="nsfw"> + <template #label><SearchLabel>{{ i18n.ts.displayOfSensitiveMedia }}</SearchLabel></template> + <option value="respect">{{ i18n.ts._displayOfSensitiveMedia.respect }}</option> + <option value="ignore">{{ i18n.ts._displayOfSensitiveMedia.ignore }}</option> + <option value="force">{{ i18n.ts._displayOfSensitiveMedia.force }}</option> + </MkSelect> + </MkPreferenceContainer> </SearchMarker> </div> </FormSection> @@ -148,21 +176,25 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_m"> <SearchMarker :keywords="['position']"> - <MkRadios v-model="notificationPosition"> - <template #label><SearchLabel>{{ i18n.ts.position }}</SearchLabel></template> - <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option> - <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option> - <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option> - <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option> - </MkRadios> + <MkPreferenceContainer k="notificationPosition"> + <MkRadios v-model="notificationPosition"> + <template #label><SearchLabel>{{ i18n.ts.position }}</SearchLabel></template> + <option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option> + <option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option> + <option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option> + <option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option> + </MkRadios> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['stack', 'axis', 'direction']"> - <MkRadios v-model="notificationStackAxis"> - <template #label><SearchLabel>{{ i18n.ts.stackAxis }}</SearchLabel></template> - <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option> - <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option> - </MkRadios> + <MkPreferenceContainer k="notificationStackAxis"> + <MkRadios v-model="notificationStackAxis"> + <template #label><SearchLabel>{{ i18n.ts.stackAxis }}</SearchLabel></template> + <option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option> + <option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option> + </MkRadios> + </MkPreferenceContainer> </SearchMarker> <MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton> @@ -183,7 +215,7 @@ import * as Misskey from 'misskey-js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkRadios from '@/components/MkRadios.vue'; -import { defaultStore } from '@/store.js'; +import { prefer } from '@/preferences.js'; import { reloadAsk } from '@/scripts/reload-ask.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; @@ -194,26 +226,27 @@ import { claimAchievement } from '@/scripts/achievements.js'; import MkButton from '@/components/MkButton.vue'; import FormSection from '@/components/form/section.vue'; import { instance } from '@/instance.js'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; const fontSize = ref(miLocalStorage.getItem('fontSize')); const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null); -const showAvatarDecorations = computed(defaultStore.makeGetterSetter('showAvatarDecorations')); -const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle')); -const menuStyle = computed(defaultStore.makeGetterSetter('menuStyle')); -const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal')); -const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect')); -const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia')); -const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars')); -const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect')); -const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline')); -const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance')); -const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize')); -const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthOfReaction')); -const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition')); -const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis')); -const nsfw = computed(defaultStore.makeGetterSetter('nsfw')); -const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker')); +const showAvatarDecorations = prefer.model('showAvatarDecorations'); +const emojiStyle = prefer.model('emojiStyle'); +const menuStyle = prefer.model('menuStyle'); +const useBlurEffectForModal = prefer.model('useBlurEffectForModal'); +const useBlurEffect = prefer.model('useBlurEffect'); +const highlightSensitiveMedia = prefer.model('highlightSensitiveMedia'); +const squareAvatars = prefer.model('squareAvatars'); +const enableSeasonalScreenEffect = prefer.model('enableSeasonalScreenEffect'); +const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline'); +const mediaListWithOneImageAppearance = prefer.model('mediaListWithOneImageAppearance'); +const reactionsDisplaySize = prefer.model('reactionsDisplaySize'); +const limitWidthOfReaction = prefer.model('limitWidthOfReaction'); +const notificationPosition = prefer.model('notificationPosition'); +const notificationStackAxis = prefer.model('notificationStackAxis'); +const nsfw = prefer.model('nsfw'); +const instanceTicker = prefer.model('instanceTicker'); watch(fontSize, () => { if (fontSize.value == null) { diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue index e574ec7dc0..e1965b6d36 100644 --- a/packages/frontend/src/pages/settings/deck.vue +++ b/packages/frontend/src/pages/settings/deck.vue @@ -23,14 +23,14 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkRadios from '@/components/MkRadios.vue'; -import { deckStore } from '@/ui/deck/deck-store.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { prefer } from '@/preferences.js'; -const navWindow = computed(deckStore.makeGetterSetter('navWindow')); -const useSimpleUiForNonRootPages = computed(deckStore.makeGetterSetter('useSimpleUiForNonRootPages')); -const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn')); -const columnAlign = computed(deckStore.makeGetterSetter('columnAlign')); +const navWindow = prefer.model('deck.navWindow'); +const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages'); +const alwaysShowMainColumn = prefer.model('deck.alwaysShowMainColumn'); +const columnAlign = prefer.model('deck.columnAlign'); const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index 0138aac1c5..8e61b11dbe 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -50,17 +50,21 @@ SPDX-License-Identifier: AGPL-3.0-only </FormLink> <SearchMarker :keywords="['keep', 'original', 'raw', 'upload']"> - <MkSwitch v-model="keepOriginalUploading"> - <template #label><SearchLabel>{{ i18n.ts.keepOriginalUploading }}</SearchLabel></template> - <template #caption><SearchKeyword>{{ i18n.ts.keepOriginalUploadingDescription }}</SearchKeyword></template> - </MkSwitch> + <MkPreferenceContainer k="keepOriginalUploading"> + <MkSwitch v-model="keepOriginalUploading"> + <template #label><SearchLabel>{{ i18n.ts.keepOriginalUploading }}</SearchLabel></template> + <template #caption><SearchKeyword>{{ i18n.ts.keepOriginalUploadingDescription }}</SearchKeyword></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['keep', 'original', 'filename']"> - <MkSwitch v-model="keepOriginalFilename"> - <template #label><SearchLabel>{{ i18n.ts.keepOriginalFilename }}</SearchLabel></template> - <template #caption><SearchKeyword>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchKeyword></template> - </MkSwitch> + <MkPreferenceContainer k="keepOriginalFilename"> + <MkSwitch v-model="keepOriginalFilename"> + <template #label><SearchLabel>{{ i18n.ts.keepOriginalFilename }}</SearchLabel></template> + <template #caption><SearchKeyword>{{ i18n.ts.keepOriginalFilenameDescription }}</SearchKeyword></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file']"> @@ -93,11 +97,12 @@ import FormSplit from '@/components/form/split.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import bytes from '@/filters/bytes.js'; -import { defaultStore } from '@/store.js'; import MkChart from '@/components/MkChart.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { signinRequired } from '@/account.js'; +import { prefer } from '@/preferences.js'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; const $i = signinRequired(); @@ -120,8 +125,8 @@ const meterStyle = computed(() => { }; }); -const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading')); -const keepOriginalFilename = computed(defaultStore.makeGetterSetter('keepOriginalFilename')); +const keepOriginalUploading = prefer.model('keepOriginalUploading'); +const keepOriginalFilename = prefer.model('keepOriginalFilename'); misskeyApi('drive').then(info => { capacity.value = info.capacity; @@ -129,9 +134,9 @@ misskeyApi('drive').then(info => { fetching.value = false; }); -if (defaultStore.state.uploadFolder) { +if (prefer.s.uploadFolder) { misskeyApi('drive/folders/show', { - folderId: defaultStore.state.uploadFolder, + folderId: prefer.s.uploadFolder, }).then(response => { uploadFolder.value = response; }); @@ -139,11 +144,11 @@ if (defaultStore.state.uploadFolder) { function chooseUploadFolder() { os.selectDriveFolder(false).then(async folder => { - defaultStore.set('uploadFolder', folder[0] ? folder[0].id : null); + prefer.set('uploadFolder', folder[0] ? folder[0].id : null); os.success(); - if (defaultStore.state.uploadFolder) { + if (prefer.s.uploadFolder) { uploadFolder.value = await misskeyApi('drive/folders/show', { - folderId: defaultStore.state.uploadFolder, + folderId: prefer.s.uploadFolder, }); } else { uploadFolder.value = null; diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue index b16c943676..d2060a9112 100644 --- a/packages/frontend/src/pages/settings/emoji-picker.vue +++ b/packages/frontend/src/pages/settings/emoji-picker.vue @@ -89,37 +89,45 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ i18n.ts.emojiPickerDisplay }}</template> <div class="_gaps_m"> - <MkRadios v-model="emojiPickerScale"> - <template #label>{{ i18n.ts.size }}</template> - <option :value="1">{{ i18n.ts.small }}</option> - <option :value="2">{{ i18n.ts.medium }}</option> - <option :value="3">{{ i18n.ts.large }}</option> - </MkRadios> + <MkPreferenceContainer k="emojiPickerScale"> + <MkRadios v-model="emojiPickerScale"> + <template #label>{{ i18n.ts.size }}</template> + <option :value="1">{{ i18n.ts.small }}</option> + <option :value="2">{{ i18n.ts.medium }}</option> + <option :value="3">{{ i18n.ts.large }}</option> + </MkRadios> + </MkPreferenceContainer> - <MkRadios v-model="emojiPickerWidth"> - <template #label>{{ i18n.ts.numberOfColumn }}</template> - <option :value="1">5</option> - <option :value="2">6</option> - <option :value="3">7</option> - <option :value="4">8</option> - <option :value="5">9</option> - </MkRadios> + <MkPreferenceContainer k="emojiPickerWidth"> + <MkRadios v-model="emojiPickerWidth"> + <template #label>{{ i18n.ts.numberOfColumn }}</template> + <option :value="1">5</option> + <option :value="2">6</option> + <option :value="3">7</option> + <option :value="4">8</option> + <option :value="5">9</option> + </MkRadios> + </MkPreferenceContainer> - <MkRadios v-model="emojiPickerHeight"> - <template #label>{{ i18n.ts.height }}</template> - <option :value="1">{{ i18n.ts.small }}</option> - <option :value="2">{{ i18n.ts.medium }}</option> - <option :value="3">{{ i18n.ts.large }}</option> - <option :value="4">{{ i18n.ts.large }}+</option> - </MkRadios> + <MkPreferenceContainer k="emojiPickerHeight"> + <MkRadios v-model="emojiPickerHeight"> + <template #label>{{ i18n.ts.height }}</template> + <option :value="1">{{ i18n.ts.small }}</option> + <option :value="2">{{ i18n.ts.medium }}</option> + <option :value="3">{{ i18n.ts.large }}</option> + <option :value="4">{{ i18n.ts.large }}+</option> + </MkRadios> + </MkPreferenceContainer> - <MkSelect v-model="emojiPickerStyle"> - <template #label>{{ i18n.ts.style }}</template> - <template #caption>{{ i18n.ts.needReloadToApply }}</template> - <option value="auto">{{ i18n.ts.auto }}</option> - <option value="popup">{{ i18n.ts.popup }}</option> - <option value="drawer">{{ i18n.ts.drawer }}</option> - </MkSelect> + <MkPreferenceContainer k="emojiPickerStyle"> + <MkSelect v-model="emojiPickerStyle"> + <template #label>{{ i18n.ts.style }}</template> + <template #caption>{{ i18n.ts.needReloadToApply }}</template> + <option value="auto">{{ i18n.ts.auto }}</option> + <option value="popup">{{ i18n.ts.popup }}</option> + <option value="drawer">{{ i18n.ts.drawer }}</option> + </MkSelect> + </MkPreferenceContainer> </div> </FormSection> </div> @@ -127,14 +135,14 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref, watch } from 'vue'; -import type { Ref } from 'vue'; import Sortable from 'vuedraggable'; +import type { Ref } from 'vue'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; import FormSection from '@/components/form/section.vue'; import MkSelect from '@/components/MkSelect.vue'; import * as os from '@/os.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { deepClone } from '@/scripts/clone.js'; @@ -143,14 +151,16 @@ import { emojiPicker } from '@/scripts/emoji-picker.js'; import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; import MkEmoji from '@/components/global/MkEmoji.vue'; import MkFolder from '@/components/MkFolder.vue'; +import { prefer } from '@/preferences.js'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; -const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(defaultStore.state.reactions)); -const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmojis)); +const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(store.state.reactions)); +const pinnedEmojis: Ref<string[]> = ref(deepClone(store.state.pinnedEmojis)); -const emojiPickerScale = computed(defaultStore.makeGetterSetter('emojiPickerScale')); -const emojiPickerWidth = computed(defaultStore.makeGetterSetter('emojiPickerWidth')); -const emojiPickerHeight = computed(defaultStore.makeGetterSetter('emojiPickerHeight')); -const emojiPickerStyle = computed(defaultStore.makeGetterSetter('emojiPickerStyle')); +const emojiPickerScale = prefer.model('emojiPickerScale'); +const emojiPickerWidth = prefer.model('emojiPickerWidth'); +const emojiPickerHeight = prefer.model('emojiPickerHeight'); +const emojiPickerStyle = prefer.model('emojiPickerStyle'); const removeReaction = (reaction: string, ev: MouseEvent) => remove(pinnedEmojisForReaction, reaction, ev); const chooseReaction = (ev: MouseEvent) => pickEmoji(pinnedEmojisForReaction, ev); @@ -210,7 +220,7 @@ async function setDefault(itemsRef: Ref<string[]>) { }); if (canceled) return; - itemsRef.value = deepClone(defaultStore.def.reactions.default); + itemsRef.value = deepClone(store.def.reactions.default); } async function pickEmoji(itemsRef: Ref<string[]>, ev: MouseEvent) { @@ -230,13 +240,13 @@ function getHTMLElement(ev: MouseEvent): HTMLElement { } watch(pinnedEmojisForReaction, () => { - defaultStore.set('reactions', pinnedEmojisForReaction.value); + store.set('reactions', pinnedEmojisForReaction.value); }, { deep: true, }); watch(pinnedEmojis, () => { - defaultStore.set('pinnedEmojis', pinnedEmojis.value); + store.set('pinnedEmojis', pinnedEmojis.value); }, { deep: true, }); diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue index 6b67a9a1a8..949f9019d9 100644 --- a/packages/frontend/src/pages/settings/import-export.vue +++ b/packages/frontend/src/pages/settings/import-export.vue @@ -155,11 +155,11 @@ import { selectFile } from '@/scripts/select-file.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { $i } from '@/account.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; const excludeMutingUsers = ref(false); const excludeInactiveUsers = ref(false); -const withReplies = ref(defaultStore.state.defaultWithReplies); +const withReplies = ref(store.state.defaultWithReplies); const onExportSuccess = () => { os.alert({ diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index 458605d545..6203e7f698 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -12,6 +12,10 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="!narrow || currentPage?.route.name == null" class="nav"> <div class="baaadecd"> <MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> + <MkInfo v-if="!store.reactiveState.enablePreferencesAutoCloudBackup.value && store.reactiveState.showPreferencesAutoCloudBackupSuggestion.value" class="info"> + <div>{{ i18n.ts._preferencesBackup.autoPreferencesBackupIsNotEnabledForThisDevice }}</div> + <div><button class="_textButton" @click="enableAutoBackup">{{ i18n.ts.enable }}</button> | <button class="_textButton" @click="skipAutoBackup">{{ i18n.ts.skip }}</button></div> + </MkInfo> <MkSuperMenu :def="menuDef" :grid="narrow" :searchIndex="SETTING_INDEX"></MkSuperMenu> </div> </div> @@ -41,6 +45,8 @@ import { definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } import * as os from '@/os.js'; import { useRouter } from '@/router/supplier.js'; import { searchIndexes } from '@/scripts/autogen/settings-search-index.js'; +import { enableAutoBackup, getPreferencesProfileMenu } from '@/preferences/utility.js'; +import { store } from '@/store.js'; const SETTING_INDEX = searchIndexes; // TODO: lazy load @@ -65,6 +71,10 @@ const ro = new ResizeObserver((entries, observer) => { narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD; }); +function skipAutoBackup() { + store.set('showPreferencesAutoCloudBackupSuggestion', false); +} + const menuDef = computed<SuperMenuDef[]>(() => [{ items: [{ icon: 'ti ti-user', @@ -168,10 +178,12 @@ const menuDef = computed<SuperMenuDef[]>(() => [{ }], }, { items: [{ - icon: 'ti ti-device-floppy', - text: i18n.ts.preferencesBackups, - to: '/settings/preferences-backups', - active: currentPage.value?.route.name === 'preferences-backups', + type: 'button', + icon: 'ti ti-settings-2', + text: i18n.ts.preferencesProfile, + action: async (ev: MouseEvent) => { + os.popupMenu(getPreferencesProfileMenu(), ev.currentTarget ?? ev.target); + }, }, { type: 'button', icon: 'ti ti-trash', diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index 4aac2a25bd..3620c05ca8 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -186,8 +186,8 @@ import { signinRequired } from '@/account.js'; import MkInfo from '@/components/MkInfo.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkSwitch from '@/components/MkSwitch.vue'; -import { defaultStore } from '@/store'; import { reloadAsk } from '@/scripts/reload-ask.js'; +import { prefer } from '@/preferences.js'; const $i = signinRequired(); @@ -210,7 +210,7 @@ const expandedRenoteMuteItems = ref([]); const expandedMuteItems = ref([]); const expandedBlockItems = ref([]); -const showSoftWordMutedWord = computed(defaultStore.makeGetterSetter('showSoftWordMutedWord')); +const showSoftWordMutedWord = prefer.model('showSoftWordMutedWord'); watch([ showSoftWordMutedWord, diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index c38cdc4fc2..5e1dedd709 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -53,22 +53,24 @@ import FormSlot from '@/components/form/slot.vue'; import MkContainer from '@/components/MkContainer.vue'; import * as os from '@/os.js'; import { navbarItemDef } from '@/navbar.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { reloadAsk } from '@/scripts/reload-ask.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { prefer } from '@/preferences.js'; +import { PREF_DEF } from '@/preferences/def.js'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); -const items = ref(defaultStore.state.menu.map(x => ({ +const items = ref(prefer.s.menu.map(x => ({ id: Math.random().toString(), type: x, }))); -const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay')); +const menuDisplay = computed(store.makeGetterSetter('menuDisplay')); async function addItem() { - const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k)); + const menu = Object.keys(navbarItemDef).filter(k => !prefer.s.menu.includes(k)); const { canceled, result: item } = await os.select({ title: i18n.ts.addItem, items: [...menu.map(k => ({ @@ -89,12 +91,12 @@ function removeItem(index: number) { } async function save() { - defaultStore.set('menu', items.value.map(x => x.type)); + prefer.set('menu', items.value.map(x => x.type)); await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); } function reset() { - items.value = defaultStore.def.menu.default.map(x => ({ + items.value = PREF_DEF.menu.default.map(x => ({ id: Math.random().toString(), type: x, })); diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index 9742c548e7..21133d72e7 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -117,20 +117,21 @@ import MkKeyValue from '@/components/MkKeyValue.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { signout, signinRequired } from '@/account.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { reloadAsk } from '@/scripts/reload-ask.js'; import FormSection from '@/components/form/section.vue'; +import { prefer } from '@/preferences.js'; const $i = signinRequired(); -const reportError = computed(defaultStore.makeGetterSetter('reportError')); -const enableCondensedLine = computed(defaultStore.makeGetterSetter('enableCondensedLine')); -const skipNoteRender = computed(defaultStore.makeGetterSetter('skipNoteRender')); -const devMode = computed(defaultStore.makeGetterSetter('devMode')); -const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies')); +const reportError = prefer.model('reportError'); +const enableCondensedLine = prefer.model('enableCondensedLine'); +const skipNoteRender = prefer.model('skipNoteRender'); +const devMode = prefer.model('devMode'); +const defaultWithReplies = computed(store.makeGetterSetter('defaultWithReplies')); watch(skipNoteRender, async () => { await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); diff --git a/packages/frontend/src/pages/settings/plugin.install.vue b/packages/frontend/src/pages/settings/plugin.install.vue index 3ab26e80d9..c79aec91c0 100644 --- a/packages/frontend/src/pages/settings/plugin.install.vue +++ b/packages/frontend/src/pages/settings/plugin.install.vue @@ -23,10 +23,10 @@ import MkCodeEditor from '@/components/MkCodeEditor.vue'; import MkButton from '@/components/MkButton.vue'; import FormInfo from '@/components/MkInfo.vue'; import * as os from '@/os.js'; -import { installPlugin } from '@/scripts/install-plugin.js'; import { unisonReload } from '@/scripts/unison-reload.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { installPlugin } from '@/plugin.js'; const code = ref<string | null>(null); diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue index 3c3dcfe41e..fe57812a1d 100644 --- a/packages/frontend/src/pages/settings/plugin.vue +++ b/packages/frontend/src/pages/settings/plugin.vue @@ -4,72 +4,92 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div class="_gaps_m"> - <FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink> +<SearchMarker path="/settings/plugin" :label="i18n.ts.plugins" :keywords="['plugin']" icon="ti ti-plug"> + <div class="_gaps_m"> + <FormLink to="/settings/plugin/install"><template #icon><i class="ti ti-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink> - <FormSection> - <template #label>{{ i18n.ts.manage }}</template> - <div class="_gaps_s"> - <div v-for="plugin in plugins" :key="plugin.id" class="_panel _gaps_m" style="padding: 20px;"> - <div class="_gaps_s"> - <span style="display: flex; align-items: center;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span> - <MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch> - </div> + <FormSection> + <template #label>{{ i18n.ts.manage }}</template> + <div class="_gaps_s"> + <MkFolder v-for="plugin in plugins" :key="plugin.installId"> + <template #icon><i class="ti ti-plug"></i></template> + <template #suffix> + <i v-if="plugin.active" class="ti ti-player-play" style="color: var(--MI_THEME-accent);"></i> + <i v-else class="ti ti-player-pause" style="opacity: 0.7;"></i> + </template> + <template #label> + <div :style="plugin.active ? '' : 'opacity: 0.7;'"> + {{ plugin.name }} + <span style="margin-left: 1em; opacity: 0.7;">v{{ plugin.version }}</span> + </div> + </template> + <template #caption> + {{ plugin.description }} + </template> + <template #footer> + <div class="_buttons"> + <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="ti ti-settings"></i> {{ i18n.ts.settings }}</MkButton> + <MkButton inline danger @click="uninstall(plugin)"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton> + </div> + </template> - <div class="_gaps_s"> - <MkKeyValue> - <template #key>{{ i18n.ts.author }}</template> - <template #value>{{ plugin.author }}</template> - </MkKeyValue> - <MkKeyValue> - <template #key>{{ i18n.ts.description }}</template> - <template #value>{{ plugin.description }}</template> - </MkKeyValue> - <MkKeyValue> - <template #key>{{ i18n.ts.permission }}</template> - <template #value> - <ul style="margin-top: 0; margin-bottom: 0;"> - <li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li> - <li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li> - </ul> - </template> - </MkKeyValue> - </div> + <div class="_gaps_m"> + <div class="_gaps_s"> + <span style="display: flex; align-items: center;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span> + <MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch> + </div> - <div class="_buttons"> - <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="ti ti-settings"></i> {{ i18n.ts.settings }}</MkButton> - <MkButton inline danger @click="uninstall(plugin)"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton> - </div> + <div class="_gaps_s"> + <MkKeyValue> + <template #key>{{ i18n.ts.author }}</template> + <template #value>{{ plugin.author }}</template> + </MkKeyValue> + <MkKeyValue> + <template #key>{{ i18n.ts.description }}</template> + <template #value>{{ plugin.description }}</template> + </MkKeyValue> + <MkKeyValue> + <template #key>{{ i18n.ts.permission }}</template> + <template #value> + <ul style="margin-top: 0; margin-bottom: 0;"> + <li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li> + <li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li> + </ul> + </template> + </MkKeyValue> + </div> - <MkFolder> - <template #icon><i class="ti ti-terminal-2"></i></template> - <template #label>{{ i18n.ts._plugin.viewLog }}</template> + <MkFolder> + <template #icon><i class="ti ti-terminal-2"></i></template> + <template #label>{{ i18n.ts._plugin.viewLog }}</template> - <div class="_gaps_s"> - <div class="_buttons"> - <MkButton inline @click="copy(pluginLogs.get(plugin.id)?.join('\n'))"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton> - </div> + <div class="_gaps_s"> + <div class="_buttons"> + <MkButton inline @click="copy(pluginLogs.get(plugin.installId)?.join('\n'))"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton> + </div> - <MkCode :code="pluginLogs.get(plugin.id)?.join('\n') ?? ''"/> - </div> - </MkFolder> + <MkCode :code="pluginLogs.get(plugin.installId)?.join('\n') ?? ''"/> + </div> + </MkFolder> - <MkFolder> - <template #icon><i class="ti ti-code"></i></template> - <template #label>{{ i18n.ts._plugin.viewSource }}</template> + <MkFolder> + <template #icon><i class="ti ti-code"></i></template> + <template #label>{{ i18n.ts._plugin.viewSource }}</template> - <div class="_gaps_s"> - <div class="_buttons"> - <MkButton inline @click="copy(plugin.src)"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton> - </div> + <div class="_gaps_s"> + <div class="_buttons"> + <MkButton inline @click="copy(plugin.src)"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton> + </div> - <MkCode :code="plugin.src ?? ''" lang="is"/> + <MkCode :code="plugin.src ?? ''" lang="ais"/> + </div> + </MkFolder> </div> </MkFolder> </div> - </div> - </FormSection> -</div> + </FormSection> + </div> +</SearchMarker> </template> <script lang="ts" setup> @@ -83,19 +103,16 @@ import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import * as os from '@/os.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { ColdDeviceStorage } from '@/store.js'; import { unisonReload } from '@/scripts/unison-reload.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { pluginLogs } from '@/plugin.js'; +import { changePluginActive, configPlugin, pluginLogs, uninstallPlugin } from '@/plugin.js'; +import { prefer } from '@/preferences.js'; -const plugins = ref(ColdDeviceStorage.get('plugins')); +const plugins = prefer.r.plugins; async function uninstall(plugin) { - ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id)); - await os.apiWithDialog('i/revoke-token', { - token: plugin.token, - }); + await uninstallPlugin(plugin); nextTick(() => { unisonReload(); }); @@ -106,30 +123,15 @@ function copy(text) { os.success(); } -// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする async function config(plugin) { - const config = plugin.config; - for (const key in plugin.configData) { - config[key].default = plugin.configData[key]; - } - - const { canceled, result } = await os.form(plugin.name, config); - if (canceled) return; - - const coldPlugins = ColdDeviceStorage.get('plugins'); - coldPlugins.find(p => p.id === plugin.id)!.configData = result; - ColdDeviceStorage.set('plugins', coldPlugins); - + await configPlugin(plugin); nextTick(() => { location.reload(); }); } function changeActive(plugin, active) { - const coldPlugins = ColdDeviceStorage.get('plugins'); - coldPlugins.find(p => p.id === plugin.id)!.active = active; - ColdDeviceStorage.set('plugins', coldPlugins); - + changePluginActive(plugin, active); nextTick(() => { location.reload(); }); diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue deleted file mode 100644 index 7388e014ed..0000000000 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ /dev/null @@ -1,465 +0,0 @@ -<!-- -SPDX-FileCopyrightText: syuilo and misskey-project -SPDX-License-Identifier: AGPL-3.0-only ---> - -<template> -<div class="_gaps_m"> - <div :class="$style.buttons"> - <MkButton inline primary @click="saveNew">{{ i18n.ts._preferencesBackups.saveNew }}</MkButton> - <MkButton inline @click="loadFile">{{ i18n.ts._preferencesBackups.loadFile }}</MkButton> - </div> - - <FormSection> - <template #label>{{ i18n.ts._preferencesBackups.list }}</template> - <template v-if="profiles && Object.keys(profiles).length > 0"> - <div class="_gaps_s"> - <div - v-for="(profile, id) in profiles" - :key="id" - class="_panel" - :class="$style.profile" - @click="$event => menu($event, id)" - @contextmenu.prevent.stop="$event => menu($event, id)" - > - <div :class="$style.profileName">{{ profile.name }}</div> - <div :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.createdAt({ date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div> - <div v-if="profile.updatedAt" :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.updatedAt({ date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div> - </div> - </div> - </template> - <div v-else-if="profiles"> - <MkInfo>{{ i18n.ts._preferencesBackups.noBackups }}</MkInfo> - </div> - <MkLoading v-else/> - </FormSection> -</div> -</template> - -<script lang="ts" setup> -import { onMounted, onUnmounted, ref } from 'vue'; -import { v4 as uuid } from 'uuid'; -import { version, host } from '@@/js/config.js'; -import FormSection from '@/components/form/section.vue'; -import MkButton from '@/components/MkButton.vue'; -import MkInfo from '@/components/MkInfo.vue'; -import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { ColdDeviceStorage, defaultStore } from '@/store.js'; -import { unisonReload } from '@/scripts/unison-reload.js'; -import { useStream } from '@/stream.js'; -import { $i } from '@/account.js'; -import { i18n } from '@/i18n.js'; -import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { miLocalStorage } from '@/local-storage.js'; - -const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ - 'collapseRenotes', - 'menu', - 'visibility', - 'localOnly', - 'statusbars', - 'widgets', - 'tl', - 'pinnedUserLists', - 'overridedDeviceKind', - 'serverDisconnectedBehavior', - 'nsfw', - 'highlightSensitiveMedia', - 'animation', - 'animatedMfm', - 'advancedMfm', - 'showReactionsCount', - 'loadRawImages', - 'imageNewTab', - 'dataSaver', - 'disableShowingAnimatedImages', - 'emojiStyle', - 'menuStyle', - 'useBlurEffectForModal', - 'useBlurEffect', - 'showFixedPostForm', - 'showFixedPostFormInChannel', - 'enableInfiniteScroll', - 'useReactionPickerForContextMenu', - 'showGapBetweenNotesInTimeline', - 'instanceTicker', - 'emojiPickerScale', - 'emojiPickerWidth', - 'emojiPickerHeight', - 'emojiPickerStyle', - 'defaultSideView', - 'menuDisplay', - 'reportError', - 'squareAvatars', - 'showAvatarDecorations', - 'numberOfPageCache', - 'showNoteActionsOnlyHover', - 'showClipButtonInNoteFooter', - 'reactionsDisplaySize', - 'forceShowAds', - 'aiChanMode', - 'devMode', - 'mediaListWithOneImageAppearance', - 'notificationPosition', - 'notificationStackAxis', - 'keepScreenOn', - 'defaultWithReplies', - 'disableStreamingTimeline', - 'useGroupedNotifications', - 'sound_masterVolume', - 'sound_note', - 'sound_noteMy', - 'sound_notification', -]; -const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [ - 'lightTheme', - 'darkTheme', - 'syncDeviceDarkMode', - 'plugins', -]; - -const scope = ['clientPreferencesProfiles']; - -const profileProps = ['name', 'createdAt', 'updatedAt', 'misskeyVersion', 'settings', 'host']; - -type Profile = { - name: string; - createdAt: string; - updatedAt: string | null; - misskeyVersion: string; - host: string; - settings: { - hot: Record<keyof typeof defaultStoreSaveKeys, unknown>; - cold: Record<keyof typeof coldDeviceStorageSaveKeys, unknown>; - fontSize: string | null; - useSystemFont: 't' | null; - wallpaper: string | null; - }; -}; - -const connection = $i && useStream().useChannel('main'); - -const profiles = ref<Record<string, Profile> | null>(null); - -misskeyApi('i/registry/get-all', { scope }) - .then(res => { - profiles.value = res || {}; - }); - -function isObject(value: unknown): value is Record<string, unknown> { - return value != null && typeof value === 'object' && !Array.isArray(value); -} - -function validate(profile: any): void { - if (!isObject(profile)) throw new Error('not an object'); - - // Check if unnecessary properties exist - if (Object.keys(profile).some(key => !profileProps.includes(key))) throw new Error('Unnecessary properties exist'); - - if (!profile.name) throw new Error('Missing required prop: name'); - if (!profile.misskeyVersion) throw new Error('Missing required prop: misskeyVersion'); - - // Check if createdAt and updatedAt is Date - // https://zenn.dev/lollipop_onl/articles/eoz-judge-js-invalid-date - if (!profile.createdAt || Number.isNaN(new Date(profile.createdAt as any).getTime())) throw new Error('createdAt is falsy or not Date'); - if (profile.updatedAt) { - if (Number.isNaN(new Date(profile.updatedAt as any).getTime())) { - throw new Error('updatedAt is not Date'); - } - } else if (profile.updatedAt !== null) { - throw new Error('updatedAt is not null'); - } - - if (!profile.settings) throw new Error('Missing required prop: settings'); - if (!isObject(profile.settings)) throw new Error('Invalid prop: settings'); -} - -function getSettings(): Profile['settings'] { - const hot = {} as Record<keyof typeof defaultStoreSaveKeys, unknown>; - for (const key of defaultStoreSaveKeys) { - hot[key] = defaultStore.state[key]; - } - - const cold = {} as Record<keyof typeof coldDeviceStorageSaveKeys, unknown>; - for (const key of coldDeviceStorageSaveKeys) { - cold[key] = ColdDeviceStorage.get(key); - } - - return { - hot, - cold, - fontSize: miLocalStorage.getItem('fontSize'), - useSystemFont: miLocalStorage.getItem('useSystemFont') as 't' | null, - wallpaper: miLocalStorage.getItem('wallpaper'), - }; -} - -async function saveNew(): Promise<void> { - if (!profiles.value) return; - - const { canceled, result: name } = await os.inputText({ - title: i18n.ts._preferencesBackups.inputName, - default: '', - }); - if (canceled) return; - - if (Object.values(profiles.value).some(x => x.name === name)) { - return os.alert({ - title: i18n.ts._preferencesBackups.cannotSave, - text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }), - }); - } - - const id = uuid(); - const profile: Profile = { - name, - createdAt: (new Date()).toISOString(), - updatedAt: null, - misskeyVersion: version, - host, - settings: getSettings(), - }; - await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile }); -} - -function loadFile(): void { - const input = document.createElement('input'); - input.type = 'file'; - input.multiple = false; - input.onchange = async () => { - if (!profiles.value) return; - if (!input.files || input.files.length === 0) return; - - const file = input.files[0]; - - if (file.type !== 'application/json') { - return os.alert({ - type: 'error', - title: i18n.ts._preferencesBackups.cannotLoad, - text: i18n.ts._preferencesBackups.invalidFile, - }); - } - - let profile: Profile; - try { - profile = JSON.parse(await file.text()) as unknown as Profile; - validate(profile); - } catch (err) { - return os.alert({ - type: 'error', - title: i18n.ts._preferencesBackups.cannotLoad, - text: (err as any)?.message ?? '', - }); - } - - const id = uuid(); - await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile }); - - // 一応廃棄 - (window as any).__misskey_input_ref__ = null; - }; - - // https://qiita.com/fukasawah/items/b9dc732d95d99551013d - // iOS Safari で正常に動かす為のおまじない - (window as any).__misskey_input_ref__ = input; - - input.click(); -} - -async function applyProfile(id: string): Promise<void> { - if (!profiles.value) return; - - const profile = profiles.value[id]; - - const { canceled: cancel1 } = await os.confirm({ - type: 'warning', - title: i18n.ts._preferencesBackups.apply, - text: i18n.tsx._preferencesBackups.applyConfirm({ name: profile.name }), - }); - if (cancel1) return; - - // TODO: バージョン or ホストが違ったらさらに警告を表示 - - const settings = profile.settings; - - // defaultStore - for (const key of defaultStoreSaveKeys) { - if (settings.hot[key] !== undefined) { - defaultStore.set(key, settings.hot[key]); - } - } - - // coldDeviceStorage - for (const key of coldDeviceStorageSaveKeys) { - if (settings.cold[key] !== undefined) { - ColdDeviceStorage.set(key, settings.cold[key]); - } - } - - // fontSize - if (settings.fontSize) { - miLocalStorage.setItem('fontSize', settings.fontSize); - } else { - miLocalStorage.removeItem('fontSize'); - } - - // useSystemFont - if (settings.useSystemFont) { - miLocalStorage.setItem('useSystemFont', settings.useSystemFont); - } else { - miLocalStorage.removeItem('useSystemFont'); - } - - // wallpaper - if (settings.wallpaper != null) { - miLocalStorage.setItem('wallpaper', settings.wallpaper); - } else { - miLocalStorage.removeItem('wallpaper'); - } - - const { canceled: cancel2 } = await os.confirm({ - type: 'info', - text: i18n.ts.reloadToApplySetting, - }); - if (cancel2) return; - - unisonReload(); -} - -async function deleteProfile(id: string): Promise<void> { - if (!profiles.value) return; - - const { canceled } = await os.confirm({ - type: 'info', - title: i18n.ts.delete, - text: i18n.tsx.deleteAreYouSure({ x: profiles.value[id].name }), - }); - if (canceled) return; - - await os.apiWithDialog('i/registry/remove', { scope, key: id }); - delete profiles.value[id]; -} - -async function save(id: string): Promise<void> { - if (!profiles.value) return; - - const { name, createdAt } = profiles.value[id]; - - const { canceled } = await os.confirm({ - type: 'info', - title: i18n.ts._preferencesBackups.save, - text: i18n.tsx._preferencesBackups.saveConfirm({ name }), - }); - if (canceled) return; - - const profile: Profile = { - name, - createdAt, - updatedAt: (new Date()).toISOString(), - misskeyVersion: version, - host, - settings: getSettings(), - }; - await os.apiWithDialog('i/registry/set', { scope, key: id, value: profile }); -} - -async function rename(id: string): Promise<void> { - if (!profiles.value) return; - - const { canceled: cancel1, result: name } = await os.inputText({ - title: i18n.ts._preferencesBackups.inputName, - default: '', - }); - if (cancel1 || profiles.value[id].name === name) return; - - if (Object.values(profiles.value).some(x => x.name === name)) { - return os.alert({ - title: i18n.ts._preferencesBackups.cannotSave, - text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }), - }); - } - - const registry = Object.assign({}, { ...profiles.value[id] }); - - const { canceled: cancel2 } = await os.confirm({ - type: 'info', - title: i18n.ts.rename, - text: i18n.tsx._preferencesBackups.renameConfirm({ old: registry.name, new: name }), - }); - if (cancel2) return; - - registry.name = name; - await os.apiWithDialog('i/registry/set', { scope, key: id, value: registry }); -} - -function menu(ev: MouseEvent, profileId: string) { - if (!profiles.value) return; - - return os.popupMenu([{ - text: i18n.ts._preferencesBackups.apply, - icon: 'ti ti-check', - action: () => applyProfile(profileId), - }, { - type: 'a', - text: i18n.ts.download, - icon: 'ti ti-download', - href: URL.createObjectURL(new Blob([JSON.stringify(profiles.value[profileId], null, 2)], { type: 'application/json' })), - download: `${profiles.value[profileId].name}.json`, - }, { type: 'divider' }, { - text: i18n.ts.rename, - icon: 'ti ti-forms', - action: () => rename(profileId), - }, { - text: i18n.ts._preferencesBackups.save, - icon: 'ti ti-device-floppy', - action: () => save(profileId), - }, { type: 'divider' }, { - text: i18n.ts.delete, - icon: 'ti ti-trash', - action: () => deleteProfile(profileId), - danger: true, - }], (ev.currentTarget ?? ev.target ?? undefined) as unknown as HTMLElement | undefined); -} - -onMounted(() => { - // streamingのuser storage updateイベントを監視して更新 - connection?.on('registryUpdated', ({ scope: recievedScope, key, value }) => { - if (!recievedScope || recievedScope.length !== scope.length || recievedScope[0] !== scope[0]) return; - if (!profiles.value) return; - - profiles.value[key] = value; - }); -}); - -onUnmounted(() => { - connection?.off('registryUpdated'); -}); - -definePageMetadata(() => ({ - title: i18n.ts.preferencesBackups, - icon: 'ti ti-device-floppy', -})); -</script> - -<style lang="scss" module> -.buttons { - display: flex; - gap: var(--MI-margin); - flex-wrap: wrap; -} - -.profile { - padding: 20px; - cursor: pointer; - - &Name { - font-weight: 700; - } - - &Time { - font-size: .85em; - opacity: .7; - } -} -</style> diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index fe718bfa69..2df621eaa6 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -33,30 +33,36 @@ SPDX-License-Identifier: AGPL-3.0-only <FormSection> <div class="_gaps_s"> <SearchMarker :keywords="['post', 'form', 'timeline']"> - <MkSwitch v-model="showFixedPostForm"> - <template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="showFixedPostForm"> + <MkSwitch v-model="showFixedPostForm"> + <template #label><SearchLabel>{{ i18n.ts.showFixedPostForm }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['post', 'form', 'timeline', 'channel']"> - <MkSwitch v-model="showFixedPostFormInChannel"> - <template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="showFixedPostFormInChannel"> + <MkSwitch v-model="showFixedPostFormInChannel"> + <template #label><SearchLabel>{{ i18n.ts.showFixedPostFormInChannel }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['pinned', 'list']"> <MkFolder> <template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template> <!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ --> - <MkButton v-if="defaultStore.reactiveState.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton> + <MkButton v-if="prefer.r.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton> <MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton> </MkFolder> </SearchMarker> <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn']"> - <MkSwitch v-model="enableQuickAddMfmFunction"> - <template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="enableQuickAddMfmFunction"> + <MkSwitch v-model="enableQuickAddMfmFunction"> + <template #label><SearchLabel>{{ i18n.ts.enableQuickAddMfmFunction }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> </div> </FormSection> @@ -68,40 +74,52 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_m"> <div class="_gaps_s"> <SearchMarker :keywords="['renote']"> - <MkSwitch v-model="collapseRenotes"> - <template #label><SearchLabel>{{ i18n.ts.collapseRenotes }}</SearchLabel></template> - <template #caption><SearchKeyword>{{ i18n.ts.collapseRenotesDescription }}</SearchKeyword></template> - </MkSwitch> + <MkPreferenceContainer k="collapseRenotes"> + <MkSwitch v-model="collapseRenotes"> + <template #label><SearchLabel>{{ i18n.ts.collapseRenotes }}</SearchLabel></template> + <template #caption><SearchKeyword>{{ i18n.ts.collapseRenotesDescription }}</SearchKeyword></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['hover', 'show', 'footer', 'action']"> - <MkSwitch v-model="showNoteActionsOnlyHover"> - <template #label><SearchLabel>{{ i18n.ts.showNoteActionsOnlyHover }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="showNoteActionsOnlyHover"> + <MkSwitch v-model="showNoteActionsOnlyHover"> + <template #label><SearchLabel>{{ i18n.ts.showNoteActionsOnlyHover }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['footer', 'action', 'clip', 'show']"> - <MkSwitch v-model="showClipButtonInNoteFooter"> - <template #label><SearchLabel>{{ i18n.ts.showClipButtonInNoteFooter }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="showClipButtonInNoteFooter"> + <MkSwitch v-model="showClipButtonInNoteFooter"> + <template #label><SearchLabel>{{ i18n.ts.showClipButtonInNoteFooter }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['mfm', 'enable', 'show', 'advanced']"> - <MkSwitch v-model="advancedMfm"> - <template #label><SearchLabel>{{ i18n.ts.enableAdvancedMfm }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="advancedMfm"> + <MkSwitch v-model="advancedMfm"> + <template #label><SearchLabel>{{ i18n.ts.enableAdvancedMfm }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['reaction', 'count', 'show']"> - <MkSwitch v-model="showReactionsCount"> - <template #label><SearchLabel>{{ i18n.ts.showReactionsCount }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="showReactionsCount"> + <MkSwitch v-model="showReactionsCount"> + <template #label><SearchLabel>{{ i18n.ts.showReactionsCount }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment']"> - <MkSwitch v-model="loadRawImages"> - <template #label><SearchLabel>{{ i18n.ts.loadRawImages }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="loadRawImages"> + <MkSwitch v-model="loadRawImages"> + <template #label><SearchLabel>{{ i18n.ts.loadRawImages }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> </div> </div> @@ -114,9 +132,11 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_m"> <SearchMarker :keywords="['group']"> - <MkSwitch v-model="useGroupedNotifications"> - <template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="useGroupedNotifications"> + <MkSwitch v-model="useGroupedNotifications"> + <template #label><SearchLabel>{{ i18n.ts.useGroupedNotifications }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> </div> </FormSection> @@ -129,62 +149,88 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_m"> <div class="_gaps_s"> <SearchMarker :keywords="['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab']"> - <MkSwitch v-model="imageNewTab"> - <template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="imageNewTab"> + <MkSwitch v-model="imageNewTab"> + <template #label><SearchLabel>{{ i18n.ts.openImageInNewTab }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['reaction', 'picker', 'contextmenu', 'open']"> - <MkSwitch v-model="useReactionPickerForContextMenu"> - <template #label><SearchLabel>{{ i18n.ts.useReactionPickerForContextMenu }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="useReactionPickerForContextMenu"> + <MkSwitch v-model="useReactionPickerForContextMenu"> + <template #label><SearchLabel>{{ i18n.ts.useReactionPickerForContextMenu }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['load', 'auto', 'more']"> - <MkSwitch v-model="enableInfiniteScroll"> - <template #label><SearchLabel>{{ i18n.ts.enableInfiniteScroll }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="enableInfiniteScroll"> + <MkSwitch v-model="enableInfiniteScroll"> + <template #label><SearchLabel>{{ i18n.ts.enableInfiniteScroll }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['disable', 'streaming', 'timeline']"> - <MkSwitch v-model="disableStreamingTimeline"> - <template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="disableStreamingTimeline"> + <MkSwitch v-model="disableStreamingTimeline"> + <template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['follow', 'confirm', 'always']"> - <MkSwitch v-model="alwaysConfirmFollow"> - <template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="alwaysConfirmFollow"> + <MkSwitch v-model="alwaysConfirmFollow"> + <template #label><SearchLabel>{{ i18n.ts.alwaysConfirmFollow }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm']"> - <MkSwitch v-model="confirmWhenRevealingSensitiveMedia"> - <template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="confirmWhenRevealingSensitiveMedia"> + <MkSwitch v-model="confirmWhenRevealingSensitiveMedia"> + <template #label><SearchLabel>{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['reaction', 'confirm']"> - <MkSwitch v-model="confirmOnReact"> - <template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="confirmOnReact"> + <MkSwitch v-model="confirmOnReact"> + <template #label><SearchLabel>{{ i18n.ts.confirmOnReact }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> + </SearchMarker> + + <SearchMarker :keywords="['remember', 'keep', 'note', 'cw']"> + <MkPreferenceContainer k="keepCw"> + <MkSwitch v-model="keepCw"> + <template #label><SearchLabel>{{ i18n.ts.keepCw }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> </div> <SearchMarker :keywords="['server', 'disconnect', 'reconnect', 'reload', 'streaming']"> - <MkSelect v-model="serverDisconnectedBehavior"> - <template #label><SearchLabel>{{ i18n.ts.whenServerDisconnected }}</SearchLabel></template> - <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> - <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> - <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> - </MkSelect> + <MkPreferenceContainer k="serverDisconnectedBehavior"> + <MkSelect v-model="serverDisconnectedBehavior"> + <template #label><SearchLabel>{{ i18n.ts.whenServerDisconnected }}</SearchLabel></template> + <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> + <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> + <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> + </MkSelect> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['cache', 'page']"> - <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing> - <template #label><SearchLabel>{{ i18n.ts.numberOfPageCache }}</SearchLabel></template> - <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template> - </MkRange> + <MkPreferenceContainer k="numberOfPageCache"> + <MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing> + <template #label><SearchLabel>{{ i18n.ts.numberOfPageCache }}</SearchLabel></template> + <template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template> + </MkRange> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :label="i18n.ts.dataSaver" :keywords="['datasaver']"> @@ -229,18 +275,22 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps"> <SearchMarker :keywords="['ad', 'show']"> - <MkSwitch v-model="forceShowAds"> - <template #label><SearchLabel>{{ i18n.ts.forceShowAds }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="forceShowAds"> + <MkSwitch v-model="forceShowAds"> + <template #label><SearchLabel>{{ i18n.ts.forceShowAds }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker> - <MkRadios v-model="hemisphere"> - <template #label><SearchLabel>{{ i18n.ts.hemisphere }}</SearchLabel></template> - <option value="N">{{ i18n.ts._hemisphere.N }}</option> - <option value="S">{{ i18n.ts._hemisphere.S }}</option> - <template #caption>{{ i18n.ts._hemisphere.caption }}</template> - </MkRadios> + <MkPreferenceContainer k="hemisphere"> + <MkRadios v-model="hemisphere"> + <template #label><SearchLabel>{{ i18n.ts.hemisphere }}</SearchLabel></template> + <option value="N">{{ i18n.ts._hemisphere.N }}</option> + <option value="S">{{ i18n.ts._hemisphere.S }}</option> + <template #caption>{{ i18n.ts._hemisphere.caption }}</template> + </MkRadios> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['emoji', 'dictionary', 'additional', 'extra']"> @@ -248,8 +298,8 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label><SearchLabel>{{ i18n.ts.additionalEmojiDictionary }}</SearchLabel></template> <div class="_buttons"> <template v-for="lang in emojiIndexLangs" :key="lang"> - <MkButton v-if="defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton> - <MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton> + <MkButton v-if="store.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton> + <MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ store.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton> </template> </div> </MkFolder> @@ -272,7 +322,6 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref, watch } from 'vue'; -import * as Misskey from 'misskey-js'; import { langs } from '@@/js/config.js'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; @@ -284,41 +333,44 @@ import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/MkLink.vue'; import MkInfo from '@/components/MkInfo.vue'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import * as os from '@/os.js'; -import { instance } from '@/instance.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { reloadAsk } from '@/scripts/reload-ask.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { miLocalStorage } from '@/local-storage.js'; +import { prefer } from '@/preferences.js'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; const lang = ref(miLocalStorage.getItem('lang')); -const dataSaver = ref(defaultStore.state.dataSaver); +const dataSaver = ref(prefer.s.dataSaver); + +const overridedDeviceKind = computed(store.makeGetterSetter('overridedDeviceKind')); -const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere')); -const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind')); -const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior')); -const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover')); -const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter')); -const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes')); -const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm')); -const showReactionsCount = computed(defaultStore.makeGetterSetter('showReactionsCount')); -const enableQuickAddMfmFunction = computed(defaultStore.makeGetterSetter('enableQuickAddMfmFunction')); -const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds')); -const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages')); -const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab')); -const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm')); -const showFixedPostFormInChannel = computed(defaultStore.makeGetterSetter('showFixedPostFormInChannel')); -const numberOfPageCache = computed(defaultStore.makeGetterSetter('numberOfPageCache')); -const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll')); -const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu')); -const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline')); -const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications')); -const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow')); -const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia')); -const confirmOnReact = computed(defaultStore.makeGetterSetter('confirmOnReact')); -const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu')); +const keepCw = prefer.model('keepCw'); +const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior'); +const hemisphere = prefer.model('hemisphere'); +const showNoteActionsOnlyHover = prefer.model('showNoteActionsOnlyHover'); +const showClipButtonInNoteFooter = prefer.model('showClipButtonInNoteFooter'); +const collapseRenotes = prefer.model('collapseRenotes'); +const advancedMfm = prefer.model('advancedMfm'); +const showReactionsCount = prefer.model('showReactionsCount'); +const enableQuickAddMfmFunction = prefer.model('enableQuickAddMfmFunction'); +const forceShowAds = prefer.model('forceShowAds'); +const loadRawImages = prefer.model('loadRawImages'); +const imageNewTab = prefer.model('imageNewTab'); +const showFixedPostForm = prefer.model('showFixedPostForm'); +const showFixedPostFormInChannel = prefer.model('showFixedPostFormInChannel'); +const numberOfPageCache = prefer.model('numberOfPageCache'); +const enableInfiniteScroll = prefer.model('enableInfiniteScroll'); +const useReactionPickerForContextMenu = prefer.model('useReactionPickerForContextMenu'); +const disableStreamingTimeline = prefer.model('disableStreamingTimeline'); +const useGroupedNotifications = prefer.model('useGroupedNotifications'); +const alwaysConfirmFollow = prefer.model('alwaysConfirmFollow'); +const confirmWhenRevealingSensitiveMedia = prefer.model('confirmWhenRevealingSensitiveMedia'); +const confirmOnReact = prefer.model('confirmOnReact'); +const contextMenu = prefer.model('contextMenu'); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); @@ -356,7 +408,7 @@ function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) { function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) { async function main() { - const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes; + const currentIndexes = store.state.additionalUnicodeEmojiIndexes; function download() { switch (lang) { @@ -368,7 +420,7 @@ function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) { } currentIndexes[lang] = await download(); - await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes); + await store.set('additionalUnicodeEmojiIndexes', currentIndexes); } os.promiseDialog(main()); @@ -376,9 +428,9 @@ function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) { function removeEmojiIndex(lang: string) { async function main() { - const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes; + const currentIndexes = store.state.additionalUnicodeEmojiIndexes; delete currentIndexes[lang]; - await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes); + await store.set('additionalUnicodeEmojiIndexes', currentIndexes); } os.promiseDialog(main()); @@ -393,16 +445,17 @@ async function setPinnedList() { })), }); if (canceled) return; + if (list == null) return; - defaultStore.set('pinnedUserLists', [list]); + prefer.set('pinnedUserLists', [list]); } function removePinnedList() { - defaultStore.set('pinnedUserLists', []); + prefer.set('pinnedUserLists', []); } function enableAllDataSaver() { - const g = { ...defaultStore.state.dataSaver }; + const g = { ...prefer.s.dataSaver }; Object.keys(g).forEach((key) => { g[key] = true; }); @@ -410,7 +463,7 @@ function enableAllDataSaver() { } function disableAllDataSaver() { - const g = { ...defaultStore.state.dataSaver }; + const g = { ...prefer.s.dataSaver }; Object.keys(g).forEach((key) => { g[key] = false; }); @@ -418,7 +471,7 @@ function disableAllDataSaver() { } watch(dataSaver, (to) => { - defaultStore.set('dataSaver', to); + prefer.set('dataSaver', to); }, { deep: true, }); diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index cd0d54a73b..792b4147da 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -172,38 +172,41 @@ SPDX-License-Identifier: AGPL-3.0-only <FormSection> <div class="_gaps_m"> <SearchMarker :keywords="['remember', 'keep', 'note', 'visibility']"> - <MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()"> - <template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="rememberNoteVisibility"> + <MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()"> + <template #label><SearchLabel>{{ i18n.ts.rememberNoteVisibility }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['default', 'note', 'visibility']"> - <MkFolder v-if="!rememberNoteVisibility"> - <template #label><SearchLabel>{{ i18n.ts.defaultNoteVisibility }}</SearchLabel></template> - <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template> - <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template> - <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template> - <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template> + <MkDisableSection :disabled="rememberNoteVisibility"> + <MkFolder> + <template #label><SearchLabel>{{ i18n.ts.defaultNoteVisibility }}</SearchLabel></template> + <template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template> + <template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template> + <template v-else-if="defaultNoteVisibility === 'followers'" #suffix>{{ i18n.ts._visibility.followers }}</template> + <template v-else-if="defaultNoteVisibility === 'specified'" #suffix>{{ i18n.ts._visibility.specified }}</template> + + <div class="_gaps_m"> + <MkPreferenceContainer k="defaultNoteVisibility"> + <MkSelect v-model="defaultNoteVisibility"> + <option value="public">{{ i18n.ts._visibility.public }}</option> + <option value="home">{{ i18n.ts._visibility.home }}</option> + <option value="followers">{{ i18n.ts._visibility.followers }}</option> + <option value="specified">{{ i18n.ts._visibility.specified }}</option> + </MkSelect> + </MkPreferenceContainer> - <div class="_gaps_m"> - <MkSelect v-model="defaultNoteVisibility"> - <option value="public">{{ i18n.ts._visibility.public }}</option> - <option value="home">{{ i18n.ts._visibility.home }}</option> - <option value="followers">{{ i18n.ts._visibility.followers }}</option> - <option value="specified">{{ i18n.ts._visibility.specified }}</option> - </MkSelect> - <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch> - </div> - </MkFolder> + <MkPreferenceContainer k="defaultNoteLocalOnly"> + <MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.disableFederation }}</MkSwitch> + </MkPreferenceContainer> + </div> + </MkFolder> + </MkDisableSection> </SearchMarker> </div> </FormSection> - - <SearchMarker :keywords="['remember', 'keep', 'note', 'cw']"> - <MkSwitch v-model="keepCw" @update:modelValue="save()"> - <template #label><SearchLabel>{{ i18n.ts.keepCw }}</SearchLabel></template> - </MkSwitch> - </SearchMarker> </div> </SearchMarker> </template> @@ -215,7 +218,6 @@ import MkSelect from '@/components/MkSelect.vue'; import FormSection from '@/components/form/section.vue'; import MkFolder from '@/components/MkFolder.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { signinRequired } from '@/account.js'; @@ -225,6 +227,8 @@ import { formatDateTimeString } from '@/scripts/format-time-string.js'; import MkInput from '@/components/MkInput.vue'; import * as os from '@/os.js'; import MkDisableSection from '@/components/MkDisableSection.vue'; +import { prefer } from '@/preferences.js'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; const $i = signinRequired(); @@ -241,10 +245,9 @@ const publicReactions = ref($i.publicReactions); const followingVisibility = ref($i.followingVisibility); const followersVisibility = ref($i.followersVisibility); -const defaultNoteVisibility = computed(defaultStore.makeGetterSetter('defaultNoteVisibility')); -const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly')); -const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility')); -const keepCw = computed(defaultStore.makeGetterSetter('keepCw')); +const defaultNoteVisibility = prefer.model('defaultNoteVisibility'); +const defaultNoteLocalOnly = prefer.model('defaultNoteLocalOnly'); +const rememberNoteVisibility = prefer.model('rememberNoteVisibility'); const makeNotesFollowersOnlyBefore_type = computed(() => { if (makeNotesFollowersOnlyBefore.value == null) { diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index 51148a1f72..f9ddbbc9ed 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -168,7 +168,7 @@ import { signinRequired } from '@/account.js'; import { langmap } from '@/scripts/langmap.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { claimAchievement } from '@/scripts/achievements.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { globalEvents } from '@/events.js'; import MkInfo from '@/components/MkInfo.vue'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -177,7 +177,7 @@ const $i = signinRequired(); const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); -const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance')); +const reactionAcceptance = computed(store.makeGetterSetter('reactionAcceptance')); function assertVaildLang(lang: string | null): lang is keyof typeof langmap { return lang != null && lang in langmap; diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue index 1df2d89277..808ae06f41 100644 --- a/packages/frontend/src/pages/settings/sounds.vue +++ b/packages/frontend/src/pages/settings/sounds.vue @@ -7,21 +7,27 @@ SPDX-License-Identifier: AGPL-3.0-only <SearchMarker path="/settings/sounds" :label="i18n.ts.sounds" :keywords="['sounds']" icon="ti ti-music"> <div class="_gaps_m"> <SearchMarker :keywords="['mute']"> - <MkSwitch v-model="notUseSound"> - <template #label><SearchLabel>{{ i18n.ts.notUseSound }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="sound.notUseSound"> + <MkSwitch v-model="notUseSound"> + <template #label><SearchLabel>{{ i18n.ts.notUseSound }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['active', 'mute']"> - <MkSwitch v-model="useSoundOnlyWhenActive"> - <template #label><SearchLabel>{{ i18n.ts.useSoundOnlyWhenActive }}</SearchLabel></template> - </MkSwitch> + <MkPreferenceContainer k="sound.useSoundOnlyWhenActive"> + <MkSwitch v-model="useSoundOnlyWhenActive"> + <template #label><SearchLabel>{{ i18n.ts.useSoundOnlyWhenActive }}</SearchLabel></template> + </MkSwitch> + </MkPreferenceContainer> </SearchMarker> <SearchMarker :keywords="['volume', 'master']"> - <MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`"> - <template #label><SearchLabel>{{ i18n.ts.masterVolume }}</SearchLabel></template> - </MkRange> + <MkPreferenceContainer k="sound.masterVolume"> + <MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`"> + <template #label><SearchLabel>{{ i18n.ts.masterVolume }}</SearchLabel></template> + </MkRange> + </MkPreferenceContainer> </SearchMarker> <FormSection> @@ -52,7 +58,8 @@ import { computed, ref } from 'vue'; import XSound from './sounds.sound.vue'; import type { Ref } from 'vue'; import type { SoundType, OperationType } from '@/scripts/sound.js'; -import type { SoundStore } from '@/store.js'; +import type { SoundStore } from '@/preferences/def.js'; +import { prefer } from '@/preferences.js'; import MkRange from '@/components/MkRange.vue'; import MkButton from '@/components/MkButton.vue'; import FormSection from '@/components/form/section.vue'; @@ -60,18 +67,19 @@ import MkFolder from '@/components/MkFolder.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { operationTypes } from '@/scripts/sound.js'; -import { defaultStore } from '@/store.js'; import MkSwitch from '@/components/MkSwitch.vue'; +import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; +import { PREF_DEF } from '@/preferences/def.js'; -const notUseSound = computed(defaultStore.makeGetterSetter('sound_notUseSound')); -const useSoundOnlyWhenActive = computed(defaultStore.makeGetterSetter('sound_useSoundOnlyWhenActive')); -const masterVolume = computed(defaultStore.makeGetterSetter('sound_masterVolume')); +const notUseSound = prefer.model('sound.notUseSound'); +const useSoundOnlyWhenActive = prefer.model('sound.useSoundOnlyWhenActive'); +const masterVolume = prefer.model('sound.masterVolume'); const sounds = ref<Record<OperationType, Ref<SoundStore>>>({ - note: defaultStore.reactiveState.sound_note, - noteMy: defaultStore.reactiveState.sound_noteMy, - notification: defaultStore.reactiveState.sound_notification, - reaction: defaultStore.reactiveState.sound_reaction, + note: prefer.r['sound.on.note'], + noteMy: prefer.r['sound.on.noteMy'], + notification: prefer.r['sound.on.notification'], + reaction: prefer.r['sound.on.reaction'], }); function getSoundTypeName(f: SoundType): string { @@ -93,14 +101,14 @@ async function updated(type: keyof typeof sounds.value, sound) { volume: sound.volume, }; - defaultStore.set(`sound_${type}`, v); + prefer.set(`sound.on.${type}`, v); sounds.value[type] = v; } function reset() { for (const sound of Object.keys(sounds.value) as Array<keyof typeof sounds.value>) { - const v = defaultStore.def[`sound_${sound}`].default; - defaultStore.set(`sound_${sound}`, v); + const v = PREF_DEF[`sound.on.${sound}`].default; + prefer.set(`sound.on.${sound}`, v); sounds.value[sound] = v; } } diff --git a/packages/frontend/src/pages/settings/statusbar.statusbar.vue b/packages/frontend/src/pages/settings/statusbar.statusbar.vue index 140b6beb14..ede395e51e 100644 --- a/packages/frontend/src/pages/settings/statusbar.statusbar.vue +++ b/packages/frontend/src/pages/settings/statusbar.statusbar.vue @@ -94,17 +94,17 @@ import MkSwitch from '@/components/MkSwitch.vue'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; import MkRange from '@/components/MkRange.vue'; -import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { deepClone } from '@/scripts/clone.js'; +import { prefer } from '@/preferences.js'; const props = defineProps<{ _id: string; userLists: Misskey.entities.UserList[] | null; }>(); -const statusbar = reactive(deepClone(defaultStore.state.statusbars.find(x => x.id === props._id))); +const statusbar = reactive(deepClone(prefer.s.statusbars.find(x => x.id === props._id))); watch(() => statusbar.type, () => { if (statusbar.type === 'rss') { @@ -134,13 +134,13 @@ watch(() => statusbar.type, () => { watch(statusbar, save); async function save() { - const i = defaultStore.state.statusbars.findIndex(x => x.id === props._id); - const statusbars = deepClone(defaultStore.state.statusbars); + const i = prefer.s.statusbars.findIndex(x => x.id === props._id); + const statusbars = deepClone(prefer.s.statusbars); statusbars[i] = deepClone(statusbar); - defaultStore.set('statusbars', statusbars); + prefer.set('statusbars', statusbars); } function del() { - defaultStore.set('statusbars', defaultStore.state.statusbars.filter(x => x.id !== props._id)); + prefer.set('statusbars', prefer.s.statusbars.filter(x => x.id !== props._id)); } </script> diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue index 1ae3de7994..068d28bc4e 100644 --- a/packages/frontend/src/pages/settings/statusbar.vue +++ b/packages/frontend/src/pages/settings/statusbar.vue @@ -22,11 +22,11 @@ import XStatusbar from './statusbar.statusbar.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkButton from '@/components/MkButton.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { prefer } from '@/preferences.js'; -const statusbars = defaultStore.reactiveState.statusbars; +const statusbars = prefer.r.statusbars; const userLists = ref<Misskey.entities.UserList[] | null>(null); @@ -37,13 +37,13 @@ onMounted(() => { }); async function add() { - defaultStore.push('statusbars', { + prefer.set('statusbars', [...statusbars.value, { id: uuid(), type: null, black: false, size: 'medium', props: {}, - }); + }]); } const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/settings/theme.install.vue b/packages/frontend/src/pages/settings/theme.install.vue index 4f05d3784c..b19b12aaab 100644 --- a/packages/frontend/src/pages/settings/theme.install.vue +++ b/packages/frontend/src/pages/settings/theme.install.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, computed } from 'vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue'; import MkButton from '@/components/MkButton.vue'; -import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js'; +import { parseThemeCode, previewTheme, installTheme } from '@/scripts/theme.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index b0e4ce13d5..41de2aa6a6 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -75,6 +75,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, onActivated, ref, watch } from 'vue'; import JSON5 from 'json5'; +import defaultLightTheme from '@@/themes/l-light.json5'; +import defaultDarkTheme from '@@/themes/d-green-lime.json5'; import type { MkSelectItem } from '@/components/MkSelect.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; @@ -84,15 +86,15 @@ import MkButton from '@/components/MkButton.vue'; import { getBuiltinThemesRef } from '@/scripts/theme.js'; import { selectFile } from '@/scripts/select-file.js'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js'; -import { ColdDeviceStorage, defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { uniqueBy } from '@/scripts/array.js'; -import { fetchThemes, getThemes } from '@/theme-store.js'; +import { getThemes } from '@/theme-store.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { miLocalStorage } from '@/local-storage.js'; import { reloadAsk } from '@/scripts/reload-ask.js'; -import * as os from '@/os.js'; +import { prefer } from '@/preferences.js'; const installedThemes = ref(getThemes()); const builtinThemes = getBuiltinThemesRef(); @@ -169,39 +171,39 @@ const darkThemeSelectorItems = computed(() => { return items; }); -const darkTheme = ColdDeviceStorage.ref('darkTheme'); +const darkTheme = prefer.r.darkTheme; const darkThemeId = computed({ get() { - return darkTheme.value.id; + return darkTheme.value ? darkTheme.value.id : defaultDarkTheme.id; }, set(id) { const t = themes.value.find(x => x.id === id); if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる - ColdDeviceStorage.set('darkTheme', t); + prefer.set('darkTheme', t); } }, }); -const lightTheme = ColdDeviceStorage.ref('lightTheme'); +const lightTheme = prefer.r.lightTheme; const lightThemeId = computed({ get() { - return lightTheme.value.id; + return lightTheme.value ? lightTheme.value.id : defaultLightTheme.id; }, set(id) { const t = themes.value.find(x => x.id === id); if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる - ColdDeviceStorage.set('lightTheme', t); + prefer.set('lightTheme', t); } }, }); -const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); -const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); +const darkMode = computed(store.makeGetterSetter('darkMode')); +const syncDeviceDarkMode = prefer.model('syncDeviceDarkMode'); const wallpaper = ref(miLocalStorage.getItem('wallpaper')); const themesCount = installedThemes.value.length; watch(syncDeviceDarkMode, () => { if (syncDeviceDarkMode.value) { - defaultStore.set('darkMode', isDeviceDarkmode()); + store.set('darkMode', isDeviceDarkmode()); } }); @@ -215,12 +217,6 @@ watch(wallpaper, async () => { }); onActivated(() => { - fetchThemes().then(() => { - installedThemes.value = getThemes(); - }); -}); - -fetchThemes().then(() => { installedThemes.value = getThemes(); }); diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index b669e25179..5fc921c55c 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -26,7 +26,7 @@ import MkButton from '@/components/MkButton.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import * as os from '@/os.js'; import { genEmbedCode } from '@/scripts/get-embed-code.js'; @@ -44,11 +44,11 @@ const pagination = { const notes = ref<InstanceType<typeof MkNotes>>(); async function post() { - defaultStore.set('postFormHashtags', props.tag); - defaultStore.set('postFormWithHashtags', true); + store.set('postFormHashtags', props.tag); + store.set('postFormWithHashtags', true); await os.post(); - defaultStore.set('postFormHashtags', ''); - defaultStore.set('postFormWithHashtags', false); + store.set('postFormHashtags', ''); + store.set('postFormWithHashtags', false); notes.value?.pagingComponent?.reload(); } diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue index 76567cc403..c701030f9e 100644 --- a/packages/frontend/src/pages/theme-editor.vue +++ b/packages/frontend/src/pages/theme-editor.vue @@ -78,24 +78,23 @@ import { toUnicode } from 'punycode.js'; import tinycolor from 'tinycolor2'; import { v4 as uuid } from 'uuid'; import JSON5 from 'json5'; - import lightTheme from '@@/themes/_light.json5'; import darkTheme from '@@/themes/_dark.json5'; +import { host } from '@@/js/config.js'; +import type { Theme } from '@/scripts/theme.js'; import MkButton from '@/components/MkButton.vue'; import MkCodeEditor from '@/components/MkCodeEditor.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkFolder from '@/components/MkFolder.vue'; - import { $i } from '@/account.js'; import { applyTheme } from '@/scripts/theme.js'; -import type { Theme } from '@/scripts/theme.js'; -import { host } from '@@/js/config.js'; import * as os from '@/os.js'; -import { ColdDeviceStorage, defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { addTheme } from '@/theme-store.js'; import { i18n } from '@/i18n.js'; import { useLeaveGuard } from '@/scripts/use-leave-guard.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import { prefer } from '@/preferences.js'; const bgColors = [ { color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' }, @@ -201,10 +200,10 @@ async function saveAs() { if (description.value) theme.value.desc = description.value; await addTheme(theme.value); applyTheme(theme.value); - if (defaultStore.state.darkMode) { - ColdDeviceStorage.set('darkTheme', theme.value); + if (store.state.darkMode) { + prefer.set('darkTheme', theme.value); } else { - ColdDeviceStorage.set('lightTheme', theme.value); + prefer.set('lightTheme', theme.value); } changed.value = false; os.alert({ diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 044a1908ab..4c15194672 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -9,10 +9,10 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSpacer :contentMax="800"> <MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"> <div :key="src" ref="rootEl"> - <MkInfo v-if="isBasicTimeline(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()"> + <MkInfo v-if="isBasicTimeline(src) && !store.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()"> {{ i18n.ts._timelineDescription[src] }} </MkInfo> - <MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/> + <MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/> <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div :class="$style.tl"> <MkTimeline @@ -36,25 +36,26 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue'; +import { scroll } from '@@/js/scroll.js'; import type { Tab } from '@/components/global/MkPageHeader.tabs.vue'; +import type { MenuItem } from '@/types/menu.js'; +import type { BasicTimelineType } from '@/timelines.js'; import MkTimeline from '@/components/MkTimeline.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkPostForm from '@/components/MkPostForm.vue'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; -import { scroll } from '@@/js/scroll.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js'; import { deviceKind } from '@/scripts/device-kind.js'; import { deepMerge } from '@/scripts/merge.js'; -import type { MenuItem } from '@/types/menu.js'; import { miLocalStorage } from '@/local-storage.js'; import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js'; -import type { BasicTimelineType } from '@/timelines.js'; +import { prefer } from '@/preferences.js'; provide('shouldOmitHeaderTitle', true); @@ -66,18 +67,18 @@ type TimelinePageSrc = BasicTimelineType | `list:${string}`; const queue = ref(0); const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global'); const src = computed<TimelinePageSrc>({ - get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value), + get: () => ($i ? store.reactiveState.tl.value.src : srcWhenNotSignin.value), set: (x) => saveSrc(x), }); const withRenotes = computed<boolean>({ - get: () => defaultStore.reactiveState.tl.value.filter.withRenotes, + get: () => store.reactiveState.tl.value.filter.withRenotes, set: (x) => saveTlFilter('withRenotes', x), }); // computed内での無限ループを防ぐためのフラグ const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>( - defaultStore.reactiveState.tl.value.filter.withReplies ? 'withReplies' : - defaultStore.reactiveState.tl.value.filter.onlyFiles ? 'onlyFiles' : + store.reactiveState.tl.value.filter.withReplies ? 'withReplies' : + store.reactiveState.tl.value.filter.onlyFiles ? 'onlyFiles' : false, ); @@ -87,7 +88,7 @@ const withReplies = computed<boolean>({ if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'onlyFiles') { return false; } else { - return defaultStore.reactiveState.tl.value.filter.withReplies; + return store.reactiveState.tl.value.filter.withReplies; } }, set: (x) => saveTlFilter('withReplies', x), @@ -97,7 +98,7 @@ const onlyFiles = computed<boolean>({ if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'withReplies') { return false; } else { - return defaultStore.reactiveState.tl.value.filter.onlyFiles; + return store.reactiveState.tl.value.filter.onlyFiles; } }, set: (x) => saveTlFilter('onlyFiles', x), @@ -114,7 +115,7 @@ watch([withReplies, onlyFiles], ([withRepliesTo, onlyFilesTo]) => { }); const withSensitive = computed<boolean>({ - get: () => defaultStore.reactiveState.tl.value.filter.withSensitive, + get: () => store.reactiveState.tl.value.filter.withSensitive, set: (x) => saveTlFilter('withSensitive', x), }); @@ -195,23 +196,23 @@ async function chooseChannel(ev: MouseEvent): Promise<void> { } function saveSrc(newSrc: TimelinePageSrc): void { - const out = deepMerge({ src: newSrc }, defaultStore.state.tl); + const out = deepMerge({ src: newSrc }, store.state.tl); if (newSrc.startsWith('userList:')) { const id = newSrc.substring('userList:'.length); - out.userList = defaultStore.reactiveState.pinnedUserLists.value.find(l => l.id === id) ?? null; + out.userList = prefer.r.pinnedUserLists.value.find(l => l.id === id) ?? null; } - defaultStore.set('tl', out); + store.set('tl', out); if (['local', 'global'].includes(newSrc)) { srcWhenNotSignin.value = newSrc as 'local' | 'global'; } } -function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) { +function saveTlFilter(key: keyof typeof store.state.tl.filter, newValue: boolean) { if (key !== 'withReplies' || $i) { - const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl); - defaultStore.set('tl', out); + const out = deepMerge({ filter: { [key]: newValue } }, store.state.tl); + store.set('tl', out); } } @@ -230,9 +231,9 @@ function focus(): void { function closeTutorial(): void { if (!isBasicTimeline(src.value)) return; - const before = defaultStore.state.timelineTutorials; + const before = store.state.timelineTutorials; before[src.value] = true; - defaultStore.set('timelineTutorials', before); + store.set('timelineTutorials', before); } function switchTlIfNeeded() { @@ -298,7 +299,7 @@ const headerActions = computed(() => { return tmp; }); -const headerTabs = computed(() => [...(defaultStore.reactiveState.pinnedUserLists.value.map(l => ({ +const headerTabs = computed(() => [...(prefer.r.pinnedUserLists.value.map(l => ({ key: 'list:' + l.id, title: l.name, icon: 'ti ti-star', diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue index 7b74ea67ca..64d20f7945 100644 --- a/packages/frontend/src/pages/user/activity.following.vue +++ b/packages/frontend/src/pages/user/activity.following.vue @@ -20,7 +20,7 @@ import type { ChartDataset } from 'chart.js'; import * as Misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; import { initChart } from '@/scripts/init-chart.js'; @@ -64,7 +64,7 @@ async function renderChart() { const raw = await misskeyApi('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' }); - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const colorFollowLocal = '#008FFB'; const colorFollowRemote = '#008FFB88'; diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue index 8c7484ae08..cb171928eb 100644 --- a/packages/frontend/src/pages/user/activity.notes.vue +++ b/packages/frontend/src/pages/user/activity.notes.vue @@ -20,7 +20,7 @@ import type { ChartDataset } from 'chart.js'; import * as Misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; import { initChart } from '@/scripts/init-chart.js'; @@ -64,7 +64,7 @@ async function renderChart() { const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' }); - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const colorNormal = '#008FFB'; const colorReply = '#FEB019'; diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue index a073626cbb..2804211f95 100644 --- a/packages/frontend/src/pages/user/activity.pv.vue +++ b/packages/frontend/src/pages/user/activity.pv.vue @@ -20,7 +20,7 @@ import type { ChartDataset } from 'chart.js'; import * as Misskey from 'misskey-js'; import gradient from 'chartjs-plugin-gradient'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { defaultStore } from '@/store.js'; +import { store } from '@/store.js'; import { useChartTooltip } from '@/scripts/use-chart-tooltip.js'; import { chartVLine } from '@/scripts/chart-vline.js'; import { initChart } from '@/scripts/init-chart.js'; @@ -64,7 +64,7 @@ async function renderChart() { const raw = await misskeyApi('charts/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' }); - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const colorUser = '#3498db'; const colorVisitor = '#2ecc71'; diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 8ebcf975b7..932d944dde 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -176,7 +176,6 @@ import number from '@/filters/number.js'; import { userPage } from '@/filters/user.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { defaultStore } from '@/store.js'; import { $i, iAmModerator } from '@/account.js'; import { dateString } from '@/filters/date.js'; import { confetti } from '@/scripts/confetti.js'; @@ -185,6 +184,7 @@ import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFf import { useRouter } from '@/router/supplier.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import MkSparkle from '@/components/MkSparkle.vue'; +import { prefer } from '@/preferences.js'; function calcAge(birthdate: string): number { const date = new Date(birthdate); @@ -236,7 +236,7 @@ watch(moderationNote, async () => { const style = computed(() => { if (props.user.bannerUrl == null) return {}; - if (defaultStore.state.disableShowingAnimatedImages) { + if (prefer.s.disableShowingAnimatedImages) { return { backgroundImage: `url(${ getStaticImageUrl(props.user.bannerUrl) })`, }; |