|
@@ -16,168 +16,167 @@ import {
|
|
|
import { defaultPreferences } from './config';
|
|
import { defaultPreferences } from './config';
|
|
|
import { updateCSSVariables } from './update-css-variables';
|
|
import { updateCSSVariables } from './update-css-variables';
|
|
|
|
|
|
|
|
-const STORAGE_KEY = 'preferences';
|
|
|
|
|
-const STORAGE_KEY_LOCALE = `${STORAGE_KEY}-locale`;
|
|
|
|
|
-const STORAGE_KEY_THEME = `${STORAGE_KEY}-theme`;
|
|
|
|
|
|
|
+const STORAGE_KEYS = {
|
|
|
|
|
+ MAIN: 'preferences',
|
|
|
|
|
+ LOCALE: 'preferences-locale',
|
|
|
|
|
+ THEME: 'preferences-theme',
|
|
|
|
|
+} as const;
|
|
|
|
|
|
|
|
class PreferenceManager {
|
|
class PreferenceManager {
|
|
|
- private cache: null | StorageManager = null;
|
|
|
|
|
- // private flattenedState: Flatten<Preferences>;
|
|
|
|
|
|
|
+ private cache: StorageManager;
|
|
|
|
|
+ private debouncedSave: (preference: Preferences) => void;
|
|
|
private initialPreferences: Preferences = defaultPreferences;
|
|
private initialPreferences: Preferences = defaultPreferences;
|
|
|
- private isInitialized: boolean = false;
|
|
|
|
|
- private savePreferences: (preference: Preferences) => void;
|
|
|
|
|
- private state: Preferences = reactive<Preferences>({
|
|
|
|
|
- ...this.loadPreferences(),
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ private isInitialized = false;
|
|
|
|
|
+ private state: Preferences;
|
|
|
|
|
+
|
|
|
constructor() {
|
|
constructor() {
|
|
|
this.cache = new StorageManager();
|
|
this.cache = new StorageManager();
|
|
|
-
|
|
|
|
|
- // 避免频繁的操作缓存
|
|
|
|
|
- this.savePreferences = useDebounceFn(
|
|
|
|
|
- (preference: Preferences) => this._savePreferences(preference),
|
|
|
|
|
|
|
+ this.state = reactive<Preferences>(
|
|
|
|
|
+ this.loadFromCache() || { ...defaultPreferences },
|
|
|
|
|
+ );
|
|
|
|
|
+ this.debouncedSave = useDebounceFn(
|
|
|
|
|
+ (preference) => this.saveToCache(preference),
|
|
|
150,
|
|
150,
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- clearCache() {
|
|
|
|
|
- [STORAGE_KEY, STORAGE_KEY_LOCALE, STORAGE_KEY_THEME].forEach((key) => {
|
|
|
|
|
- this.cache?.removeItem(key);
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 清除所有缓存的偏好设置
|
|
|
|
|
+ */
|
|
|
|
|
+ clearCache = () => {
|
|
|
|
|
+ Object.values(STORAGE_KEYS).forEach((key) => this.cache.removeItem(key));
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- public getInitialPreferences() {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取初始化偏好设置
|
|
|
|
|
+ */
|
|
|
|
|
+ getInitialPreferences = () => {
|
|
|
return this.initialPreferences;
|
|
return this.initialPreferences;
|
|
|
- }
|
|
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- public getPreferences() {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前偏好设置(只读)
|
|
|
|
|
+ */
|
|
|
|
|
+ getPreferences = () => {
|
|
|
return readonly(this.state);
|
|
return readonly(this.state);
|
|
|
- }
|
|
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 覆盖偏好设置
|
|
|
|
|
- * overrides 要覆盖的偏好设置
|
|
|
|
|
- * namespace 命名空间
|
|
|
|
|
|
|
+ * 初始化偏好设置
|
|
|
|
|
+ * @param namespace - 命名空间,用于隔离不同应用的配置
|
|
|
|
|
+ * @param overrides - 要覆盖的偏好设置
|
|
|
*/
|
|
*/
|
|
|
- public async initPreferences({ namespace, overrides }: InitialOptions) {
|
|
|
|
|
- // 是否初始化过
|
|
|
|
|
|
|
+ initPreferences = async ({ namespace, overrides }: InitialOptions) => {
|
|
|
|
|
+ // 防止重复初始化
|
|
|
if (this.isInitialized) {
|
|
if (this.isInitialized) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- // 初始化存储管理器
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 使用命名空间初始化存储管理器
|
|
|
this.cache = new StorageManager({ prefix: namespace });
|
|
this.cache = new StorageManager({ prefix: namespace });
|
|
|
|
|
+
|
|
|
// 合并初始偏好设置
|
|
// 合并初始偏好设置
|
|
|
this.initialPreferences = merge({}, overrides, defaultPreferences);
|
|
this.initialPreferences = merge({}, overrides, defaultPreferences);
|
|
|
|
|
|
|
|
- // 加载并合并当前存储的偏好设置
|
|
|
|
|
|
|
+ // 加载缓存的偏好设置并与初始配置合并
|
|
|
|
|
+ const cachedPreferences = this.loadFromCache() || {};
|
|
|
const mergedPreference = merge(
|
|
const mergedPreference = merge(
|
|
|
{},
|
|
{},
|
|
|
- // overrides,
|
|
|
|
|
- this.loadCachedPreferences() || {},
|
|
|
|
|
|
|
+ cachedPreferences,
|
|
|
this.initialPreferences,
|
|
this.initialPreferences,
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
// 更新偏好设置
|
|
// 更新偏好设置
|
|
|
this.updatePreferences(mergedPreference);
|
|
this.updatePreferences(mergedPreference);
|
|
|
|
|
|
|
|
|
|
+ // 设置监听器
|
|
|
this.setupWatcher();
|
|
this.setupWatcher();
|
|
|
|
|
|
|
|
|
|
+ // 初始化平台标识
|
|
|
this.initPlatform();
|
|
this.initPlatform();
|
|
|
- // 标记为已初始化
|
|
|
|
|
|
|
+
|
|
|
this.isInitialized = true;
|
|
this.isInitialized = true;
|
|
|
- }
|
|
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 重置偏好设置
|
|
|
|
|
- * 偏好设置将被重置为初始值,并从 localStorage 中移除。
|
|
|
|
|
- *
|
|
|
|
|
- * @example
|
|
|
|
|
- * 假设 initialPreferences 为 { theme: 'light', language: 'en' }
|
|
|
|
|
- * 当前 state 为 { theme: 'dark', language: 'fr' }
|
|
|
|
|
- * this.resetPreferences();
|
|
|
|
|
- * 调用后,state 将被重置为 { theme: 'light', language: 'en' }
|
|
|
|
|
- * 并且 localStorage 中的对应项将被移除
|
|
|
|
|
|
|
+ * 重置偏好设置到初始状态
|
|
|
*/
|
|
*/
|
|
|
- resetPreferences() {
|
|
|
|
|
|
|
+ resetPreferences = () => {
|
|
|
// 将状态重置为初始偏好设置
|
|
// 将状态重置为初始偏好设置
|
|
|
Object.assign(this.state, this.initialPreferences);
|
|
Object.assign(this.state, this.initialPreferences);
|
|
|
- // 保存重置后的偏好设置
|
|
|
|
|
- this.savePreferences(this.state);
|
|
|
|
|
- // 从存储中移除偏好设置项
|
|
|
|
|
- [STORAGE_KEY, STORAGE_KEY_THEME, STORAGE_KEY_LOCALE].forEach((key) => {
|
|
|
|
|
- this.cache?.removeItem(key);
|
|
|
|
|
- });
|
|
|
|
|
- this.updatePreferences(this.state);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 保存偏好设置至缓存
|
|
|
|
|
+ this.saveToCache(this.state);
|
|
|
|
|
+
|
|
|
|
|
+ // 直接触发 UI 更新
|
|
|
|
|
+ this.handleUpdates(this.state);
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 更新偏好设置
|
|
* 更新偏好设置
|
|
|
* @param updates - 要更新的偏好设置
|
|
* @param updates - 要更新的偏好设置
|
|
|
*/
|
|
*/
|
|
|
- public updatePreferences(updates: DeepPartial<Preferences>) {
|
|
|
|
|
|
|
+ updatePreferences = (updates: DeepPartial<Preferences>) => {
|
|
|
|
|
+ // 深度合并更新内容和当前状态
|
|
|
const mergedState = merge({}, updates, markRaw(this.state));
|
|
const mergedState = merge({}, updates, markRaw(this.state));
|
|
|
-
|
|
|
|
|
Object.assign(this.state, mergedState);
|
|
Object.assign(this.state, mergedState);
|
|
|
|
|
|
|
|
- // 根据更新的键值执行相应的操作
|
|
|
|
|
|
|
+ // 根据更新的值执行更新
|
|
|
this.handleUpdates(updates);
|
|
this.handleUpdates(updates);
|
|
|
- this.savePreferences(this.state);
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 保存偏好设置
|
|
|
|
|
- * @param {Preferences} preference - 需要保存的偏好设置
|
|
|
|
|
- */
|
|
|
|
|
- private _savePreferences(preference: Preferences) {
|
|
|
|
|
- this.cache?.setItem(STORAGE_KEY, preference);
|
|
|
|
|
- this.cache?.setItem(STORAGE_KEY_LOCALE, preference.app.locale);
|
|
|
|
|
- this.cache?.setItem(STORAGE_KEY_THEME, preference.theme.mode);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 保存到缓存
|
|
|
|
|
+ this.debouncedSave(this.state);
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 处理更新的键值
|
|
|
|
|
- * 根据更新的键值执行相应的操作。
|
|
|
|
|
- * @param {DeepPartial<Preferences>} updates - 部分更新的偏好设置
|
|
|
|
|
|
|
+ * 处理更新
|
|
|
|
|
+ * @param updates - 更新的偏好设置
|
|
|
*/
|
|
*/
|
|
|
private handleUpdates(updates: DeepPartial<Preferences>) {
|
|
private handleUpdates(updates: DeepPartial<Preferences>) {
|
|
|
- const themeUpdates = updates.theme || {};
|
|
|
|
|
- const appUpdates = updates.app || {};
|
|
|
|
|
|
|
+ const { theme, app } = updates;
|
|
|
|
|
+
|
|
|
if (
|
|
if (
|
|
|
- (themeUpdates && Object.keys(themeUpdates).length > 0) ||
|
|
|
|
|
- Reflect.has(themeUpdates, 'fontSize')
|
|
|
|
|
|
|
+ theme &&
|
|
|
|
|
+ (Object.keys(theme).length > 0 || Reflect.has(theme, 'fontSize'))
|
|
|
) {
|
|
) {
|
|
|
updateCSSVariables(this.state);
|
|
updateCSSVariables(this.state);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
if (
|
|
|
- Reflect.has(appUpdates, 'colorGrayMode') ||
|
|
|
|
|
- Reflect.has(appUpdates, 'colorWeakMode')
|
|
|
|
|
|
|
+ app &&
|
|
|
|
|
+ (Reflect.has(app, 'colorGrayMode') || Reflect.has(app, 'colorWeakMode'))
|
|
|
) {
|
|
) {
|
|
|
this.updateColorMode(this.state);
|
|
this.updateColorMode(this.state);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化平台标识
|
|
|
|
|
+ */
|
|
|
private initPlatform() {
|
|
private initPlatform() {
|
|
|
- const dom = document.documentElement;
|
|
|
|
|
- dom.dataset.platform = isMacOs() ? 'macOs' : 'window';
|
|
|
|
|
|
|
+ document.documentElement.dataset.platform = isMacOs() ? 'macOs' : 'window';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 从缓存中加载偏好设置。如果缓存中没有找到对应的偏好设置,则返回默认偏好设置。
|
|
|
|
|
|
|
+ * 从缓存加载偏好设置
|
|
|
|
|
+ * @returns 缓存的偏好设置,如果不存在则返回 null
|
|
|
*/
|
|
*/
|
|
|
- private loadCachedPreferences() {
|
|
|
|
|
- return this.cache?.getItem<Preferences>(STORAGE_KEY);
|
|
|
|
|
|
|
+ private loadFromCache(): null | Preferences {
|
|
|
|
|
+ return this.cache.getItem<Preferences>(STORAGE_KEYS.MAIN);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 加载偏好设置
|
|
|
|
|
- * @returns {Preferences} 加载的偏好设置
|
|
|
|
|
|
|
+ * 保存偏好设置到缓存
|
|
|
|
|
+ * @param preference - 要保存的偏好设置
|
|
|
*/
|
|
*/
|
|
|
- private loadPreferences(): Preferences {
|
|
|
|
|
- return this.loadCachedPreferences() || { ...defaultPreferences };
|
|
|
|
|
|
|
+ private saveToCache(preference: Preferences) {
|
|
|
|
|
+ this.cache.setItem(STORAGE_KEYS.MAIN, preference);
|
|
|
|
|
+ this.cache.setItem(STORAGE_KEYS.LOCALE, preference.app.locale);
|
|
|
|
|
+ this.cache.setItem(STORAGE_KEYS.THEME, preference.theme.mode);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 监听状态和系统偏好设置的变化。
|
|
|
|
|
|
|
+ * 监听状态和系统偏好设置的变化
|
|
|
*/
|
|
*/
|
|
|
private setupWatcher() {
|
|
private setupWatcher() {
|
|
|
if (this.isInitialized) {
|
|
if (this.isInitialized) {
|
|
@@ -187,6 +186,7 @@ class PreferenceManager {
|
|
|
// 监听断点,判断是否移动端
|
|
// 监听断点,判断是否移动端
|
|
|
const breakpoints = useBreakpoints(breakpointsTailwind);
|
|
const breakpoints = useBreakpoints(breakpointsTailwind);
|
|
|
const isMobile = breakpoints.smaller('md');
|
|
const isMobile = breakpoints.smaller('md');
|
|
|
|
|
+
|
|
|
watch(
|
|
watch(
|
|
|
() => isMobile.value,
|
|
() => isMobile.value,
|
|
|
(val) => {
|
|
(val) => {
|
|
@@ -201,12 +201,13 @@ class PreferenceManager {
|
|
|
window
|
|
window
|
|
|
.matchMedia('(prefers-color-scheme: dark)')
|
|
.matchMedia('(prefers-color-scheme: dark)')
|
|
|
.addEventListener('change', ({ matches: isDark }) => {
|
|
.addEventListener('change', ({ matches: isDark }) => {
|
|
|
- // 如果偏好设置中主题模式为auto,则跟随系统更新
|
|
|
|
|
|
|
+ // 仅在自动模式下跟随系统主题
|
|
|
if (this.state.theme.mode === 'auto') {
|
|
if (this.state.theme.mode === 'auto') {
|
|
|
|
|
+ // 先应用实际的主题
|
|
|
this.updatePreferences({
|
|
this.updatePreferences({
|
|
|
theme: { mode: isDark ? 'dark' : 'light' },
|
|
theme: { mode: isDark ? 'dark' : 'light' },
|
|
|
});
|
|
});
|
|
|
- // 恢复为auto模式
|
|
|
|
|
|
|
+ // 再恢复为 auto 模式,保持跟随系统的状态
|
|
|
this.updatePreferences({
|
|
this.updatePreferences({
|
|
|
theme: { mode: 'auto' },
|
|
theme: { mode: 'auto' },
|
|
|
});
|
|
});
|
|
@@ -216,19 +217,17 @@ class PreferenceManager {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 更新页面颜色模式(灰色、色弱)
|
|
* 更新页面颜色模式(灰色、色弱)
|
|
|
- * @param preference
|
|
|
|
|
|
|
+ * @param preference - 偏好设置
|
|
|
*/
|
|
*/
|
|
|
private updateColorMode(preference: Preferences) {
|
|
private updateColorMode(preference: Preferences) {
|
|
|
- if (preference.app) {
|
|
|
|
|
- const { colorGrayMode, colorWeakMode } = preference.app;
|
|
|
|
|
- const dom = document.documentElement;
|
|
|
|
|
- const COLOR_WEAK = 'invert-mode';
|
|
|
|
|
- const COLOR_GRAY = 'grayscale-mode';
|
|
|
|
|
- dom.classList.toggle(COLOR_WEAK, colorWeakMode);
|
|
|
|
|
- dom.classList.toggle(COLOR_GRAY, colorGrayMode);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const { colorGrayMode, colorWeakMode } = preference.app;
|
|
|
|
|
+ const dom = document.documentElement;
|
|
|
|
|
+
|
|
|
|
|
+ dom.classList.toggle('invert-mode', colorWeakMode);
|
|
|
|
|
+ dom.classList.toggle('grayscale-mode', colorGrayMode);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const preferencesManager = new PreferenceManager();
|
|
const preferencesManager = new PreferenceManager();
|
|
|
|
|
+
|
|
|
export { PreferenceManager, preferencesManager };
|
|
export { PreferenceManager, preferencesManager };
|