427 lines
16 KiB
TypeScript
427 lines
16 KiB
TypeScript
import {defineStore} from 'pinia';
|
|
import {computed, reactive} from 'vue';
|
|
import {ConfigService} from '@/../bindings/voidraft/internal/services';
|
|
import {
|
|
AppConfig,
|
|
AppearanceConfig,
|
|
EditingConfig,
|
|
GeneralConfig,
|
|
LanguageType,
|
|
SystemThemeType,
|
|
TabType,
|
|
ThemeType
|
|
} from '@/../bindings/voidraft/internal/models';
|
|
import {useI18n} from 'vue-i18n';
|
|
import {useErrorHandler} from '@/utils/errorHandler';
|
|
import {ConfigUtils} from '@/utils/configUtils';
|
|
import {WindowController} from '@/utils/windowController';
|
|
import * as runtime from '@wailsio/runtime';
|
|
// 国际化相关导入
|
|
export type SupportedLocaleType = 'zh-CN' | 'en-US';
|
|
|
|
// 支持的语言列表
|
|
export const SUPPORTED_LOCALES = [
|
|
{
|
|
code: 'zh-CN' as SupportedLocaleType,
|
|
name: '简体中文'
|
|
},
|
|
{
|
|
code: 'en-US' as SupportedLocaleType,
|
|
name: 'English'
|
|
}
|
|
] as const;
|
|
|
|
// 配置键映射和限制的类型定义
|
|
type GeneralConfigKeyMap = {
|
|
readonly [K in keyof GeneralConfig]: string;
|
|
};
|
|
|
|
type EditingConfigKeyMap = {
|
|
readonly [K in keyof EditingConfig]: string;
|
|
};
|
|
|
|
type AppearanceConfigKeyMap = {
|
|
readonly [K in keyof AppearanceConfig]: string;
|
|
};
|
|
|
|
type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
|
|
|
|
// 配置键映射
|
|
const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = {
|
|
alwaysOnTop: 'general.always_on_top',
|
|
dataPath: 'general.data_path',
|
|
enableSystemTray: 'general.enable_system_tray',
|
|
enableGlobalHotkey: 'general.enable_global_hotkey',
|
|
globalHotkey: 'general.global_hotkey'
|
|
} as const;
|
|
|
|
const EDITING_CONFIG_KEY_MAP: EditingConfigKeyMap = {
|
|
fontSize: 'editing.font_size',
|
|
fontFamily: 'editing.font_family',
|
|
fontWeight: 'editing.font_weight',
|
|
lineHeight: 'editing.line_height',
|
|
enableTabIndent: 'editing.enable_tab_indent',
|
|
tabSize: 'editing.tab_size',
|
|
tabType: 'editing.tab_type',
|
|
autoSaveDelay: 'editing.auto_save_delay'
|
|
} as const;
|
|
|
|
const APPEARANCE_CONFIG_KEY_MAP: AppearanceConfigKeyMap = {
|
|
language: 'appearance.language',
|
|
theme: 'appearance.theme',
|
|
systemTheme: 'appearance.system_theme'
|
|
} as const;
|
|
|
|
// 配置限制
|
|
const CONFIG_LIMITS = {
|
|
fontSize: {min: 12, max: 28, default: 13},
|
|
tabSize: {min: 2, max: 8, default: 4},
|
|
lineHeight: {min: 1.0, max: 3.0, default: 1.5},
|
|
tabType: {values: [TabType.TabTypeSpaces, TabType.TabTypeTab], default: TabType.TabTypeSpaces}
|
|
} as const;
|
|
|
|
// 常用字体选项
|
|
export const FONT_OPTIONS = [
|
|
{
|
|
label: '鸿蒙字体',
|
|
value: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif'
|
|
},
|
|
{label: '微软雅黑', value: '"Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif'},
|
|
{label: '苹方字体', value: '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif'},
|
|
{
|
|
label: 'JetBrains Mono',
|
|
value: '"JetBrains Mono", "Fira Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'
|
|
},
|
|
{label: 'Fira Code', value: '"Fira Code", "JetBrains Mono", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'},
|
|
{label: 'Source Code Pro', value: '"Source Code Pro", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'},
|
|
{label: 'Cascadia Code', value: '"Cascadia Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace'},
|
|
{
|
|
label: '系统等宽字体',
|
|
value: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace'
|
|
}
|
|
] as const;
|
|
|
|
// 获取浏览器的默认语言
|
|
const getBrowserLanguage = (): SupportedLocaleType => {
|
|
const browserLang = navigator.language;
|
|
const langCode = browserLang.split('-')[0];
|
|
|
|
// 检查是否支持此语言
|
|
const supportedLang = SUPPORTED_LOCALES.find(locale =>
|
|
locale.code.startsWith(langCode) || locale.code.split('-')[0] === langCode
|
|
);
|
|
|
|
return supportedLang?.code || 'zh-CN';
|
|
};
|
|
|
|
// 默认配置
|
|
const DEFAULT_CONFIG: AppConfig = {
|
|
general: {
|
|
alwaysOnTop: false,
|
|
dataPath: '',
|
|
enableSystemTray: true,
|
|
enableGlobalHotkey: false,
|
|
globalHotkey: {
|
|
ctrl: false,
|
|
shift: false,
|
|
alt: true,
|
|
win: false,
|
|
key: 'X'
|
|
}
|
|
},
|
|
editing: {
|
|
fontSize: CONFIG_LIMITS.fontSize.default,
|
|
fontFamily: FONT_OPTIONS[0].value,
|
|
fontWeight: 'normal',
|
|
lineHeight: CONFIG_LIMITS.lineHeight.default,
|
|
enableTabIndent: true,
|
|
tabSize: CONFIG_LIMITS.tabSize.default,
|
|
tabType: CONFIG_LIMITS.tabType.default,
|
|
autoSaveDelay: 5000
|
|
},
|
|
appearance: {
|
|
language: LanguageType.LangZhCN,
|
|
theme: 'default-dark' as ThemeType,
|
|
systemTheme: 'dark' as SystemThemeType
|
|
},
|
|
keyBindings: {},
|
|
updates: {},
|
|
metadata: {
|
|
version: '1.0.0',
|
|
lastUpdated: null
|
|
}
|
|
};
|
|
|
|
|
|
export const useConfigStore = defineStore('config', () => {
|
|
const {locale} = useI18n();
|
|
const {safeCall} = useErrorHandler();
|
|
|
|
// 响应式状态
|
|
const state = reactive({
|
|
config: {...DEFAULT_CONFIG} as AppConfig,
|
|
isLoading: false,
|
|
configLoaded: false
|
|
});
|
|
|
|
// 计算属性 - 使用工厂函数简化
|
|
const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]);
|
|
const limits = Object.fromEntries(
|
|
(['fontSize', 'tabSize', 'lineHeight'] as const).map(key => [key, createLimitComputed(key)])
|
|
) as Record<NumberConfigKey, ReturnType<typeof createLimitComputed>>;
|
|
|
|
// 通用配置更新方法
|
|
const updateGeneralConfig = async <K extends keyof GeneralConfig>(key: K, value: GeneralConfig[K]): Promise<void> => {
|
|
// 确保配置已加载
|
|
if (!state.configLoaded && !state.isLoading) {
|
|
await initConfig();
|
|
}
|
|
|
|
const backendKey = GENERAL_CONFIG_KEY_MAP[key];
|
|
if (!backendKey) {
|
|
throw new Error(`No backend key mapping found for general.${key.toString()}`);
|
|
}
|
|
|
|
await ConfigService.Set(backendKey, value);
|
|
state.config.general[key] = value;
|
|
};
|
|
|
|
const updateEditingConfig = async <K extends keyof EditingConfig>(key: K, value: EditingConfig[K]): Promise<void> => {
|
|
// 确保配置已加载
|
|
if (!state.configLoaded && !state.isLoading) {
|
|
await initConfig();
|
|
}
|
|
|
|
const backendKey = EDITING_CONFIG_KEY_MAP[key];
|
|
if (!backendKey) {
|
|
throw new Error(`No backend key mapping found for editing.${key.toString()}`);
|
|
}
|
|
|
|
await ConfigService.Set(backendKey, value);
|
|
state.config.editing[key] = value;
|
|
};
|
|
|
|
const updateAppearanceConfig = async <K extends keyof AppearanceConfig>(key: K, value: AppearanceConfig[K]): Promise<void> => {
|
|
// 确保配置已加载
|
|
if (!state.configLoaded && !state.isLoading) {
|
|
await initConfig();
|
|
}
|
|
|
|
const backendKey = APPEARANCE_CONFIG_KEY_MAP[key];
|
|
if (!backendKey) {
|
|
throw new Error(`No backend key mapping found for appearance.${key.toString()}`);
|
|
}
|
|
|
|
await ConfigService.Set(backendKey, value);
|
|
state.config.appearance[key] = value;
|
|
};
|
|
|
|
// 加载配置
|
|
const initConfig = async (): Promise<void> => {
|
|
if (state.isLoading) return;
|
|
|
|
state.isLoading = true;
|
|
try {
|
|
const appConfig = await ConfigService.GetConfig();
|
|
|
|
if (appConfig) {
|
|
// 合并配置
|
|
if (appConfig.general) Object.assign(state.config.general, appConfig.general);
|
|
if (appConfig.editing) Object.assign(state.config.editing, appConfig.editing);
|
|
if (appConfig.appearance) Object.assign(state.config.appearance, appConfig.appearance);
|
|
if (appConfig.keyBindings) Object.assign(state.config.keyBindings, appConfig.keyBindings);
|
|
if (appConfig.updates) Object.assign(state.config.updates, appConfig.updates);
|
|
if (appConfig.metadata) Object.assign(state.config.metadata, appConfig.metadata);
|
|
}
|
|
|
|
state.configLoaded = true;
|
|
|
|
// 初始化热键监听器
|
|
const windowController = WindowController.getInstance();
|
|
await windowController.initializeHotkeyListener();
|
|
} finally {
|
|
state.isLoading = false;
|
|
}
|
|
};
|
|
|
|
// 通用数值调整器工厂
|
|
const createAdjuster = <T extends NumberConfigKey>(key: T) => {
|
|
const limit = CONFIG_LIMITS[key];
|
|
const clamp = (value: number) => ConfigUtils.clamp(value, limit.min, limit.max);
|
|
|
|
return {
|
|
increase: () => safeCall(() => updateEditingConfig(key, clamp(state.config.editing[key] + 1)), 'config.saveFailed', 'config.saveSuccess'),
|
|
decrease: () => safeCall(() => updateEditingConfig(key, clamp(state.config.editing[key] - 1)), 'config.saveFailed', 'config.saveSuccess'),
|
|
set: (value: number) => safeCall(() => updateEditingConfig(key, clamp(value)), 'config.saveFailed', 'config.saveSuccess'),
|
|
reset: () => safeCall(() => updateEditingConfig(key, limit.default), 'config.saveFailed', 'config.saveSuccess')
|
|
};
|
|
};
|
|
|
|
// 通用布尔值切换器
|
|
const createGeneralToggler = <T extends keyof GeneralConfig>(key: T) =>
|
|
() => safeCall(() => updateGeneralConfig(key, !state.config.general[key] as GeneralConfig[T]), 'config.saveFailed', 'config.saveSuccess');
|
|
|
|
const createEditingToggler = <T extends keyof EditingConfig>(key: T) =>
|
|
() => safeCall(() => updateEditingConfig(key, !state.config.editing[key] as EditingConfig[T]), 'config.saveFailed', 'config.saveSuccess');
|
|
|
|
// 枚举值切换器
|
|
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
|
|
() => {
|
|
const currentIndex = values.indexOf(state.config.editing[key] as T);
|
|
const nextIndex = (currentIndex + 1) % values.length;
|
|
return safeCall(() => updateEditingConfig(key, values[nextIndex]), 'config.saveFailed', 'config.saveSuccess');
|
|
};
|
|
|
|
// 重置配置
|
|
const resetConfig = async (): Promise<void> => {
|
|
if (state.isLoading) return;
|
|
|
|
state.isLoading = true;
|
|
try {
|
|
// 调用后端重置配置
|
|
await safeCall(() => ConfigService.ResetConfig(), 'config.resetFailed', 'config.resetSuccess');
|
|
|
|
// 立即重新加载后端配置以确保前端状态同步
|
|
await safeCall(async () => {
|
|
const appConfig = await ConfigService.GetConfig();
|
|
if (appConfig) {
|
|
state.config = JSON.parse(JSON.stringify(appConfig)) as AppConfig;
|
|
}
|
|
}, 'config.loadFailed', 'config.loadSuccess');
|
|
} finally {
|
|
state.isLoading = false;
|
|
}
|
|
};
|
|
|
|
// 语言设置方法
|
|
const setLanguage = async (language: LanguageType): Promise<void> => {
|
|
await safeCall(async () => {
|
|
await updateAppearanceConfig('language', language);
|
|
|
|
// 同步更新前端语言
|
|
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
|
locale.value = frontendLocale as any;
|
|
}, 'config.languageChangeFailed', 'config.languageChanged');
|
|
};
|
|
|
|
// 主题设置方法
|
|
const setTheme = async (theme: ThemeType): Promise<void> => {
|
|
await safeCall(async () => {
|
|
await updateAppearanceConfig('theme', theme);
|
|
}, 'config.themeChangeFailed', 'config.themeChanged');
|
|
};
|
|
|
|
// 系统主题设置方法
|
|
const setSystemTheme = async (systemTheme: SystemThemeType): Promise<void> => {
|
|
await safeCall(async () => {
|
|
await updateAppearanceConfig('systemTheme', systemTheme);
|
|
}, 'config.systemThemeChangeFailed', 'config.systemThemeChanged');
|
|
};
|
|
|
|
// 初始化语言设置
|
|
const initializeLanguage = async (): Promise<void> => {
|
|
try {
|
|
// 如果配置未加载,先加载配置
|
|
if (!state.configLoaded) {
|
|
await initConfig();
|
|
}
|
|
|
|
// 同步前端语言设置
|
|
const frontendLocale = ConfigUtils.backendLanguageToFrontend(state.config.appearance.language);
|
|
locale.value = frontendLocale as any;
|
|
} catch (error) {
|
|
const browserLang = getBrowserLanguage();
|
|
locale.value = browserLang as any;
|
|
}
|
|
};
|
|
|
|
// 创建数值调整器实例
|
|
const adjusters = {
|
|
fontSize: createAdjuster('fontSize'),
|
|
tabSize: createAdjuster('tabSize'),
|
|
lineHeight: createAdjuster('lineHeight')
|
|
};
|
|
|
|
// 创建切换器实例
|
|
const togglers = {
|
|
tabIndent: createEditingToggler('enableTabIndent'),
|
|
alwaysOnTop: async () => {
|
|
await safeCall(async () => {
|
|
await updateGeneralConfig('alwaysOnTop', !state.config.general.alwaysOnTop);
|
|
// 立即应用窗口置顶状态
|
|
await runtime.Window.SetAlwaysOnTop(state.config.general.alwaysOnTop);
|
|
}, 'config.alwaysOnTopFailed', 'config.alwaysOnTopSuccess');
|
|
},
|
|
tabType: createEnumToggler('tabType', CONFIG_LIMITS.tabType.values)
|
|
};
|
|
|
|
// 字符串配置设置器
|
|
const setters = {
|
|
fontFamily: (value: string) => safeCall(() => updateEditingConfig('fontFamily', value), 'config.saveFailed', 'config.saveSuccess'),
|
|
fontWeight: (value: string) => safeCall(() => updateEditingConfig('fontWeight', value), 'config.saveFailed', 'config.saveSuccess'),
|
|
dataPath: (value: string) => safeCall(() => updateGeneralConfig('dataPath', value), 'config.saveFailed', 'config.saveSuccess'),
|
|
autoSaveDelay: (value: number) => safeCall(() => updateEditingConfig('autoSaveDelay', value), 'config.saveFailed', 'config.saveSuccess')
|
|
};
|
|
|
|
return {
|
|
// 状态
|
|
config: computed(() => state.config),
|
|
configLoaded: computed(() => state.configLoaded),
|
|
isLoading: computed(() => state.isLoading),
|
|
|
|
// 限制常量
|
|
...limits,
|
|
|
|
// 核心方法
|
|
initConfig: () => safeCall(() => initConfig(), 'config.loadFailed', 'config.loadSuccess'),
|
|
resetConfig,
|
|
|
|
// 语言相关方法
|
|
setLanguage,
|
|
initializeLanguage,
|
|
|
|
// 主题相关方法
|
|
setTheme,
|
|
setSystemTheme,
|
|
|
|
// 字体大小操作
|
|
...adjusters.fontSize,
|
|
increaseFontSize: adjusters.fontSize.increase,
|
|
decreaseFontSize: adjusters.fontSize.decrease,
|
|
resetFontSize: adjusters.fontSize.reset,
|
|
setFontSize: adjusters.fontSize.set,
|
|
|
|
// Tab操作
|
|
toggleTabIndent: togglers.tabIndent,
|
|
setEnableTabIndent: (value: boolean) => safeCall(() => updateEditingConfig('enableTabIndent', value), 'config.saveFailed', 'config.saveSuccess'),
|
|
...adjusters.tabSize,
|
|
increaseTabSize: adjusters.tabSize.increase,
|
|
decreaseTabSize: adjusters.tabSize.decrease,
|
|
setTabSize: adjusters.tabSize.set,
|
|
toggleTabType: togglers.tabType,
|
|
|
|
// 行高操作
|
|
setLineHeight: adjusters.lineHeight.set,
|
|
|
|
// 窗口操作
|
|
toggleAlwaysOnTop: togglers.alwaysOnTop,
|
|
setAlwaysOnTop: (value: boolean) => safeCall(() => updateGeneralConfig('alwaysOnTop', value), 'config.saveFailed', 'config.saveSuccess'),
|
|
|
|
// 字体操作
|
|
setFontFamily: setters.fontFamily,
|
|
setFontWeight: setters.fontWeight,
|
|
|
|
// 路径操作
|
|
setDataPath: setters.dataPath,
|
|
|
|
// 保存配置相关方法
|
|
setAutoSaveDelay: setters.autoSaveDelay,
|
|
|
|
// 热键配置相关方法
|
|
setEnableGlobalHotkey: (value: boolean) => safeCall(() => updateGeneralConfig('enableGlobalHotkey', value), 'config.saveFailed', 'config.saveSuccess'),
|
|
setGlobalHotkey: (hotkey: any) => safeCall(() => updateGeneralConfig('globalHotkey', hotkey), 'config.saveFailed', 'config.saveSuccess'),
|
|
|
|
// 系统托盘配置相关方法
|
|
setEnableSystemTray: (value: boolean) => safeCall(() => updateGeneralConfig('enableSystemTray', value), 'config.saveFailed', 'config.saveSuccess')
|
|
};
|
|
}); |