import { useCallback, useRef } from 'react' import { useTabStore } from '../stores/tabStore' import { useEditorStore } from '../stores/editorStore' import { recentFilesRepository } from '../db/recentFilesRepository' import { logError } from '../lib/errorHandler' import { MeToast, showToast } from '../lib/toast' /** * AR-01: 从 App.tsx 提取的文件操作逻辑 * UX-02: 添加 loading 状态指示 * v0.1.9: handleSave 添加防重入锁,避免编辑器 onSave + 全局 Ctrl+S 双重触发 * v0.6.0: loading 改用 MeToast.loading 链式转换,保存结果用 MeToast.promise 提示 */ export function useFileOperations() { const createTab = useTabStore(s => s.createTab) const getActiveTab = useTabStore(s => s.getActiveTab) const setLoading = useEditorStore(s => s.setLoading) // 防重入:onSave 回调 + 全局 Ctrl+S handler 可能在 300ms 内双重触发 const savingGate = useRef(false) const handleOpenFile = useCallback(async (): Promise => { if (!window.electronAPI) return const loading = MeToast.loading('打开文件...') try { setLoading('file-open', true) const result = await window.electronAPI.openFile() if (result && 'filePath' in result) { createTab(result.filePath, result.content) if (result.filePath) recentFilesRepository.add(result.filePath) setTimeout(() => useTabStore.getState().saveToDB(), 100) loading.success('文件已打开') } else { loading.dismiss() } } catch (error) { logError('打开文件失败', error) loading.error('打开文件失败') } finally { setLoading('file-open', false) } }, [createTab, setLoading]) const handleSave = useCallback(async (): Promise => { // 防重入:300ms 内忽略重复调用(编辑器 onSave + 全局 Ctrl+S 双重触发) if (savingGate.current) return savingGate.current = true try { const tab = getActiveTab() if (!tab || !window.electronAPI) return // v0.6.0: promise 监听保存生命周期 — 自动 loading → success/error,返回原 Promise const result = await MeToast.promise( window.electronAPI.saveFile({ filePath: tab.filePath, content: tab.content, }), { loading: '保存中...', success: '已保存', error: '保存失败', }, ) if (result.success) { useTabStore.getState().setModified(tab.id, false) } else { // IPC 返回 success:false 不 reject,promise 的 error 文案不会触发,需显式提示 showToast('保存失败', 'error') } } catch (error) { logError('保存文件失败', error) } finally { // 300ms 后释放锁,允许下次保存 setTimeout(() => { savingGate.current = false }, 300) } }, [getActiveTab]) const handleSaveAs = useCallback(async (): Promise => { try { const tab = getActiveTab() if (!tab || !window.electronAPI) return const result = await MeToast.promise( window.electronAPI.saveFileAs({ content: tab.content }), { loading: '另存为...', success: '已保存', error: '另存为失败', }, ) if (result.success) { useTabStore.getState().setModified(tab.id, false) } else if (!result.canceled) { showToast('另存为失败', 'error') } } catch (error) { logError('另存为失败', error) } }, [getActiveTab]) const handleOpenRecent = useCallback( async (filePath: string): Promise => { if (!window.electronAPI) return setLoading('file-open', true) try { const result = await window.electronAPI.readFile(filePath) if (result.success && result.content !== undefined) { createTab(filePath, result.content) recentFilesRepository.add(filePath) setTimeout(() => useTabStore.getState().saveToDB(), 100) } } finally { setLoading('file-open', false) } }, [createTab, setLoading], ) return { handleOpenFile, handleSave, handleSaveAs, handleOpenRecent } }