/** * KeybindingStore — 快捷键自定义系统状态管理 * * 管理用户自定义快捷键映射: * - customKeybindings: { actionId: customKeys } — 仅存储用户修改的 * - getEffectiveKeys(): 合并默认值和自定义值返回实际生效的键 * - 冲突检测: 检测新键绑定是否与现有绑定冲突 * - 持久化: 通过 electronAPI.settingsSet 保存到 SQLite * * @module stores/keybindingStore */ import { create } from 'zustand' import { createLogger } from '../utils/logger' import { KEYBINDING_DEFINITIONS, getKeybindingDefinition, type KeybindingActionId, type KeybindingDefinition, } from '../constants/keybindings' const logger = createLogger('KeybindingStore') // ─── 类型 ────────────────────────────────────────────── /** 自定义键绑定映射 (仅存储用户修改的) */ export type CustomKeybindings = Record interface KeybindingState { /** 自定义键绑定 (仅存储与默认不同的) */ customKeybindings: CustomKeybindings /** 是否已从持久化加载 */ isLoaded: boolean // Actions /** 从 electronAPI 加载自定义快捷键 */ loadFromSettings: () => Promise /** 获取操作的有效键绑定 (自定义优先,否则默认) */ getEffectiveKeys: (actionId: KeybindingActionId) => string /** 获取所有操作的有效键绑定映射 */ getAllEffective: () => Record /** 设置自定义键绑定 */ setCustomKeybinding: (actionId: KeybindingActionId, keys: string) => Promise /** 重置单个操作为默认 */ resetKeybinding: (actionId: KeybindingActionId) => Promise /** 重置所有为默认 */ resetAll: () => Promise /** 检测冲突 — 返回与新键绑定冲突的其他操作列表 */ getConflicts: (actionId: KeybindingActionId, keys: string) => KeybindingDefinition[] /** 规范化键序列 (统一修饰键顺序和大小写) */ normalizeKeySequence: (raw: string) => string } // ─── Store ───────────────────────────────────────────── export const useKeybindingStore = create((set, get) => ({ customKeybindings: {} as CustomKeybindings, isLoaded: false, loadFromSettings: async () => { try { const result = await window.electronAPI?.settingsGet('keybindings') if (result?.success && result.value && typeof result.value === 'object') { set({ customKeybindings: result.value as CustomKeybindings, isLoaded: true }) } else { set({ isLoaded: true }) } } catch (error) { logger.error('Failed to load keybindings:', error) set({ isLoaded: true }) } }, getEffectiveKeys: (actionId: KeybindingActionId) => { const { customKeybindings } = get() const custom = customKeybindings[actionId] if (custom && custom.trim()) return custom const def = getKeybindingDefinition(actionId) return def?.defaultKeys ?? '' }, getAllEffective: () => { const { customKeybindings } = get() const result = {} as Record for (const def of KEYBINDING_DEFINITIONS) { const custom = customKeybindings[def.id] result[def.id] = custom && custom.trim() ? custom : def.defaultKeys } return result }, setCustomKeybinding: async (actionId: KeybindingActionId, keys: string) => { const newCustoms = { ...get().customKeybindings, [actionId]: keys } set({ customKeybindings: newCustoms }) try { await window.electronAPI?.settingsSet('keybindings', newCustoms) } catch (error) { logger.error('Failed to persist keybinding:', error) } }, resetKeybinding: async (actionId: KeybindingActionId) => { const { [actionId]: _, ...rest } = get().customKeybindings set({ customKeybindings: rest as CustomKeybindings }) try { await window.electronAPI?.settingsSet('keybindings', rest) } catch (error) { logger.error('Failed to persist keybinding reset:', error) } }, resetAll: async () => { set({ customKeybindings: {} as CustomKeybindings }) try { await window.electronAPI?.settingsSet('keybindings', {}) } catch (error) { logger.error('Failed to persist keybinding resetAll:', error) } }, getConflicts: (actionId: KeybindingActionId, keys: string) => { if (!keys || !keys.trim()) return [] // 特殊处理: Ctrl+1…9 范围键不参与冲突检测 if (keys.includes('…') || keys.includes('...')) return [] const normalizedInput = get().normalizeKeySequence(keys) const conflicts: KeybindingDefinition[] = [] for (const def of KEYBINDING_DEFINITIONS) { // 跳过自身 if (def.id === actionId) continue // 跳过范围键 if (def.defaultKeys.includes('…') || def.defaultKeys.includes('...')) continue const effectiveKeys = get().getEffectiveKeys(def.id) // 跳过范围键的有效值 if (effectiveKeys.includes('…') || effectiveKeys.includes('...')) continue const normalizedExisting = get().normalizeKeySequence(effectiveKeys) if (normalizedExisting === normalizedInput) { conflicts.push(def) } } return conflicts }, normalizeKeySequence: (raw: string) => { if (!raw) return '' // 分割、去空格、统一大小写 const parts = raw .split('+') .map((p) => p.trim()) .filter(Boolean) .map((p) => { const lower = p.toLowerCase() // Meta → Ctrl (跨平台统一) if (lower === 'meta' || lower === 'cmd') return 'Ctrl' // 修饰键首字母大写 if (lower === 'ctrl' || lower === 'control') return 'Ctrl' if (lower === 'shift') return 'Shift' if (lower === 'alt' || lower === 'option') return 'Alt' // 普通键:首字母大写其余小写 (如 Tab, Enter, F11) return p.charAt(0).toUpperCase() + p.slice(1).toLowerCase() }) // 排序: Ctrl > Shift > Alt > 其他键 const modifierOrder: Record = { Ctrl: 0, Shift: 1, Alt: 2 } parts.sort((a, b) => { const aOrder = modifierOrder[a] ?? 99 const bOrder = modifierOrder[b] ?? 99 return aOrder - bOrder }) return parts.join('+') }, })) export default useKeybindingStore