|
|
|
@ -98,10 +98,7 @@ const BOX_SELECTION_EXPAND_MIN_DISTANCE = 4; |
|
|
|
const MULTI_SELECTION_BOUNDS_PADDING = 40; |
|
|
|
const GROUP_NODE_Z_INDEX = 0; |
|
|
|
const DEFAULT_NODE_Z_INDEX = 1; |
|
|
|
const SELECTED_NODE_Z_INDEX = 10000; |
|
|
|
const DRAGGING_GROUP_Z_INDEX = 9999; |
|
|
|
const DRAGGING_GROUP_MEMBER_Z_INDEX = 10001; |
|
|
|
const DRAGGING_NODE_Z_INDEX = 10002; |
|
|
|
const INITIAL_ACTIVE_NODE_Z_INDEX = 100; |
|
|
|
|
|
|
|
type ScreenPoint = { |
|
|
|
x: number; |
|
|
|
@ -575,6 +572,10 @@ export function WorkflowCanvas() { |
|
|
|
const [isMiniMapOpen, setIsMiniMapOpen] = useState(false); |
|
|
|
const [zoomPercent, setZoomPercent] = useState(100); |
|
|
|
const [draggingGroupId, setDraggingGroupId] = useState<string | null>(null); |
|
|
|
const [activeNodeLayer, setActiveNodeLayer] = useState<{ nodeId: string | null; zIndex: number }>({ |
|
|
|
nodeId: null, |
|
|
|
zIndex: INITIAL_ACTIVE_NODE_Z_INDEX, |
|
|
|
}); |
|
|
|
const canvasGridColor = "var(--canvas-grid)"; |
|
|
|
const miniMapMaskColor = "var(--surface-overlay)"; |
|
|
|
const miniMapNodeColor = "var(--text-disabled)"; |
|
|
|
@ -587,6 +588,20 @@ export function WorkflowCanvas() { |
|
|
|
const gestureZoomStartRef = useRef<{ zoom: number } | null>(null); |
|
|
|
const [boxSelectionRect, setBoxSelectionRect] = useState<FlowRect | null>(null); |
|
|
|
|
|
|
|
const bringNodeToFront = useCallback((nodeId: string) => { |
|
|
|
setActiveNodeLayer((current) => ({ |
|
|
|
nodeId, |
|
|
|
zIndex: current.zIndex + 1, |
|
|
|
})); |
|
|
|
}, []); |
|
|
|
|
|
|
|
const resetActiveNodeLayer = useCallback(() => { |
|
|
|
setActiveNodeLayer((current) => ({ |
|
|
|
...current, |
|
|
|
nodeId: null, |
|
|
|
})); |
|
|
|
}, []); |
|
|
|
|
|
|
|
const onNodesChange = useCallback((changes: NodeChange<WorkflowNode>[]) => { |
|
|
|
if (!boxSelectionActiveRef.current) { |
|
|
|
storeOnNodesChange(changes); |
|
|
|
@ -878,6 +893,13 @@ export function WorkflowCanvas() { |
|
|
|
// Apply visual state that React Flow can turn into DOM classes and z-index.
|
|
|
|
const allNodes = useMemo(() => { |
|
|
|
const draggingGroupNodeId = draggingGroupId ? `group-node-${draggingGroupId}` : null; |
|
|
|
const activeNode = activeNodeLayer.nodeId |
|
|
|
? nodes.find((node) => node.id === activeNodeLayer.nodeId) |
|
|
|
: undefined; |
|
|
|
const activeGroupId = activeNode?.type === "group" |
|
|
|
? (activeNode.data as { groupId?: string }).groupId |
|
|
|
: undefined; |
|
|
|
const activeGroupNodeId = activeGroupId ? `group-node-${activeGroupId}` : null; |
|
|
|
|
|
|
|
return nodes.map((node) => { |
|
|
|
// Never dim Switch or ConditionalSwitch nodes themselves
|
|
|
|
@ -897,23 +919,29 @@ export function WorkflowCanvas() { |
|
|
|
// Preserve existing className if any, add/remove dimmed/skipped classes
|
|
|
|
const baseClass = (node.className || "").replace(/\bswitch-dimmed\b/g, "").replace(/\bnode-skipped\b/g, "").trim(); |
|
|
|
const newClass = extraClasses ? `${baseClass} ${extraClasses}`.trim() : baseClass; |
|
|
|
const zIndex = node.type === "group" |
|
|
|
? isDraggingGroupShell |
|
|
|
? DRAGGING_GROUP_Z_INDEX |
|
|
|
: GROUP_NODE_Z_INDEX |
|
|
|
: isDraggingGroupMember |
|
|
|
? DRAGGING_GROUP_MEMBER_Z_INDEX |
|
|
|
: node.dragging |
|
|
|
? DRAGGING_NODE_Z_INDEX |
|
|
|
: node.selected |
|
|
|
? SELECTED_NODE_Z_INDEX |
|
|
|
: DEFAULT_NODE_Z_INDEX; |
|
|
|
let zIndex = node.type === "group" ? GROUP_NODE_Z_INDEX : DEFAULT_NODE_Z_INDEX; |
|
|
|
|
|
|
|
if (activeGroupId && node.id === activeGroupNodeId) { |
|
|
|
zIndex = activeNodeLayer.zIndex; |
|
|
|
} else if (activeGroupId && node.type !== "group" && (node.groupId === activeGroupId || node.parentId === activeGroupNodeId)) { |
|
|
|
zIndex = activeNodeLayer.zIndex + 1; |
|
|
|
} else if (!activeGroupId && node.id === activeNodeLayer.nodeId) { |
|
|
|
zIndex = activeNodeLayer.zIndex; |
|
|
|
} |
|
|
|
|
|
|
|
if (isDraggingGroupShell) { |
|
|
|
zIndex = Math.max(zIndex, activeNodeLayer.zIndex); |
|
|
|
} else if (isDraggingGroupMember) { |
|
|
|
zIndex = Math.max(zIndex, activeNodeLayer.zIndex + 1); |
|
|
|
} else if (node.dragging) { |
|
|
|
zIndex = Math.max(zIndex, activeNodeLayer.zIndex + 1); |
|
|
|
} |
|
|
|
|
|
|
|
// Only create new node object if className changed
|
|
|
|
if (node.className === newClass && node.zIndex === zIndex) return node; |
|
|
|
return { ...node, className: newClass, zIndex }; |
|
|
|
}); |
|
|
|
}, [nodes, dimmedNodeIds, draggingGroupId, skippedNodeIds]); |
|
|
|
}, [nodes, activeNodeLayer, dimmedNodeIds, draggingGroupId, skippedNodeIds]); |
|
|
|
|
|
|
|
const absoluteNodePositions = useMemo(() => { |
|
|
|
const byId = new Map(allNodes.map((node) => [node.id, node])); |
|
|
|
@ -3049,9 +3077,13 @@ export function WorkflowCanvas() { |
|
|
|
onConnectEnd={handleConnectEnd} |
|
|
|
onMoveStart={() => { isPanningRef.current = true; setHoveredNodeId(null); document.documentElement.classList.add("canvas-interacting"); }} |
|
|
|
onMoveEnd={() => { isPanningRef.current = false; document.documentElement.classList.remove("canvas-interacting"); syncZoomPercent(); }} |
|
|
|
onNodeClick={(_event, node) => { |
|
|
|
bringNodeToFront(node.id); |
|
|
|
}} |
|
|
|
onNodeDragStart={(_event, node) => { |
|
|
|
isDraggingNodeRef.current = true; |
|
|
|
document.documentElement.classList.add("canvas-interacting"); |
|
|
|
bringNodeToFront(node.id); |
|
|
|
setDraggingGroupId(node.type === "group" ? (node.data as { groupId?: string }).groupId ?? null : null); |
|
|
|
}} |
|
|
|
onNodeDragStop={(event, node) => { |
|
|
|
@ -3060,6 +3092,7 @@ export function WorkflowCanvas() { |
|
|
|
setDraggingGroupId(null); |
|
|
|
handleNodeDragStop(event, node); |
|
|
|
}} |
|
|
|
onPaneClick={resetActiveNodeLayer} |
|
|
|
onPaneContextMenu={openCanvasContextMenu} |
|
|
|
onNodeContextMenu={openNodeContextMenu} |
|
|
|
onSelectionStart={handleSelectionStart} |
|
|
|
|