import { useLayoutEffect, type Dispatch, type MutableRefObject, type SetStateAction } from "react"; import type { Edge as RFEdge, Node as RFNode } from "@xyflow/react"; import type { Doc, Id } from "@/convex/_generated/dataModel"; import { buildIncomingCanvasFlowNodes, reconcileCanvasFlowEdges, reconcileCanvasFlowNodes, } from "./canvas-flow-reconciliation-helpers"; type PositionPin = { x: number; y: number }; type CanvasFlowReconciliationRefs = { nodesRef: MutableRefObject; edgesRef: MutableRefObject; deletingNodeIds: MutableRefObject>; convexNodeIdsSnapshotForEdgeCarryRef: MutableRefObject>; resolvedRealIdByClientRequestRef: MutableRefObject>>; pendingConnectionCreatesRef: MutableRefObject>; pendingLocalPositionUntilConvexMatchesRef: MutableRefObject< Map >; pendingLocalNodeDataUntilConvexMatchesRef: MutableRefObject>; preferLocalPositionNodeIdsRef: MutableRefObject>; isDragging: MutableRefObject; isResizing: MutableRefObject; }; export function useCanvasFlowReconciliation(args: { convexNodes: Doc<"nodes">[] | undefined; convexEdges: Doc<"edges">[] | undefined; storageUrlsById: Record | undefined; themeMode: "light" | "dark"; pendingRemovedEdgeIds: ReadonlySet; pendingMovePins: ReadonlyMap; setNodes: Dispatch>; setEdges: Dispatch>; refs: CanvasFlowReconciliationRefs; }) { const { convexEdges, convexNodes, storageUrlsById, themeMode, pendingRemovedEdgeIds, pendingMovePins, setNodes, setEdges, } = args; const { nodesRef, edgesRef, deletingNodeIds, convexNodeIdsSnapshotForEdgeCarryRef, resolvedRealIdByClientRequestRef, pendingConnectionCreatesRef, pendingLocalPositionUntilConvexMatchesRef, pendingLocalNodeDataUntilConvexMatchesRef, preferLocalPositionNodeIdsRef, isDragging, isResizing, } = args.refs; useLayoutEffect(() => { if (!convexEdges) return; setEdges((previousEdges) => { const reconciliation = reconcileCanvasFlowEdges({ previousEdges, convexEdges, convexNodes, previousConvexNodeIdsSnapshot: convexNodeIdsSnapshotForEdgeCarryRef.current, pendingRemovedEdgeIds, pendingConnectionCreateIds: pendingConnectionCreatesRef.current, resolvedRealIdByClientRequest: resolvedRealIdByClientRequestRef.current, localNodeIds: new Set(nodesRef.current.map((node) => node.id)), isAnyNodeDragging: isDragging.current || nodesRef.current.some((node) => Boolean((node as { dragging?: boolean }).dragging), ), colorMode: themeMode, }); resolvedRealIdByClientRequestRef.current = reconciliation.inferredRealIdByClientRequest; convexNodeIdsSnapshotForEdgeCarryRef.current = reconciliation.nextConvexNodeIdsSnapshot; for (const clientRequestId of reconciliation.settledPendingConnectionCreateIds) { pendingConnectionCreatesRef.current.delete(clientRequestId); } return reconciliation.edges; }); }, [ convexEdges, convexNodes, pendingRemovedEdgeIds, setEdges, themeMode, convexNodeIdsSnapshotForEdgeCarryRef, edgesRef, isDragging, nodesRef, pendingConnectionCreatesRef, resolvedRealIdByClientRequestRef, ]); useLayoutEffect(() => { if (!convexNodes || isResizing.current) return; setNodes((previousNodes) => { const anyRfNodeDragging = previousNodes.some((node) => Boolean((node as { dragging?: boolean }).dragging), ); if (isDragging.current || anyRfNodeDragging) { return previousNodes; } const incomingNodes = buildIncomingCanvasFlowNodes({ convexNodes, storageUrlsById, previousNodes, edges: edgesRef.current, }); const reconciliation = reconcileCanvasFlowNodes({ previousNodes, incomingNodes, convexNodes, deletingNodeIds: deletingNodeIds.current, resolvedRealIdByClientRequest: resolvedRealIdByClientRequestRef.current, pendingConnectionCreateIds: pendingConnectionCreatesRef.current, preferLocalPositionNodeIds: preferLocalPositionNodeIdsRef.current, pendingLocalPositionPins: pendingLocalPositionUntilConvexMatchesRef.current, pendingLocalNodeDataPins: pendingLocalNodeDataUntilConvexMatchesRef.current, pendingMovePins, }); resolvedRealIdByClientRequestRef.current = reconciliation.inferredRealIdByClientRequest; pendingLocalPositionUntilConvexMatchesRef.current = reconciliation.nextPendingLocalPositionPins; pendingLocalNodeDataUntilConvexMatchesRef.current = reconciliation.nextPendingLocalNodeDataPins; for (const nodeId of reconciliation.clearedPreferLocalPositionNodeIds) { preferLocalPositionNodeIdsRef.current.delete(nodeId); } return reconciliation.nodes; }); }, [ convexNodes, edgesRef, pendingMovePins, setNodes, storageUrlsById, deletingNodeIds, isDragging, isResizing, pendingConnectionCreatesRef, pendingLocalPositionUntilConvexMatchesRef, pendingLocalNodeDataUntilConvexMatchesRef, preferLocalPositionNodeIdsRef, resolvedRealIdByClientRequestRef, ]); }