From 0254570fbf94ae99f492a781e07863dc694a12cf Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 31 May 2025 12:49:10 +0900 Subject: enhance(frontend): 設定の同期をオンにするときに競合したときに値をマージできるように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/preferences/manager.ts | 52 +++++++++++++++++++++------- 1 file changed, 39 insertions(+), 13 deletions(-) (limited to 'packages/frontend/src/preferences/manager.ts') diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts index cac659f1fe..016e1ad85b 100644 --- a/packages/frontend/src/preferences/manager.ts +++ b/packages/frontend/src/preferences/manager.ts @@ -22,7 +22,10 @@ import { deepEqual } from '@/utility/deep-equal.js'; //}; type PREF = typeof PREF_DEF; -type ValueOf = PREF[K]['default']; +type DefaultValues = { + [K in keyof PREF]: PREF[K]['default'] extends (...args: any) => infer R ? R : PREF[K]['default']; +}; +type ValueOf = DefaultValues[K]; type Scope = Partial<{ server: string | null; // host @@ -84,11 +87,22 @@ export type StorageProvider = { cloudSet: (ctx: { key: K; scope: Scope; value: ValueOf; }) => Promise; }; -export type PreferencesDefinition = Record infer R ? R : Default> = { + default: Default; accountDependent?: boolean; serverDependent?: boolean; -}>; + mergeStrategy?: (a: T, b: T) => T; +}; + +export type PreferencesDefinition = Record>; + +export function getInitialPrefValue(k: K): ValueOf { + if (typeof PREF_DEF[k].default === 'function') { // factory + return PREF_DEF[k].default(); + } else { + return PREF_DEF[k].default; + } +} export class PreferencesManager { private storageProvider: StorageProvider; @@ -262,7 +276,7 @@ export class PreferencesManager { public static newProfile(): PreferencesProfile { const data = {} as PreferencesProfile['preferences']; for (const key in PREF_DEF) { - data[key] = [[makeScope({}), PREF_DEF[key].default, {}]]; + data[key] = [[makeScope({}), getInitialPrefValue(key as keyof typeof PREF_DEF), {}]]; } return { id: uuid(), @@ -279,7 +293,7 @@ export class PreferencesManager { for (const key in PREF_DEF) { const records = profileLike.preferences[key]; if (records == null || records.length === 0) { - data[key] = [[makeScope({}), PREF_DEF[key].default, {}]]; + data[key] = [[makeScope({}), getInitialPrefValue(key as keyof typeof PREF_DEF), {}]]; continue; } else { data[key] = records; @@ -367,10 +381,20 @@ export class PreferencesManager { const existing = await this.storageProvider.cloudGet({ key, scope: record[0] }); if (existing != null && !deepEqual(existing.value, record[1])) { - const { canceled, result } = await os.select({ + const merge = (PREF_DEF as PreferencesDefinition)[key].mergeStrategy; + let mergedValue: ValueOf | undefined = undefined; // null と区別したいため + try { + if (merge != null) mergedValue = merge(record[1], existing.value); + } catch (err) { + // nop + } + const { canceled, result: choice } = await os.select({ title: i18n.ts.preferenceSyncConflictTitle, text: i18n.ts.preferenceSyncConflictText, - items: [{ + items: [...(mergedValue !== undefined ? [{ + text: i18n.ts.preferenceSyncConflictChoiceMerge, + value: 'merge', + }] : []), { text: i18n.ts.preferenceSyncConflictChoiceServer, value: 'remote', }, { @@ -380,14 +404,16 @@ export class PreferencesManager { text: i18n.ts.preferenceSyncConflictChoiceCancel, value: null, }], - default: 'remote', + default: mergedValue !== undefined ? 'merge' : 'remote', }); - if (canceled || result == null) return { enabled: false }; + if (canceled || choice == null) return { enabled: false }; - if (result === 'remote') { + if (choice === 'remote') { this.commit(key, existing.value); - } else if (result === 'local') { + } else if (choice === 'local') { // nop + } else if (choice === 'merge') { + this.commit(key, mergedValue!); } } @@ -457,7 +483,7 @@ export class PreferencesManager { text: i18n.ts.resetToDefaultValue, danger: true, action: () => { - this.commit(key, PREF_DEF[key].default); + this.commit(key, getInitialPrefValue(key)); }, }, { type: 'divider', -- cgit v1.2.3-freya