From 3129fcf164369ecd1ed7fd26e17e0806ada4e435 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:17:54 +0900 Subject: fix(frontend): fix type errors --- packages/frontend/src/preferences.ts | 4 +- packages/frontend/src/preferences/def.ts | 9 +- packages/frontend/src/preferences/manager.ts | 450 +++++++++++++++++++++++++++ packages/frontend/src/preferences/profile.ts | 436 -------------------------- packages/frontend/src/preferences/utility.ts | 2 +- 5 files changed, 456 insertions(+), 445 deletions(-) create mode 100644 packages/frontend/src/preferences/manager.ts delete mode 100644 packages/frontend/src/preferences/profile.ts (limited to 'packages/frontend/src') diff --git a/packages/frontend/src/preferences.ts b/packages/frontend/src/preferences.ts index ab73fe2b77..7d1821b72b 100644 --- a/packages/frontend/src/preferences.ts +++ b/packages/frontend/src/preferences.ts @@ -4,10 +4,10 @@ */ import { v4 as uuid } from 'uuid'; -import type { PreferencesProfile, StorageProvider } from '@/preferences/profile.js'; +import type { PreferencesProfile, StorageProvider } from '@/preferences/manager.js'; import { cloudBackup } from '@/preferences/utility.js'; import { miLocalStorage } from '@/local-storage.js'; -import { isSameCond, ProfileManager } from '@/preferences/profile.js'; +import { isSameCond, ProfileManager } from '@/preferences/manager.js'; import { store } from '@/store.js'; import { $i } from '@/account.js'; import { misskeyApi } from '@/utility/misskey-api.js'; diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts index b75b99d6b5..47d0ab5cbc 100644 --- a/packages/frontend/src/preferences/def.ts +++ b/packages/frontend/src/preferences/def.ts @@ -9,7 +9,8 @@ import type { Theme } from '@/theme.js'; import type { SoundType } from '@/utility/sound.js'; import type { Plugin } from '@/plugin.js'; import type { DeviceKind } from '@/utility/device-kind.js'; -import type { Column, DeckProfile } from '@/deck.js'; +import type { DeckProfile } from '@/deck.js'; +import type { PreferencesDefinition } from './manager.js'; import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; /** サウンド設定 */ @@ -324,8 +325,4 @@ export const PREF_DEF = { sfxVolume: 1, }, }, -} satisfies Record; +} satisfies PreferencesDefinition; diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts new file mode 100644 index 0000000000..9866227d93 --- /dev/null +++ b/packages/frontend/src/preferences/manager.ts @@ -0,0 +1,450 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { computed, onUnmounted, ref, watch } from 'vue'; +import { v4 as uuid } from 'uuid'; +import { host, version } from '@@/js/config.js'; +import { PREF_DEF } from './def.js'; +import type { Ref, WritableComputedRef } from 'vue'; +import type { MenuItem } from '@/types/menu.js'; +import { $i } from '@/account.js'; +import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; + +// NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない + +//type DottedToNested> = { +// [K in keyof T as K extends string ? K extends `${infer A}.${infer B}` ? A : K : K]: K extends `${infer A}.${infer B}` ? DottedToNested<{ [key in B]: T[K] }> : T[K]; +//}; + +type PREF = typeof PREF_DEF; +type ValueOf = PREF[K]['default']; +type Account = string; // / + +type Cond = Partial<{ + server: string | null; // 将来のため + account: Account | null; + device: string | null; // 将来のため +}>; + +type ValueMeta = Partial<{ + sync: boolean; +}>; + +type PrefRecord = [cond: Cond, value: ValueOf, meta: ValueMeta]; + +function parseCond(cond: Cond): { + server: string | null; + account: Account | null; + device: string | null; +} { + return { + server: cond.server ?? null, + account: cond.account ?? null, + device: cond.device ?? null, + }; +} + +function makeCond(cond: Partial<{ + server: string | null; + account: Account | null; + device: string | null; +}>): Cond { + const c = {} as Cond; + if (cond.server != null) c.server = cond.server; + if (cond.account != null) c.account = cond.account; + if (cond.device != null) c.device = cond.device; + return c; +} + +export function isSameCond(a: Cond, b: Cond): boolean { + // null と undefined (キー無し) は区別したくないので == で比較 + // eslint-disable-next-line eqeqeq + return a.server == b.server && a.account == b.account && a.device == b.device; +} + +export type PreferencesProfile = { + id: string; + version: string; + type: 'main'; + modifiedAt: number; + name: string; + preferences: { + [K in keyof PREF]: PrefRecord[]; + }; +}; + +export type StorageProvider = { + save: (ctx: { profile: PreferencesProfile; }) => void; + cloudGets: (ctx: { needs: { key: K; cond: Cond; }[] }) => Promise>>>; + cloudGet: (ctx: { key: K; cond: Cond; }) => Promise<{ value: ValueOf; } | null>; + cloudSet: (ctx: { key: K; cond: Cond; value: ValueOf; }) => Promise; +}; + +export type PreferencesDefinition = Record; + +export class ProfileManager { + private storageProvider: StorageProvider; + public profile: PreferencesProfile; + + /** + * static / state の略 (static が予約語のため) + */ + public s = {} as { + [K in keyof PREF]: ValueOf; + }; + + /** + * reactive の略 + */ + public r = {} as { + [K in keyof PREF]: Ref>; + }; + + constructor(profile: PreferencesProfile, storageProvider: StorageProvider) { + this.profile = profile; + this.storageProvider = storageProvider; + + const states = this.genStates(); + + for (const key in states) { + this.s[key] = states[key]; + this.r[key] = ref(this.s[key]); + } + + this.fetchCloudValues(); + + // TODO: 定期的にクラウドの値をフェッチ + } + + private isAccountDependentKey(key: K): boolean { + return (PREF_DEF as PreferencesDefinition)[key].accountDependent === true; + } + + private rewriteRawState(key: K, value: ValueOf) { + const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除 + this.r[key].value = this.s[key] = v; + } + + public commit(key: K, value: ValueOf) { + console.log('prefer:commit', key, value); + + this.rewriteRawState(key, value); + + const record = this.getMatchedRecordOf(key); + if (parseCond(record[0]).account == null && this.isAccountDependentKey(key)) { + this.profile.preferences[key].push([makeCond({ + account: `${host}/${$i!.id}`, + }), value, {}]); + this.save(); + return; + } + + record[1] = value; + this.save(); + + if (record[2].sync) { + // awaitの必要なし + // TODO: リクエストを間引く + this.storageProvider.cloudSet({ key, cond: record[0], value: record[1] }); + } + } + + /** + * 特定のキーの、簡易的なcomputed refを作ります + * 主にvue上で設定コントロールのmodelとして使う用 + */ + public model = ValueOf>( + key: K, + getter?: (v: ValueOf) => V, + setter?: (v: V) => ValueOf, + ): WritableComputedRef { + const valueRef = ref(this.s[key]); + + const stop = watch(this.r[key], val => { + valueRef.value = val; + }); + + // NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする + onUnmounted(() => { + stop(); + }); + + // TODO: VueのcustomRef使うと良い感じになるかも + return computed({ + get: () => { + if (getter) { + return getter(valueRef.value); + } else { + return valueRef.value; + } + }, + set: (value) => { + const val = setter ? setter(value) : value; + this.commit(key, val); + valueRef.value = val; + }, + }); + } + + private genStates() { + const states = {} as { [K in keyof PREF]: ValueOf }; + for (const _key in PREF_DEF) { + const key = _key as keyof PREF; + const record = this.getMatchedRecordOf(key); + (states[key] as any) = record[1]; + } + + return states; + } + + private async fetchCloudValues() { + const needs = [] as { key: keyof PREF; cond: Cond; }[]; + for (const _key in PREF_DEF) { + const key = _key as keyof PREF; + const record = this.getMatchedRecordOf(key); + if (record[2].sync) { + needs.push({ + key, + cond: record[0], + }); + } + } + + const cloudValues = await this.storageProvider.cloudGets({ needs }); + + for (const _key in PREF_DEF) { + const key = _key as keyof PREF; + const record = this.getMatchedRecordOf(key); + if (record[2].sync && Object.hasOwn(cloudValues, key) && cloudValues[key] !== undefined) { + const cloudValue = cloudValues[key]; + if (cloudValue !== this.s[key]) { + this.rewriteRawState(key, cloudValue); + record[1] = cloudValue; + console.log('cloud fetched', key, cloudValue); + } + } + } + + this.save(); + console.log('cloud fetch completed'); + } + + public static newProfile(): PreferencesProfile { + const data = {} as PreferencesProfile['preferences']; + for (const key in PREF_DEF) { + data[key] = [[makeCond({}), PREF_DEF[key].default, {}]]; + } + return { + id: uuid(), + version: version, + type: 'main', + modifiedAt: Date.now(), + name: '', + preferences: data, + }; + } + + public static normalizeProfile(profileLike: any): PreferencesProfile { + const data = {} as PreferencesProfile['preferences']; + for (const key in PREF_DEF) { + const records = profileLike.preferences[key]; + if (records == null || records.length === 0) { + data[key] = [[makeCond({}), PREF_DEF[key].default, {}]]; + continue; + } else { + data[key] = records; + + // alpha段階ではmetaが無かったのでマイグレート + // TODO: そのうち消す + for (const record of data[key] as any[][]) { + if (record.length === 2) { + record.push({}); + } + } + } + } + + return { + ...profileLike, + preferences: data, + }; + } + + public save() { + this.profile.modifiedAt = Date.now(); + this.profile.version = version; + this.storageProvider.save({ profile: this.profile }); + } + + public getMatchedRecordOf(key: K): PrefRecord { + const records = this.profile.preferences[key]; + + if ($i == null) return records.find(([cond, v]) => parseCond(cond).account == null)!; + + const accountOverrideRecord = records.find(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`); + if (accountOverrideRecord) return accountOverrideRecord; + + const record = records.find(([cond, v]) => parseCond(cond).account == null); + return record!; + } + + public isAccountOverrided(key: K): boolean { + if ($i == null) return false; + return this.profile.preferences[key].some(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`) ?? false; + } + + public setAccountOverride(key: K) { + if ($i == null) return; + if (this.isAccountDependentKey(key)) throw new Error('already account-dependent'); + if (this.isAccountOverrided(key)) return; + + const records = this.profile.preferences[key]; + records.push([makeCond({ + account: `${host}/${$i!.id}`, + }), this.s[key], {}]); + + this.save(); + } + + public clearAccountOverride(key: K) { + if ($i == null) return; + if (this.isAccountDependentKey(key)) throw new Error('cannot clear override for this account-dependent property'); + + const records = this.profile.preferences[key]; + + const index = records.findIndex(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`); + if (index === -1) return; + + records.splice(index, 1); + + this.rewriteRawState(key, this.getMatchedRecordOf(key)[1]); + + this.save(); + } + + public isSyncEnabled(key: K): boolean { + return this.getMatchedRecordOf(key)[2].sync ?? false; + } + + public async enableSync(key: K): Promise<{ enabled: boolean; } | null> { + if (this.isSyncEnabled(key)) return Promise.resolve(null); + + const record = this.getMatchedRecordOf(key); + + const existing = await this.storageProvider.cloudGet({ key, cond: record[0] }); + if (existing != null) { + const { canceled, result } = await os.select({ + title: i18n.ts.preferenceSyncConflictTitle, + text: i18n.ts.preferenceSyncConflictText, + items: [{ + text: i18n.ts.preferenceSyncConflictChoiceServer, + value: 'remote', + }, { + text: i18n.ts.preferenceSyncConflictChoiceDevice, + value: 'local', + }, { + text: i18n.ts.preferenceSyncConflictChoiceCancel, + value: null, + }], + default: 'remote', + }); + if (canceled || result == null) return { enabled: false }; + + if (result === 'remote') { + this.commit(key, existing.value); + } else if (result === 'local') { + // nop + } + } + + record[2].sync = true; + this.save(); + + // awaitの必要性は無い + this.storageProvider.cloudSet({ key, cond: record[0], value: this.s[key] }); + + return { enabled: true }; + } + + public disableSync(key: K) { + if (!this.isSyncEnabled(key)) return; + + const record = this.getMatchedRecordOf(key); + delete record[2].sync; + this.save(); + } + + public renameProfile(name: string) { + this.profile.name = name; + this.save(); + } + + public rewriteProfile(profile: PreferencesProfile) { + this.profile = profile; + const states = this.genStates(); + for (const _key in states) { + const key = _key as keyof PREF; + this.rewriteRawState(key, states[key]); + } + + this.fetchCloudValues(); + } + + public getPerPrefMenu(key: K): MenuItem[] { + const overrideByAccount = ref(this.isAccountOverrided(key)); + watch(overrideByAccount, () => { + if (overrideByAccount.value) { + this.setAccountOverride(key); + } else { + this.clearAccountOverride(key); + } + }); + + const sync = ref(this.isSyncEnabled(key)); + watch(sync, () => { + if (sync.value) { + this.enableSync(key).then((res) => { + if (res == null) return; + if (!res.enabled) sync.value = false; + }); + } else { + this.disableSync(key); + } + }); + + return [{ + icon: 'ti ti-copy', + text: i18n.ts.copyPreferenceId, + action: () => { + copyToClipboard(key); + }, + }, { + icon: 'ti ti-refresh', + text: i18n.ts.resetToDefaultValue, + danger: true, + action: () => { + this.commit(key, PREF_DEF[key].default); + }, + }, { + type: 'divider', + }, { + type: 'switch', + icon: 'ti ti-user-cog', + text: i18n.ts.overrideByAccount, + ref: overrideByAccount, + }, { + type: 'switch', + icon: 'ti ti-cloud-cog', + text: i18n.ts.syncBetweenDevices, + ref: sync, + }]; + } +} diff --git a/packages/frontend/src/preferences/profile.ts b/packages/frontend/src/preferences/profile.ts deleted file mode 100644 index 2ac4e58d14..0000000000 --- a/packages/frontend/src/preferences/profile.ts +++ /dev/null @@ -1,436 +0,0 @@ -/* - * SPDX-FileCopyrightText: syuilo and misskey-project - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { computed, onUnmounted, ref, watch } from 'vue'; -import { v4 as uuid } from 'uuid'; -import { host, version } from '@@/js/config.js'; -import { PREF_DEF } from './def.js'; -import type { Ref, WritableComputedRef } from 'vue'; -import type { MenuItem } from '@/types/menu.js'; -import { $i } from '@/account.js'; -import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; -import { i18n } from '@/i18n.js'; -import * as os from '@/os.js'; - -// NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない - -//type DottedToNested> = { -// [K in keyof T as K extends string ? K extends `${infer A}.${infer B}` ? A : K : K]: K extends `${infer A}.${infer B}` ? DottedToNested<{ [key in B]: T[K] }> : T[K]; -//}; - -type PREF = typeof PREF_DEF; -type ValueOf = PREF[K]['default']; -type Account = string; // / - -type Cond = Partial<{ - server: string | null; // 将来のため - account: Account | null; - device: string | null; // 将来のため -}>; - -type ValueMeta = Partial<{ - sync: boolean; -}>; - -type PrefRecord = [cond: Cond, value: ValueOf, meta: ValueMeta]; - -function parseCond(cond: Cond): { - server: string | null; - account: Account | null; - device: string | null; -} { - return { - server: cond.server ?? null, - account: cond.account ?? null, - device: cond.device ?? null, - }; -} - -function makeCond(cond: Partial<{ - server: string | null; - account: Account | null; - device: string | null; -}>): Cond { - const c = {} as Cond; - if (cond.server != null) c.server = cond.server; - if (cond.account != null) c.account = cond.account; - if (cond.device != null) c.device = cond.device; - return c; -} - -export function isSameCond(a: Cond, b: Cond): boolean { - // null と undefined (キー無し) は区別したくないので == で比較 - // eslint-disable-next-line eqeqeq - return a.server == b.server && a.account == b.account && a.device == b.device; -} - -export type PreferencesProfile = { - id: string; - version: string; - type: 'main'; - modifiedAt: number; - name: string; - preferences: { - [K in keyof PREF]: PrefRecord[]; - }; -}; - -export type StorageProvider = { - save: (ctx: { profile: PreferencesProfile; }) => void; - cloudGets: (ctx: { needs: { key: K; cond: Cond; }[] }) => Promise>>>; - cloudGet: (ctx: { key: K; cond: Cond; }) => Promise<{ value: ValueOf; } | null>; - cloudSet: (ctx: { key: K; cond: Cond; value: ValueOf; }) => Promise; -}; - -export class ProfileManager { - private storageProvider: StorageProvider; - public profile: PreferencesProfile; - - /** - * static / state の略 (static が予約語のため) - */ - public s = {} as { - [K in keyof PREF]: ValueOf; - }; - - /** - * reactive の略 - */ - public r = {} as { - [K in keyof PREF]: Ref>; - }; - - constructor(profile: PreferencesProfile, storageProvider: StorageProvider) { - this.profile = profile; - this.storageProvider = storageProvider; - - const states = this.genStates(); - - for (const key in states) { - this.s[key] = states[key]; - this.r[key] = ref(this.s[key]); - } - - this.fetchCloudValues(); - - // TODO: 定期的にクラウドの値をフェッチ - } - - private rewriteRawState(key: K, value: ValueOf) { - const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除 - this.r[key].value = this.s[key] = v; - } - - public commit(key: K, value: ValueOf) { - console.log('prefer:commit', key, value); - - this.rewriteRawState(key, value); - - const record = this.getMatchedRecordOf(key); - if (parseCond(record[0]).account == null && PREF_DEF[key].accountDependent) { - this.profile.preferences[key].push([makeCond({ - account: `${host}/${$i!.id}`, - }), value, {}]); - this.save(); - return; - } - - record[1] = value; - this.save(); - - if (record[2].sync) { - // awaitの必要なし - // TODO: リクエストを間引く - this.storageProvider.cloudSet({ key, cond: record[0], value: record[1] }); - } - } - - /** - * 特定のキーの、簡易的なcomputed refを作ります - * 主にvue上で設定コントロールのmodelとして使う用 - */ - public model = ValueOf>( - key: K, - getter?: (v: ValueOf) => V, - setter?: (v: V) => ValueOf, - ): WritableComputedRef { - const valueRef = ref(this.s[key]); - - const stop = watch(this.r[key], val => { - valueRef.value = val; - }); - - // NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする - onUnmounted(() => { - stop(); - }); - - // TODO: VueのcustomRef使うと良い感じになるかも - return computed({ - get: () => { - if (getter) { - return getter(valueRef.value); - } else { - return valueRef.value; - } - }, - set: (value) => { - const val = setter ? setter(value) : value; - this.commit(key, val); - valueRef.value = val; - }, - }); - } - - private genStates() { - const states = {} as { [K in keyof PREF]: ValueOf }; - for (const key in PREF_DEF) { - const record = this.getMatchedRecordOf(key); - states[key] = record[1]; - } - - return states; - } - - private async fetchCloudValues() { - const needs = [] as { key: keyof PREF; cond: Cond; }[]; - for (const key in PREF_DEF) { - const record = this.getMatchedRecordOf(key); - if (record[2].sync) { - needs.push({ - key, - cond: record[0], - }); - } - } - - const cloudValues = await this.storageProvider.cloudGets({ needs }); - - for (const key in PREF_DEF) { - const record = this.getMatchedRecordOf(key); - if (record[2].sync && Object.hasOwn(cloudValues, key) && cloudValues[key] !== undefined) { - const cloudValue = cloudValues[key]; - if (cloudValue !== this.s[key]) { - this.rewriteRawState(key, cloudValue); - record[1] = cloudValue; - console.log('cloud fetched', key, cloudValue); - } - } - } - - this.save(); - console.log('cloud fetch completed'); - } - - public static newProfile(): PreferencesProfile { - const data = {} as PreferencesProfile['preferences']; - for (const key in PREF_DEF) { - data[key] = [[makeCond({}), PREF_DEF[key].default, {}]]; - } - return { - id: uuid(), - version: version, - type: 'main', - modifiedAt: Date.now(), - name: '', - preferences: data, - }; - } - - public static normalizeProfile(profileLike: any): PreferencesProfile { - const data = {} as PreferencesProfile['preferences']; - for (const key in PREF_DEF) { - const records = profileLike.preferences[key]; - if (records == null || records.length === 0) { - data[key] = [[makeCond({}), PREF_DEF[key].default, {}]]; - continue; - } else { - data[key] = records; - - // alpha段階ではmetaが無かったのでマイグレート - // TODO: そのうち消す - for (const record of data[key] as any[][]) { - if (record.length === 2) { - record.push({}); - } - } - } - } - - return { - ...profileLike, - preferences: data, - }; - } - - public save() { - this.profile.modifiedAt = Date.now(); - this.profile.version = version; - this.storageProvider.save({ profile: this.profile }); - } - - public getMatchedRecordOf(key: K): PrefRecord { - const records = this.profile.preferences[key]; - - if ($i == null) return records.find(([cond, v]) => parseCond(cond).account == null)!; - - const accountOverrideRecord = records.find(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`); - if (accountOverrideRecord) return accountOverrideRecord; - - const record = records.find(([cond, v]) => parseCond(cond).account == null); - return record!; - } - - public isAccountOverrided(key: K): boolean { - if ($i == null) return false; - return this.profile.preferences[key].some(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`) ?? false; - } - - public setAccountOverride(key: K) { - if ($i == null) return; - if (PREF_DEF[key].accountDependent) throw new Error('already account-dependent'); - if (this.isAccountOverrided(key)) return; - - const records = this.profile.preferences[key]; - records.push([makeCond({ - account: `${host}/${$i!.id}`, - }), this.s[key], {}]); - - this.save(); - } - - public clearAccountOverride(key: K) { - if ($i == null) return; - if (PREF_DEF[key].accountDependent) throw new Error('cannot clear override for this account-dependent property'); - - const records = this.profile.preferences[key]; - - const index = records.findIndex(([cond, v]) => parseCond(cond).account === `${host}/${$i!.id}`); - if (index === -1) return; - - records.splice(index, 1); - - this.rewriteRawState(key, this.getMatchedRecordOf(key)[1]); - - this.save(); - } - - public isSyncEnabled(key: K): boolean { - return this.getMatchedRecordOf(key)[2].sync ?? false; - } - - public async enableSync(key: K): Promise<{ enabled: boolean; } | null> { - if (this.isSyncEnabled(key)) return Promise.resolve(null); - - const record = this.getMatchedRecordOf(key); - - const existing = await this.storageProvider.cloudGet({ key, cond: record[0] }); - if (existing != null) { - const { canceled, result } = await os.select({ - title: i18n.ts.preferenceSyncConflictTitle, - text: i18n.ts.preferenceSyncConflictText, - items: [{ - text: i18n.ts.preferenceSyncConflictChoiceServer, - value: 'remote', - }, { - text: i18n.ts.preferenceSyncConflictChoiceDevice, - value: 'local', - }, { - text: i18n.ts.preferenceSyncConflictChoiceCancel, - value: null, - }], - default: 'remote', - }); - if (canceled || result == null) return { enabled: false }; - - if (result === 'remote') { - this.commit(key, existing.value); - } else if (result === 'local') { - // nop - } - } - - record[2].sync = true; - this.save(); - - // awaitの必要性は無い - this.storageProvider.cloudSet({ key, cond: record[0], value: this.s[key] }); - - return { enabled: true }; - } - - public disableSync(key: K) { - if (!this.isSyncEnabled(key)) return; - - const record = this.getMatchedRecordOf(key); - delete record[2].sync; - this.save(); - } - - public renameProfile(name: string) { - this.profile.name = name; - this.save(); - } - - public rewriteProfile(profile: PreferencesProfile) { - this.profile = profile; - const states = this.genStates(); - for (const key in states) { - this.rewriteRawState(key, states[key]); - } - - this.fetchCloudValues(); - } - - public getPerPrefMenu(key: K): MenuItem[] { - const overrideByAccount = ref(this.isAccountOverrided(key)); - watch(overrideByAccount, () => { - if (overrideByAccount.value) { - this.setAccountOverride(key); - } else { - this.clearAccountOverride(key); - } - }); - - const sync = ref(this.isSyncEnabled(key)); - watch(sync, () => { - if (sync.value) { - this.enableSync(key).then((res) => { - if (res == null) return; - if (!res.enabled) sync.value = false; - }); - } else { - this.disableSync(key); - } - }); - - return [{ - icon: 'ti ti-copy', - text: i18n.ts.copyPreferenceId, - action: () => { - copyToClipboard(key); - }, - }, { - icon: 'ti ti-refresh', - text: i18n.ts.resetToDefaultValue, - danger: true, - action: () => { - this.commit(key, PREF_DEF[key].default); - }, - }, { - type: 'divider', - }, { - type: 'switch', - icon: 'ti ti-user-cog', - text: i18n.ts.overrideByAccount, - ref: overrideByAccount, - }, { - type: 'switch', - icon: 'ti ti-cloud-cog', - text: i18n.ts.syncBetweenDevices, - ref: sync, - }]; - } -} diff --git a/packages/frontend/src/preferences/utility.ts b/packages/frontend/src/preferences/utility.ts index fc6eff5f49..c37dbcf96b 100644 --- a/packages/frontend/src/preferences/utility.ts +++ b/packages/frontend/src/preferences/utility.ts @@ -4,7 +4,7 @@ */ import { ref, watch } from 'vue'; -import type { PreferencesProfile } from './profile.js'; +import type { PreferencesProfile } from './manager.js'; import type { MenuItem } from '@/types/menu.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { i18n } from '@/i18n.js'; -- cgit v1.2.3-freya