117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
import { useEffect, useRef, useState, useCallback } from 'react'
|
|
import { useTabStore } from '../stores/tabStore'
|
|
import { useAutoSaveStore } from '../stores/autoSaveStore'
|
|
import { logError } from '../lib/errorHandler'
|
|
|
|
/** Debounce delay for auto-save (ms) */
|
|
const AUTO_SAVE_DELAY = 2000
|
|
|
|
/** 模块级 ref — 状态栏等外部组件通过此函数切换自动保存 */
|
|
let _toggleAutoSave: (() => void) | null = null
|
|
export function toggleAutoSaveExternal(): void {
|
|
_toggleAutoSave?.()
|
|
}
|
|
|
|
/**
|
|
* Auto-save hook: subscribes to Zustand tab store, debounces content changes
|
|
* and saves automatically after a pause in editing for file-backed tabs.
|
|
*
|
|
* Uses `useTabStore.getState()` and `.subscribe()` so it doesn't need to
|
|
* re-render on every keystroke — the effect is triggered once and runs
|
|
* reactively via the store subscription.
|
|
*
|
|
* Captures the tabId at debounce start so the timeout always saves the
|
|
* correct tab even if the user switches tabs during the debounce window.
|
|
*/
|
|
export function useAutoSave(): { isAutoSaving: boolean; autoSaveEnabled: boolean; toggleAutoSave: () => void } {
|
|
const [isAutoSaving, setIsAutoSaving] = useState(false)
|
|
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true)
|
|
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
const isSavingRef = useRef(false)
|
|
const enabledRef = useRef(true)
|
|
const mountedRef = useRef(true)
|
|
|
|
const toggleAutoSave = useCallback(() => {
|
|
setAutoSaveEnabled(prev => {
|
|
const next = !prev
|
|
enabledRef.current = next
|
|
if (!next && timerRef.current) {
|
|
clearTimeout(timerRef.current)
|
|
timerRef.current = null
|
|
}
|
|
return next
|
|
})
|
|
}, [])
|
|
|
|
// 暴露给外部(状态栏按钮)
|
|
_toggleAutoSave = toggleAutoSave
|
|
|
|
useEffect(() => {
|
|
mountedRef.current = true
|
|
|
|
// Subscribe to Zustand store — fires on every state change
|
|
const unsub = useTabStore.subscribe((state) => {
|
|
if (!enabledRef.current) return
|
|
|
|
const tab = state.getActiveTab()
|
|
if (!tab || !tab.filePath || !tab.isModified) return
|
|
|
|
// Capture the tabId so the timeout saves the correct tab
|
|
const tabIdToSave = tab.id
|
|
|
|
// Debounce: clear previous timer, start new one
|
|
if (timerRef.current) {
|
|
clearTimeout(timerRef.current)
|
|
}
|
|
|
|
timerRef.current = setTimeout(async () => {
|
|
if (isSavingRef.current) return
|
|
|
|
// Re-read latest state; verify the captured tab still exists and is modified
|
|
const currentState = useTabStore.getState()
|
|
const tabToSave = currentState.tabs.find(t => t.id === tabIdToSave)
|
|
if (!tabToSave || !tabToSave.filePath || !tabToSave.isModified) return
|
|
|
|
isSavingRef.current = true
|
|
if (mountedRef.current) setIsAutoSaving(true)
|
|
|
|
try {
|
|
if (!window.electronAPI) return
|
|
const result = await window.electronAPI.saveFile({
|
|
filePath: tabToSave.filePath,
|
|
content: tabToSave.content
|
|
})
|
|
if (result.success && mountedRef.current) {
|
|
currentState.setModified(tabToSave.id, false)
|
|
}
|
|
} catch (error) {
|
|
logError('自动保存失败', error)
|
|
} finally {
|
|
if (mountedRef.current) setIsAutoSaving(false)
|
|
isSavingRef.current = false
|
|
}
|
|
}, AUTO_SAVE_DELAY)
|
|
})
|
|
|
|
return () => {
|
|
mountedRef.current = false
|
|
unsub()
|
|
if (timerRef.current) {
|
|
clearTimeout(timerRef.current)
|
|
timerRef.current = null
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
// 同步 autoSaveEnabled / isAutoSaving 到 autoSaveStore(供状态栏读取)
|
|
useEffect(() => {
|
|
useAutoSaveStore.getState().setState({ autoSaveEnabled })
|
|
}, [autoSaveEnabled])
|
|
|
|
useEffect(() => {
|
|
useAutoSaveStore.getState().setState({ isAutoSaving })
|
|
}, [isAutoSaving])
|
|
|
|
return { isAutoSaving, autoSaveEnabled, toggleAutoSave }
|
|
}
|