import { useState, useEffect } from 'react'; import { useStore } from './store'; import { FileTree } from './features/file-tree/FileTree'; import { Editor } from './features/editor/Editor'; import { Toolbar } from './features/toolbar/Toolbar'; import { TitleBar } from './features/shell/TitleBar'; import { InputDialog } from './features/dialog/InputDialog'; import { NotifyLayer } from './features/dialog/NotifyLayer'; import { TabBar } from './features/tabs/TabBar'; import { useTabs } from './features/tabs/tab-store'; import { GraphView } from './features/graph/GraphView'; import { SettingsPanel } from './features/settings/SettingsPanel'; import { AppearancePanel } from './features/appearance/AppearancePanel'; import { useAppearance } from './features/appearance/appearance-store'; import { CheatSheet } from './features/keymap/CheatSheet'; import { useGlobalHotkeys } from './features/keymap/use-hotkeys'; import { useKeymap } from './features/keymap/keymap-service'; import { useLayout } from './features/shell/layout-store'; import { Resizer } from './features/shell/Resizer'; import { EffectLayer } from './features/effects/EffectLayer'; import { useSettings } from './features/settings/settings-store'; import { SearchBox } from './features/search/SearchBox'; import { TagFilter } from './features/search/TagFilter'; import { OutlinePanel } from './features/outline/OutlinePanel'; import { DocInfoPanel } from './features/doc-info/DocInfoPanel'; export function App() { const { loadVault, loadTree, openDoc } = useStore(); const { restore, tabs, activeId } = useTabs(); const [rightTab, setRightTab] = useState<'outline' | 'info'>('outline'); const [view, setView] = useState<'editor' | 'graph'>('editor'); const [showSettings, setShowSettings] = useState(false); const [showCheat, setShowCheat] = useState(false); const [showAppearance, setShowAppearance] = useState(false); const appearance = useAppearance(); const settings = useSettings(); const layout = useLayout(); useEffect(() => { loadVault(); loadTree(); restore(); appearance.load(); useKeymap.getState().load(); useSettings.getState().loadGpu(); }, []); useEffect(() => { if (activeId && tabs.length > 0) { const t = tabs.find(x => x.id === activeId); if (t) openDoc(t.path).catch(() => {}); } }, []); // FR-S4:关窗前冲刷未保存内容 useEffect(() => { const onBeforeUnload = () => { try { useStore.getState().saveNow(); } catch {} }; window.addEventListener('beforeunload', onBeforeUnload); return () => window.removeEventListener('beforeunload', onBeforeUnload); }, []); useGlobalHotkeys({ onNewDoc: () => {}, onSave: () => useStore.getState().saveNow(), onFocusSearch: () => { const el = document.querySelector('input[placeholder*="搜索"]') as HTMLInputElement; el?.focus(); }, onToggleOutline: () => layout.toggleOutline(), onCheat: () => setShowCheat(true), onSettings: () => setShowSettings(true), onFind: () => { window.dispatchEvent(new CustomEvent('dsh-toggle-find')); } }); // 编辑器样式随设置生效(FR-编辑器设置真实生效) useEffect(() => { document.documentElement.style.setProperty('--editor-font-size', settings.fontSize + 'px'); document.documentElement.style.setProperty('--editor-font-family', settings.fontFamily); document.documentElement.style.setProperty('--editor-white-space', settings.wordWrap ? 'pre-wrap' : 'pre'); }, [settings.fontSize, settings.fontFamily, settings.wordWrap]); return (
{appearance.bgImage &&
}
setView(v => v === 'editor' ? 'graph' : 'editor')} onSettings={() => setShowSettings(true)} onAppearance={() => setShowAppearance(true)} onToggleSidebar={() => layout.toggleSidebar()} onToggleMinimal={() => layout.toggleMinimal()} minimal={layout.minimal} /> {view === 'editor' && }
{layout.minimal ? () : (<> {layout.sidebarVisible && (<>
layout.setSidebarWidth(layout.sidebarWidth + d)} /> )} {view === 'editor' ? : } {view === 'editor' && layout.outlineVisible && (<> layout.setOutlineWidth(layout.outlineWidth + d)} />
{rightTab === 'outline' ? : }
)} )}
{showSettings && setShowSettings(false)} />} {showAppearance && setShowAppearance(false)} />} {showCheat && setShowCheat(false)} />}
); } function tabBtn(active: boolean): React.CSSProperties { return { flex: 1, padding: '6px', background: active ? '#2a2a3a' : 'transparent', border: 'none', color: active ? '#fff' : '#888', cursor: 'pointer', borderBottom: active ? '2px solid #4a9eff' : '2px solid transparent' }; }