diff --git a/frontend/src/app/canvas/CanvasMenubar.tsx b/frontend/src/app/canvas/CanvasMenubar.tsx index 81cca70..28c6e4a 100644 --- a/frontend/src/app/canvas/CanvasMenubar.tsx +++ b/frontend/src/app/canvas/CanvasMenubar.tsx @@ -80,7 +80,13 @@ export function CanvasMenubar({ const prevSaveStatusRef = useRef(saveStatus) useEffect(() => { - if (prevSaveStatusRef.current === 'saving' && saveStatus === 'saved') { + if (saveStatus === 'unsaved') { + setShowSavedBriefly(false) + if (savedBrieflyTimerRef.current) { + clearTimeout(savedBrieflyTimerRef.current) + savedBrieflyTimerRef.current = null + } + } else if (prevSaveStatusRef.current === 'saving' && saveStatus === 'saved') { setShowSavedBriefly(true) if (savedBrieflyTimerRef.current) clearTimeout(savedBrieflyTimerRef.current) savedBrieflyTimerRef.current = setTimeout(() => { @@ -330,7 +336,7 @@ export function CanvasMenubar({ {saveStatus === 'unsaved' && ( )} - {(saveStatus === 'saved' || showSavedBriefly) && ( + {saveStatus !== 'unsaved' && (saveStatus === 'saved' || showSavedBriefly) && ( )} diff --git a/frontend/src/app/canvas/useCanvasGraph.ts b/frontend/src/app/canvas/useCanvasGraph.ts index ef4a437..d3c89f3 100644 --- a/frontend/src/app/canvas/useCanvasGraph.ts +++ b/frontend/src/app/canvas/useCanvasGraph.ts @@ -1,13 +1,11 @@ /** * Hook for canvas graph state and persistence. Wraps useGraphStateWithHistory - * with initial graph from project storage (or example). Save is explicit via save(); - * autosave runs on an interval (~10s) and saves when the graph has changed. + * with initial graph from project storage (or example). Save is explicit via save(). */ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useMemo, useRef, useState } from 'react' import { useGraphStateWithHistory } from '@/hooks/useGraphStateWithHistory' import { getInitialGraph } from '@/app/canvas/canvasGraphUtils' -import { useCanvasStore } from '@/app/canvas/canvasStore' import { saveGraphToStorage, PROJECT_VERSION } from '@/app/pleroma/projectGraphStorage' import type { AppNode, AppEdge } from '@/lib/graph/nodeTypes' @@ -20,70 +18,46 @@ export type UseCanvasGraphResult = ReturnType & saveStatus: SaveStatus } -const AUTOSAVE_INTERVAL_MS = 10_000 - 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 storeNodes = useCanvasStore((s) => s.graph.nodes) - const storeEdges = useCanvasStore((s) => s.graph.edges) - const nodesRef = useRef(nodes) const edgesRef = useRef(edges) nodesRef.current = nodes edgesRef.current = edges const [isSaving, setIsSaving] = useState(false) - const lastSavedSnapshotRef = useRef( + const [lastSavedSerialized, setLastSavedSerialized] = useState(() => JSON.stringify({ nodes: initialGraph.nodes, edges: initialGraph.edges }) ) - const serializedFromStore = useMemo( - () => JSON.stringify({ nodes: storeNodes, edges: storeEdges }), - [storeNodes, storeEdges] + const currentSerialized = useMemo( + () => JSON.stringify({ nodes, edges }), + [nodes, edges] ) - const isDirty = serializedFromStore !== lastSavedSnapshotRef.current + const isDirty = currentSerialized !== lastSavedSerialized const saveStatus: SaveStatus = isSaving ? 'saving' : isDirty ? 'unsaved' : 'saved' - const performSave = useCallback(() => { - if (!projectId) return - const current = JSON.stringify({ - nodes: nodesRef.current, - edges: edgesRef.current, - }) - if (current === lastSavedSnapshotRef.current) return - setIsSaving(true) - saveGraphToStorage(projectId, { - version: PROJECT_VERSION, - nodes: nodesRef.current, - edges: edgesRef.current, - }) - lastSavedSnapshotRef.current = current - setIsSaving(false) - }, [projectId]) - const save = useCallback(() => { if (!projectId) return + const snapshot = JSON.stringify({ + nodes: nodesRef.current, + edges: edgesRef.current, + }) setIsSaving(true) saveGraphToStorage(projectId, { version: PROJECT_VERSION, nodes: nodesRef.current, edges: edgesRef.current, }) - lastSavedSnapshotRef.current = JSON.stringify({ - nodes: nodesRef.current, - edges: edgesRef.current, - }) - setIsSaving(false) + const SAVING_DISPLAY_MS = 360 + setTimeout(() => { + setLastSavedSerialized(snapshot) + setIsSaving(false) + }, SAVING_DISPLAY_MS) }, [projectId]) - useEffect(() => { - if (!projectId) return - const id = setInterval(performSave, AUTOSAVE_INTERVAL_MS) - return () => clearInterval(id) - }, [projectId, performSave]) - return { ...result, save, saveStatus } } diff --git a/frontend/src/components/editor/CodeEditor.tsx b/frontend/src/components/editor/CodeEditor.tsx index 7b81986..3ecad9c 100644 --- a/frontend/src/components/editor/CodeEditor.tsx +++ b/frontend/src/components/editor/CodeEditor.tsx @@ -92,7 +92,7 @@ export function CodeEditor({ }, [value, textareaId]) return ( -
+
{children} @@ -96,7 +99,9 @@ export function BaseNode({ handleClassName="base-node-resize-handle nodrag nopan" /> )} - {!isFullscreenInstance && handles} + {!isFullscreenInstance && ( +
{handles}
+ )}
); } @@ -198,7 +203,7 @@ export function BaseNodeContent({ return (
); diff --git a/frontend/src/components/nodes/config/ConfigNode.tsx b/frontend/src/components/nodes/config/ConfigNode.tsx index 876e389..c9097ae 100644 --- a/frontend/src/components/nodes/config/ConfigNode.tsx +++ b/frontend/src/components/nodes/config/ConfigNode.tsx @@ -307,7 +307,7 @@ function ConfigNodeComponent({ id, data, width, height, selected }: Props) { />
-
} className="min-h-0 flex-1 w-full nodrag nopan overflow-auto border-t border-input" style={{ minHeight: editorHeight }}> +
} className="min-h-0 flex-1 w-full nodrag nopan overflow-hidden border-t border-input" style={{ minHeight: editorHeight }}>