157 lines
4.4 KiB
TypeScript
157 lines
4.4 KiB
TypeScript
import { useEffect, useCallback, useRef, useState } from 'react'
|
|
import { shallow } from 'zustand/shallow'
|
|
import {
|
|
saveDomiBoardContent,
|
|
getDomiBoardContent,
|
|
} from '../service/api'
|
|
import { useDominoStoreInstance } from '../components/canvas'
|
|
import type { SceneElement, DominoCanvasData } from '../components/canvas'
|
|
|
|
export function usePersistence(options: {
|
|
taskId: string
|
|
setLoading: (loading: boolean) => void
|
|
}) {
|
|
const { taskId, setLoading } = options
|
|
const store = useDominoStoreInstance()
|
|
|
|
const isFirstLoad = useRef(true)
|
|
const [initialized, setInitialized] = useState(false)
|
|
|
|
// 任务切换时重置状态
|
|
useEffect(() => {
|
|
isFirstLoad.current = true
|
|
setInitialized(false)
|
|
}, [taskId])
|
|
|
|
const loadBoard = useCallback(async () => {
|
|
if (!taskId) {
|
|
setLoading(false)
|
|
setInitialized(true)
|
|
return
|
|
}
|
|
const currentTaskId = taskId
|
|
setLoading(true)
|
|
|
|
try {
|
|
const res = (await getDomiBoardContent(taskId)) as
|
|
| DominoCanvasData
|
|
| string
|
|
|
|
if (taskId !== currentTaskId) return
|
|
|
|
if (res) {
|
|
try {
|
|
const parsed = (
|
|
typeof res === 'string' ? JSON.parse(res) : res
|
|
) as DominoCanvasData
|
|
if (parsed && typeof parsed === 'object') {
|
|
const { elements, elementOrder, createdAt, updatedAt } = parsed
|
|
const state = store.getState()
|
|
|
|
state.clearElements()
|
|
Object.values(elements).forEach(el => {
|
|
state.addElement(el)
|
|
})
|
|
// addElement might have added them in different order, so we overwrite it.
|
|
store.setState({
|
|
elementOrder,
|
|
metadata: {
|
|
createdAt: createdAt || Date.now(),
|
|
updatedAt: updatedAt || Date.now(),
|
|
},
|
|
})
|
|
|
|
state.resetHistory()
|
|
|
|
setLoading(false)
|
|
isFirstLoad.current = false
|
|
setInitialized(true)
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to parse saved DomiBoard content', e)
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to load DomiBoard content', e)
|
|
} finally {
|
|
if (taskId === currentTaskId) {
|
|
if (isFirstLoad.current) {
|
|
isFirstLoad.current = false
|
|
}
|
|
setLoading(false)
|
|
setInitialized(true)
|
|
store.getState().resetHistory()
|
|
}
|
|
}
|
|
}, [taskId, setLoading])
|
|
|
|
const saveTimerRef = useRef<NodeJS.Timeout | null>(null)
|
|
|
|
// Auto-save logic
|
|
useEffect(() => {
|
|
let lastState = {
|
|
elements: store.getState().elements,
|
|
elementOrder: store.getState().elementOrder,
|
|
}
|
|
|
|
const unsubscribe = store.subscribe(state => {
|
|
const curr = {
|
|
elements: state.elements,
|
|
elementOrder: state.elementOrder,
|
|
}
|
|
|
|
// Check if relevant state changed using shallow comparison
|
|
if (shallow(lastState, curr)) return
|
|
lastState = curr
|
|
|
|
if (isFirstLoad.current || !taskId) return
|
|
|
|
if (saveTimerRef.current) {
|
|
clearTimeout(saveTimerRef.current)
|
|
}
|
|
|
|
saveTimerRef.current = setTimeout(async () => {
|
|
try {
|
|
const currentMetadata = store.getState().metadata || {}
|
|
const createdAt = currentMetadata.createdAt || Date.now()
|
|
const persistentElements: Record<string, SceneElement> = {}
|
|
const persistentOrder: string[] = []
|
|
|
|
Object.values(curr.elements).forEach(el => {
|
|
if (el.type !== 'placeholder') {
|
|
persistentElements[el.id] = el
|
|
}
|
|
})
|
|
curr.elementOrder.forEach((id: string) => {
|
|
if (curr.elements[id] && curr.elements[id].type !== 'placeholder') {
|
|
persistentOrder.push(id)
|
|
}
|
|
})
|
|
|
|
const persistenceData: DominoCanvasData = {
|
|
elements: persistentElements,
|
|
elementOrder: persistentOrder,
|
|
createdAt,
|
|
updatedAt: Date.now(),
|
|
}
|
|
await saveDomiBoardContent({
|
|
task_id: taskId,
|
|
data: persistenceData,
|
|
})
|
|
} catch (e) {
|
|
console.error('Failed to auto-save DomiBoard content', e)
|
|
}
|
|
}, 2000) // 2 second debounce
|
|
})
|
|
|
|
return () => {
|
|
unsubscribe()
|
|
if (saveTimerRef.current) {
|
|
clearTimeout(saveTimerRef.current)
|
|
}
|
|
}
|
|
}, [taskId, store])
|
|
|
|
return { loadBoard, initialized }
|
|
}
|