enhance(frontend): make pref sync more smart
This commit is contained in:
parent
0254570fbf
commit
743995e469
|
@ -547,14 +547,28 @@ export function success(): Promise<void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function waiting(text?: string | null): () => void {
|
export function waiting(options: { text?: string } = {}) {
|
||||||
window.document.body.setAttribute('inert', 'true');
|
window.document.body.setAttribute('inert', 'true');
|
||||||
|
|
||||||
const showing = ref(true);
|
const showing = ref(true);
|
||||||
|
const isSuccess = ref(false);
|
||||||
|
|
||||||
|
function done(doneOptions: { success?: boolean } = {}) {
|
||||||
|
if (doneOptions.success) {
|
||||||
|
isSuccess.value = true;
|
||||||
|
window.setTimeout(() => {
|
||||||
|
showing.value = false;
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
showing.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: dynamic importすると挙動がおかしくなる(showingの変更が伝播しない)
|
||||||
const { dispose } = popup(MkWaitingDialog, {
|
const { dispose } = popup(MkWaitingDialog, {
|
||||||
success: false,
|
success: isSuccess,
|
||||||
showing: showing,
|
showing: showing,
|
||||||
text,
|
text: options.text,
|
||||||
}, {
|
}, {
|
||||||
closed: () => {
|
closed: () => {
|
||||||
window.document.body.removeAttribute('inert');
|
window.document.body.removeAttribute('inert');
|
||||||
|
@ -562,9 +576,7 @@ export function waiting(text?: string | null): () => void {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return done;
|
||||||
showing.value = false;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function form<F extends Form>(title: string, f: F): Promise<{ canceled: true, result?: undefined } | { canceled?: false, result: GetFormResultType<F> }> {
|
export function form<F extends Form>(title: string, f: F): Promise<{ canceled: true, result?: undefined } | { canceled?: false, result: GetFormResultType<F> }> {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
// TODO: そのうち消す
|
// TODO: そのうち消す
|
||||||
export function migrateOldSettings() {
|
export function migrateOldSettings() {
|
||||||
os.waiting(i18n.ts.settingsMigrating);
|
os.waiting({ text: i18n.ts.settingsMigrating });
|
||||||
|
|
||||||
store.loaded.then(async () => {
|
store.loaded.then(async () => {
|
||||||
misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []).then((themes: any) => {
|
misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []).then((themes: any) => {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type { DeviceKind } from '@/utility/device-kind.js';
|
||||||
import type { DeckProfile } from '@/deck.js';
|
import type { DeckProfile } from '@/deck.js';
|
||||||
import type { PreferencesDefinition } from './manager.js';
|
import type { PreferencesDefinition } from './manager.js';
|
||||||
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
|
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
|
||||||
|
import { deepEqual } from '@/utility/deep-equal.js';
|
||||||
|
|
||||||
/** サウンド設定 */
|
/** サウンド設定 */
|
||||||
export type SoundStore = {
|
export type SoundStore = {
|
||||||
|
@ -87,9 +88,20 @@ export const PREF_DEF = {
|
||||||
emojis: string[];
|
emojis: string[];
|
||||||
}[],
|
}[],
|
||||||
mergeStrategy: (a, b) => {
|
mergeStrategy: (a, b) => {
|
||||||
const sameIdExists = a.some(x => b.some(y => x.id === y.id));
|
const mergedItems = [] as (typeof a)[];
|
||||||
if (sameIdExists) throw new Error();
|
for (const x of a.concat(b)) {
|
||||||
return a.concat(b);
|
const sameIdItem = mergedItems.find(y => y.id === x.id);
|
||||||
|
if (sameIdItem != null) {
|
||||||
|
if (deepEqual(x, sameIdItem)) { // 完全な重複は無視
|
||||||
|
continue;
|
||||||
|
} else { // IDは同じなのに内容が違う場合はマージ不可とする
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mergedItems.push(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergedItems;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emojiPaletteForReaction: {
|
emojiPaletteForReaction: {
|
||||||
|
@ -107,9 +119,20 @@ export const PREF_DEF = {
|
||||||
themes: {
|
themes: {
|
||||||
default: [] as Theme[],
|
default: [] as Theme[],
|
||||||
mergeStrategy: (a, b) => {
|
mergeStrategy: (a, b) => {
|
||||||
const sameIdExists = a.some(x => b.some(y => x.id === y.id));
|
const mergedItems = [] as (typeof a)[];
|
||||||
if (sameIdExists) throw new Error();
|
for (const x of a.concat(b)) {
|
||||||
return a.concat(b);
|
const sameIdItem = mergedItems.find(y => y.id === x.id);
|
||||||
|
if (sameIdItem != null) {
|
||||||
|
if (deepEqual(x, sameIdItem)) { // 完全な重複は無視
|
||||||
|
continue;
|
||||||
|
} else { // IDは同じなのに内容が違う場合はマージ不可とする
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mergedItems.push(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergedItems;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lightTheme: {
|
lightTheme: {
|
||||||
|
|
|
@ -377,14 +377,12 @@ export class PreferencesManager {
|
||||||
public async enableSync<K extends keyof PREF>(key: K): Promise<{ enabled: boolean; } | null> {
|
public async enableSync<K extends keyof PREF>(key: K): Promise<{ enabled: boolean; } | null> {
|
||||||
if (this.isSyncEnabled(key)) return Promise.resolve(null);
|
if (this.isSyncEnabled(key)) return Promise.resolve(null);
|
||||||
|
|
||||||
const record = this.getMatchedRecordOf(key);
|
// undefined ... cancel
|
||||||
|
async function resolveConflict(local: ValueOf<K>, remote: ValueOf<K>): Promise<ValueOf<K> | undefined> {
|
||||||
const existing = await this.storageProvider.cloudGet({ key, scope: record[0] });
|
|
||||||
if (existing != null && !deepEqual(existing.value, record[1])) {
|
|
||||||
const merge = (PREF_DEF as PreferencesDefinition)[key].mergeStrategy;
|
const merge = (PREF_DEF as PreferencesDefinition)[key].mergeStrategy;
|
||||||
let mergedValue: ValueOf<K> | undefined = undefined; // null と区別したいため
|
let mergedValue: ValueOf<K> | undefined = undefined; // null と区別したいため
|
||||||
try {
|
try {
|
||||||
if (merge != null) mergedValue = merge(record[1], existing.value);
|
if (merge != null) mergedValue = merge(local, remote);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// nop
|
// nop
|
||||||
}
|
}
|
||||||
|
@ -406,23 +404,51 @@ export class PreferencesManager {
|
||||||
}],
|
}],
|
||||||
default: mergedValue !== undefined ? 'merge' : 'remote',
|
default: mergedValue !== undefined ? 'merge' : 'remote',
|
||||||
});
|
});
|
||||||
if (canceled || choice == null) return { enabled: false };
|
if (canceled || choice == null) return undefined;
|
||||||
|
|
||||||
if (choice === 'remote') {
|
if (choice === 'remote') {
|
||||||
this.commit(key, existing.value);
|
return remote;
|
||||||
} else if (choice === 'local') {
|
} else if (choice === 'local') {
|
||||||
// nop
|
return local;
|
||||||
} else if (choice === 'merge') {
|
} else if (choice === 'merge') {
|
||||||
this.commit(key, mergedValue!);
|
return mergedValue!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const record = this.getMatchedRecordOf(key);
|
||||||
|
|
||||||
|
let newValue = record[1];
|
||||||
|
|
||||||
|
const existing = await this.storageProvider.cloudGet({ key, scope: record[0] });
|
||||||
|
if (existing != null && !deepEqual(record[1], existing.value)) {
|
||||||
|
const resolvedValue = await resolveConflict(record[1], existing.value);
|
||||||
|
if (resolvedValue === undefined) return { enabled: false }; // canceled
|
||||||
|
newValue = resolvedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commit(key, newValue);
|
||||||
|
|
||||||
|
const done = os.waiting();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.storageProvider.cloudSet({ key, scope: record[0], value: newValue });
|
||||||
|
} catch (err) {
|
||||||
|
done();
|
||||||
|
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.somethingHappened,
|
||||||
|
text: err,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { enabled: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
done({ success: true });
|
||||||
|
|
||||||
record[2].sync = true;
|
record[2].sync = true;
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
// awaitの必要性は無い
|
|
||||||
this.storageProvider.cloudSet({ key, scope: record[0], value: this.s[key] });
|
|
||||||
|
|
||||||
return { enabled: true };
|
return { enabled: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue