integrate localStorage options into preference system

This commit is contained in:
Hazelnoot 2025-06-07 20:24:44 -04:00
parent 16858cf2f7
commit 4085c8a4f1
5 changed files with 118 additions and 100 deletions

View file

@ -23,24 +23,8 @@ import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { reloadAsk } from '@/utility/reload-ask';
const customCssModel = prefer.model('customCss'); const localCustomCss = prefer.model('customCss');
const localCustomCss = computed<string>({
get() {
return customCssModel.value ?? miLocalStorage.getItem('customCss') ?? '';
},
set(newCustomCss) {
customCssModel.value = newCustomCss;
if (newCustomCss) {
miLocalStorage.setItem('customCss', newCustomCss);
} else {
miLocalStorage.removeItem('customCss');
}
reloadAsk(true);
},
});
const headerActions = computed(() => []); const headerActions = computed(() => []);

View file

@ -1059,68 +1059,13 @@ const clickToOpen = prefer.model('clickToOpen');
const useCustomSearchEngine = computed(() => !Object.keys(searchEngineMap).includes(searchEngine.value)); const useCustomSearchEngine = computed(() => !Object.keys(searchEngineMap).includes(searchEngine.value));
const defaultCW = ref($i.defaultCW); const defaultCW = ref($i.defaultCW);
const defaultCWPriority = ref($i.defaultCWPriority); const defaultCWPriority = ref($i.defaultCWPriority);
const lang = prefer.model('lang');
const langModel = prefer.model('lang'); const fontSize = prefer.model('fontSize');
const lang = computed<string>({ const useSystemFont = prefer.model('useSystemFont');
get() { const cornerRadius = prefer.model('cornerRadius');
return langModel.value ?? miLocalStorage.getItem('lang') ?? 'en-US';
},
set(newLang) {
langModel.value = newLang;
miLocalStorage.setItem('lang', newLang);
miLocalStorage.removeItem('locale');
miLocalStorage.removeItem('localeVersion');
},
});
const fontSizeModel = prefer.model('fontSize');
const fontSize = computed<'0' | '1' | '2' | '3'>({
get() {
return fontSizeModel.value ?? miLocalStorage.getItem('fontSize') ?? '0';
},
set(newFontSize) {
fontSizeModel.value = newFontSize;
if (newFontSize !== '0') {
miLocalStorage.setItem('fontSize', newFontSize);
} else {
miLocalStorage.removeItem('fontSize');
}
},
});
const useSystemFontModel = prefer.model('useSystemFont');
const useSystemFont = computed<boolean>({
get() {
return useSystemFontModel.value ?? (miLocalStorage.getItem('useSystemFont') != null);
},
set(newUseSystemFont) {
useSystemFontModel.value = newUseSystemFont;
if (newUseSystemFont) {
miLocalStorage.setItem('useSystemFont', 't');
} else {
miLocalStorage.removeItem('useSystemFont');
}
},
});
const cornerRadiusModel = prefer.model('cornerRadius');
const cornerRadius = computed<'misskey' | 'sharkey'>({
get() {
return cornerRadiusModel.value ?? miLocalStorage.getItem('cornerRadius') ?? 'sharkey';
},
set(newCornerRadius) {
cornerRadiusModel.value = newCornerRadius;
if (newCornerRadius === 'sharkey') {
miLocalStorage.removeItem('cornerRadius');
} else {
miLocalStorage.setItem('cornerRadius', newCornerRadius);
}
},
});
watch([ watch([
hemisphere, hemisphere,
lang,
enableInfiniteScroll, enableInfiniteScroll,
showNoteActionsOnlyHover, showNoteActionsOnlyHover,
overridedDeviceKind, overridedDeviceKind,
@ -1142,9 +1087,6 @@ watch([
useStickyIcons, useStickyIcons,
keepScreenOn, keepScreenOn,
contextMenu, contextMenu,
fontSize,
useSystemFont,
cornerRadius,
makeEveryTextElementsSelectable, makeEveryTextElementsSelectable,
noteDesign, noteDesign,
], async () => { ], async () => {

View file

@ -13,6 +13,7 @@ import { deckStore } from '@/ui/deck/deck-store.js';
import { unisonReload } from '@/utility/unison-reload.js'; import { unisonReload } from '@/utility/unison-reload.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage';
// TODO: そのうち消す // TODO: そのうち消す
export function migrateOldSettings() { export function migrateOldSettings() {
@ -167,5 +168,15 @@ export function migrateOldSettings() {
window.setTimeout(() => { window.setTimeout(() => {
unisonReload(); unisonReload();
}, 10000); }, 10000);
//#region Hybrid migrations
prefer.commit('fontSize', miLocalStorage.getItem('fontSize') ?? '0');
prefer.commit('useSystemFont', miLocalStorage.getItem('useSystemFont') != null);
prefer.commit('cornerRadius', miLocalStorage.getItem('cornerRadius') ?? 'sharkey');
prefer.commit('lang', miLocalStorage.getItem('lang') ?? 'en-US');
prefer.commit('customCss', miLocalStorage.getItem('customCss') ?? '');
prefer.commit('neverShowDonationInfo', miLocalStorage.getItem('neverShowDonationInfo') != null);
prefer.commit('neverShowLocalOnlyInfo', miLocalStorage.getItem('neverShowLocalOnlyInfo') != null);
//#endregion
}); });
} }

View file

@ -10,11 +10,12 @@ import type { SoundType } from '@/utility/sound.js';
import type { Plugin } from '@/plugin.js'; import type { Plugin } from '@/plugin.js';
import type { DeviceKind } from '@/utility/device-kind.js'; 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 { Pref, PreferencesDefinition } from './manager.js';
import type { FollowingFeedState } from '@/types/following-feed.js'; import type { FollowingFeedState } from '@/types/following-feed.js';
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
import { searchEngineMap } from '@/utility/search-engine-map.js'; import { searchEngineMap } from '@/utility/search-engine-map.js';
import { defaultFollowingFeedState } from '@/types/following-feed.js'; import { defaultFollowingFeedState } from '@/types/following-feed.js';
import { miLocalStorage } from '@/local-storage';
/** サウンド設定 */ /** サウンド設定 */
export type SoundStore = { export type SoundStore = {
@ -484,25 +485,77 @@ export const PREF_DEF = {
// Null means "fall back to existing value from localStorage" // Null means "fall back to existing value from localStorage"
// For all of these preferences, "null" means fall back to existing value in localStorage. // For all of these preferences, "null" means fall back to existing value in localStorage.
fontSize: { fontSize: {
default: null as null | '0' | '1' | '2' | '3', default: '0',
}, needsReload: true,
onSet: fontSize => {
if (fontSize !== '0') {
miLocalStorage.setItem('fontSize', fontSize);
} else {
miLocalStorage.removeItem('fontSize');
}
},
} as Pref<'0' | '1' | '2' | '3'>,
useSystemFont: { useSystemFont: {
default: null as null | boolean, default: false,
}, needsReload: true,
onSet: useSystemFont => {
if (useSystemFont) {
miLocalStorage.setItem('useSystemFont', 't');
} else {
miLocalStorage.removeItem('useSystemFont');
}
},
} as Pref<boolean>,
cornerRadius: { cornerRadius: {
default: null as null | 'misskey' | 'sharkey', default: 'sharkey',
}, needsReload: true,
onSet: cornerRadius => {
if (cornerRadius === 'sharkey') {
miLocalStorage.removeItem('cornerRadius');
} else {
miLocalStorage.setItem('cornerRadius', cornerRadius);
}
},
} as Pref<'misskey' | 'sharkey'>,
lang: { lang: {
default: null as null | string, default: 'en-US',
}, needsReload: true,
onSet: lang => {
miLocalStorage.setItem('lang', lang);
miLocalStorage.removeItem('locale');
miLocalStorage.removeItem('localeVersion');
},
} as Pref<string>,
customCss: { customCss: {
default: null as null | string, default: '',
}, needsReload: true,
onSet: customCss => {
if (customCss) {
miLocalStorage.setItem('customCss', customCss);
} else {
miLocalStorage.removeItem('customCss');
}
},
} as Pref<string>,
neverShowDonationInfo: { neverShowDonationInfo: {
default: null as null | 'true', default: false,
}, onSet: neverShowDonationInfo => {
if (neverShowDonationInfo) {
miLocalStorage.setItem('neverShowDonationInfo', 'true');
} else {
miLocalStorage.removeItem('neverShowDonationInfo');
}
},
} as Pref<boolean>,
neverShowLocalOnlyInfo: { neverShowLocalOnlyInfo: {
default: null as null | 'true', default: false,
}, onSet: neverShowLocalOnlyInfo => {
if (neverShowLocalOnlyInfo) {
miLocalStorage.setItem('neverShowLocalOnlyInfo', 'true');
} else {
miLocalStorage.removeItem('neverShowLocalOnlyInfo');
}
},
} as Pref<boolean>,
//#endregion //#endregion
} satisfies PreferencesDefinition; } satisfies PreferencesDefinition;

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { computed, onUnmounted, ref, watch } from 'vue'; import { computed, nextTick, onUnmounted, ref, watch } from 'vue';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { host, version } from '@@/js/config.js'; import { host, version } from '@@/js/config.js';
import { PREF_DEF } from './def.js'; import { PREF_DEF } from './def.js';
@ -14,6 +14,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { deepEqual } from '@/utility/deep-equal.js'; import { deepEqual } from '@/utility/deep-equal.js';
import { reloadAsk } from '@/utility/reload-ask';
// NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない // NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない
@ -84,16 +85,29 @@ export type StorageProvider = {
cloudSet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; value: ValueOf<K>; }) => Promise<void>; cloudSet: <K extends keyof PREF>(ctx: { key: K; scope: Scope; value: ValueOf<K>; }) => Promise<void>;
}; };
export type PreferencesDefinition = Record<string, { export type Pref<T> = {
default: any; default: T;
accountDependent?: boolean; accountDependent?: boolean;
serverDependent?: boolean; serverDependent?: boolean;
}>; needsReload?: boolean;
onSet?: (value: T) => void;
};
export type PreferencesDefinition = Record<string, Pref<any> | undefined>;
export class PreferencesManager { export class PreferencesManager {
private storageProvider: StorageProvider; private storageProvider: StorageProvider;
public profile: PreferencesProfile; public profile: PreferencesProfile;
public cloudReady: Promise<void>; public cloudReady: Promise<void>;
private enableReload = true;
public suppressReload() {
this.enableReload = false;
}
public allowReload() {
this.enableReload = true;
}
/** /**
* static / state (static ) * static / state (static )
@ -126,11 +140,11 @@ export class PreferencesManager {
} }
private isAccountDependentKey<K extends keyof PREF>(key: K): boolean { private isAccountDependentKey<K extends keyof PREF>(key: K): boolean {
return (PREF_DEF as PreferencesDefinition)[key].accountDependent === true; return (PREF_DEF as PreferencesDefinition)[key]?.accountDependent === true;
} }
private isServerDependentKey<K extends keyof PREF>(key: K): boolean { private isServerDependentKey<K extends keyof PREF>(key: K): boolean {
return (PREF_DEF as PreferencesDefinition)[key].serverDependent === true; return (PREF_DEF as PreferencesDefinition)[key]?.serverDependent === true;
} }
private rewriteRawState<K extends keyof PREF>(key: K, value: ValueOf<K>) { private rewriteRawState<K extends keyof PREF>(key: K, value: ValueOf<K>) {
@ -150,6 +164,20 @@ export class PreferencesManager {
this.rewriteRawState(key, v); this.rewriteRawState(key, v);
const pref = (PREF_DEF as PreferencesDefinition)[key];
if (pref) {
// Call custom setter
if (pref.onSet) {
pref.onSet(v);
}
// Prompt to reload the frontend
if (pref.needsReload && this.enableReload) {
// noinspection JSIgnoredPromiseFromCall
nextTick(() => reloadAsk({ unison: true }));
}
}
const record = this.getMatchedRecordOf(key); const record = this.getMatchedRecordOf(key);
if (parseScope(record[0]).account == null && this.isAccountDependentKey(key)) { if (parseScope(record[0]).account == null && this.isAccountDependentKey(key)) {