feat(canvas): accelerate local previews and harden edge flows

This commit is contained in:
2026-04-05 17:28:43 +02:00
parent 451ab0b986
commit de37b63b2b
29 changed files with 2751 additions and 358 deletions

View File

@@ -1,17 +1,37 @@
"use client";
import { createContext, useContext, useMemo, type ReactNode } from "react";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from "react";
import {
buildGraphSnapshot,
type CanvasGraphEdgeLike,
type CanvasGraphNodeDataOverrides,
type CanvasGraphNodeLike,
type CanvasGraphSnapshot,
pruneCanvasGraphNodeDataOverrides,
} from "@/lib/canvas-render-preview";
type CanvasGraphContextValue = CanvasGraphSnapshot;
type CanvasGraphContextValue = CanvasGraphSnapshot & {
previewNodeDataOverrides: CanvasGraphNodeDataOverrides;
};
type CanvasGraphPreviewOverridesContextValue = {
setPreviewNodeDataOverride: (nodeId: string, data: unknown) => void;
clearPreviewNodeDataOverride: (nodeId: string) => void;
clearPreviewNodeDataOverrides: () => void;
};
const CanvasGraphContext = createContext<CanvasGraphContextValue | null>(null);
const CanvasGraphPreviewOverridesContext =
createContext<CanvasGraphPreviewOverridesContextValue | null>(null);
export function CanvasGraphProvider({
nodes,
@@ -22,9 +42,88 @@ export function CanvasGraphProvider({
edges: readonly CanvasGraphEdgeLike[];
children: ReactNode;
}) {
const value = useMemo(() => buildGraphSnapshot(nodes, edges), [edges, nodes]);
const [previewNodeDataOverrides, setPreviewNodeDataOverrides] =
useState<CanvasGraphNodeDataOverrides>(() => new Map());
return <CanvasGraphContext.Provider value={value}>{children}</CanvasGraphContext.Provider>;
const setPreviewNodeDataOverride = useCallback((nodeId: string, data: unknown) => {
setPreviewNodeDataOverrides((previous) => {
if (previous.has(nodeId) && Object.is(previous.get(nodeId), data)) {
return previous;
}
const next = new Map(previous);
next.set(nodeId, data);
return next;
});
}, []);
const clearPreviewNodeDataOverride = useCallback((nodeId: string) => {
setPreviewNodeDataOverrides((previous) => {
if (!previous.has(nodeId)) {
return previous;
}
const next = new Map(previous);
next.delete(nodeId);
return next;
});
}, []);
const clearPreviewNodeDataOverrides = useCallback(() => {
setPreviewNodeDataOverrides((previous) => {
if (previous.size === 0) {
return previous;
}
return new Map();
});
}, []);
const prunedPreviewNodeDataOverrides = useMemo(
() => pruneCanvasGraphNodeDataOverrides(nodes, previewNodeDataOverrides),
[nodes, previewNodeDataOverrides],
);
useEffect(() => {
if (prunedPreviewNodeDataOverrides !== previewNodeDataOverrides) {
setPreviewNodeDataOverrides(prunedPreviewNodeDataOverrides);
}
}, [previewNodeDataOverrides, prunedPreviewNodeDataOverrides]);
const graph = useMemo(
() =>
buildGraphSnapshot(nodes, edges, {
nodeDataOverrides: prunedPreviewNodeDataOverrides,
}),
[edges, nodes, prunedPreviewNodeDataOverrides],
);
const value = useMemo(
() => ({
...graph,
previewNodeDataOverrides: prunedPreviewNodeDataOverrides,
}),
[graph, prunedPreviewNodeDataOverrides],
);
const previewOverridesValue = useMemo(
() => ({
setPreviewNodeDataOverride,
clearPreviewNodeDataOverride,
clearPreviewNodeDataOverrides,
}),
[
clearPreviewNodeDataOverride,
clearPreviewNodeDataOverrides,
setPreviewNodeDataOverride,
],
);
return (
<CanvasGraphPreviewOverridesContext.Provider value={previewOverridesValue}>
<CanvasGraphContext.Provider value={value}>{children}</CanvasGraphContext.Provider>
</CanvasGraphPreviewOverridesContext.Provider>
);
}
export function useCanvasGraph(): CanvasGraphContextValue {
@@ -35,3 +134,12 @@ export function useCanvasGraph(): CanvasGraphContextValue {
return context;
}
export function useCanvasGraphPreviewOverrides(): CanvasGraphPreviewOverridesContextValue {
const context = useContext(CanvasGraphPreviewOverridesContext);
if (!context) {
throw new Error("useCanvasGraphPreviewOverrides must be used within CanvasGraphProvider");
}
return context;
}