import { useState, useEffect } from 'react'; import { useSettings } from './settings-store'; import { useKeymap } from '../keymap/keymap-service'; import { useStore } from '../../store'; import { useTabs } from '../tabs/tab-store'; import { invoke } from '../../ipc-client'; import { IPC } from '@hunter/shared'; const pageBtn: React.CSSProperties = { padding: '8px 14px', border: 'none', background: 'transparent', color: '#888', cursor: 'pointer', borderBottom: '2px solid transparent', fontSize: 13 }; const row: React.CSSProperties = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 0', borderBottom: '1px solid #2a2a3a' }; const lbl: React.CSSProperties = { fontSize: 13, color: '#ccc' }; const toggle = (on: boolean): React.CSSProperties => ({ width: 36, height: 20, borderRadius: 10, background: on ? '#4a9eff' : '#444', position: 'relative', cursor: 'pointer', border: 'none', transition: 'background 0.2s' }); export function SettingsPanel({ onClose }: { onClose: () => void }) { const s = useSettings(); const [page, setPage] = useState<'keymap' | 'editor' | 'path' | 'other'>('editor'); const { bindings, setCombo, resetAll, save: saveKeymap, conflicts } = useKeymap(); const [recId, setRecId] = useState(null); useEffect(() => { if (!recId) return; const onKey = (e: KeyboardEvent) => { e.preventDefault(); if (e.key === 'Escape') { setRecId(null); return; } if (!e.ctrlKey && !e.metaKey && !e.altKey) return; const parts: string[] = []; if (e.ctrlKey) parts.push('ctrl'); if (e.shiftKey) parts.push('shift'); if (e.altKey) parts.push('alt'); if (e.metaKey) parts.push('meta'); const k = e.key.toLowerCase(); if (k.length > 1) return; parts.push(k); const combo = parts.join('+'); setCombo(recId, combo); void saveKeymap(); setRecId(null); }; window.addEventListener('keydown', onKey, true); return () => window.removeEventListener('keydown', onKey, true); }, [recId, setCombo, saveKeymap]); return (
{ if (e.target === e.currentTarget) onClose(); }} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1500 }}>
设置
{(['editor','keymap','path','other'] as const).map(p => ( ))}
{page === 'editor' && <>
字号 s.set('fontSize', +e.target.value)} /> {s.fontSize}px
字体
自动保存间隔
代码块显示行号
自动换行
} {page === 'other' &&
删除前确认
} {page === 'path' && } {page === 'keymap' && <>
点击「录入」后按下新组合键(需含 Ctrl/Meta/Alt);Esc 取消
{bindings.map(b => { const cf = conflicts(); const inConflict = Object.values(cf).some(ids => ids.includes(b.id)); return (
{b.label} {inConflict && 冲突} {recId === b.id ? '按下组合键…' : b.combo}
); })} }
设置已自动保存
); } const sel: React.CSSProperties = { background: '#15151f', border: '1px solid #3a3a4a', color: '#ddd', borderRadius: 4, padding: '4px 8px', fontSize: 13 }; function dot(on: boolean): React.CSSProperties { return { position: 'absolute', top: 2, left: on ? 18 : 2, width: 16, height: 16, borderRadius: '50%', background: '#fff', transition: 'left 0.2s' }; } // FR-V5: 默认库路径设置页——查看当前库、浏览选择、立即切换 function PathPage() { const { vault, loadVault, loadTree } = useStore(); const [val, setVal] = useState(vault?.currentRoot ?? ''); const [msg, setMsg] = useState(''); const browse = async () => { const p = await invoke(IPC.vaultBrowse, undefined as never); if (p) setVal(p); }; const apply = async () => { if (!val.trim()) { setMsg('请填写或浏览选择库路径'); return; } try { setMsg('切换中…'); await useStore.getState().saveNow(); // 先冲刷未保存到旧库 await invoke(IPC.vaultSwitch, { root: val.trim() }); useTabs.getState().closeAll(); // 清空标签页(FR-V6) useStore.getState().resetEditor(); // 重置编辑区 await loadVault(); await loadTree(); setMsg('已切换到:' + val.trim()); } catch (e: any) { setMsg('切换失败:' + (e?.message ?? String(e))); } }; return ( <>
当前库路径 {vault?.currentRoot ?? '—'}
新库路径 setVal(e.target.value)} placeholder="填写或浏览选择目录…" style={{ ...sel, flex: 1, maxWidth: 220 }} />
{msg && {msg}}
); }