feat: save button

This commit is contained in:
2026-03-12 21:06:47 +01:00
parent 029bab8917
commit 0578b26241
3 changed files with 57 additions and 60 deletions

View File

@@ -1,78 +1,38 @@
/**
* Hook for canvas graph state and persistence. Wraps useGraphStateWithHistory
* with initial graph from project storage (or example) and debounced + idle-based save.
* with initial graph from project storage (or example). Save is explicit via save().
* Keeps CanvasPage focused on composition and layout.
*/
import { useEffect, useMemo, useRef } from 'react'
import { useCallback, useMemo, useRef } from 'react'
import { useGraphStateWithHistory } from '@/hooks/useGraphStateWithHistory'
import { getInitialGraph } from '@/app/canvas/canvasGraphUtils'
import { saveGraphToStorage, PROJECT_VERSION } from '@/app/pleroma/projectGraphStorage'
import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes'
export type UseCanvasGraphResult = ReturnType<typeof useGraphStateWithHistory>
/** Debounce delay (ms) before we schedule a save. */
const SAVE_DEBOUNCE_MS = 800
/** Max wait (ms) for requestIdleCallback before falling back to setTimeout. */
const SAVE_IDLE_TIMEOUT_MS = 2000
export type UseCanvasGraphResult = ReturnType<typeof useGraphStateWithHistory> & {
/** Persist current nodes/edges to storage. No-op when projectId is missing. */
save: () => void
}
export function useCanvasGraph(projectId: string | undefined): UseCanvasGraphResult {
const initialGraph = useMemo(() => getInitialGraph(projectId), [projectId])
const result = useGraphStateWithHistory(initialGraph.nodes, initialGraph.edges)
const { nodes, edges } = result
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const idleCallbackRef = useRef<number | null>(null)
const pendingSaveRef = useRef<{ projectId: string; nodes: AppNode[]; edges: AppEdge[] } | null>(
null
)
const nodesRef = useRef(nodes)
const edgesRef = useRef(edges)
nodesRef.current = nodes
edgesRef.current = edges
useEffect(() => {
const save = useCallback(() => {
if (!projectId) return
saveGraphToStorage(projectId, {
version: PROJECT_VERSION,
nodes: nodesRef.current,
edges: edgesRef.current,
})
}, [projectId])
const scheduleSave = () => {
pendingSaveRef.current = { projectId, nodes, edges }
const doSave = () => {
const pending = pendingSaveRef.current
pendingSaveRef.current = null
if (pending && pending.projectId === projectId) {
saveGraphToStorage(pending.projectId, {
version: PROJECT_VERSION,
nodes: pending.nodes,
edges: pending.edges,
})
}
}
if (typeof requestIdleCallback !== 'undefined') {
idleCallbackRef.current = requestIdleCallback(doSave, {
timeout: SAVE_IDLE_TIMEOUT_MS,
})
} else {
idleCallbackRef.current = window.setTimeout(doSave, 0) as unknown as number
}
}
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
saveTimeoutRef.current = setTimeout(scheduleSave, SAVE_DEBOUNCE_MS)
return () => {
if (saveTimeoutRef.current) {
clearTimeout(saveTimeoutRef.current)
saveTimeoutRef.current = null
}
if (idleCallbackRef.current != null) {
if (typeof cancelIdleCallback !== 'undefined') {
cancelIdleCallback(idleCallbackRef.current)
} else {
clearTimeout(idleCallbackRef.current)
}
idleCallbackRef.current = null
}
}
}, [projectId, nodes, edges])
return result
return { ...result, save }
}