"use client"; import { useCallback } from "react"; import { ReactFlow, ReactFlowProvider, Background, Controls, MiniMap, BackgroundVariant, Panel, } from "@xyflow/react"; import type { Node } from "@xyflow/react"; import { useGraphStore } from "../../stores/useGraphStore"; import { useAutoLayout } from "../../hooks/useAutoLayout"; import { bfsPath } from "../../lib/bfs-path"; import { BpmnActivityNode, BpmnSubprocessNode, BpmnStartEventNode, BpmnEndEventNode, BpmnTimerEventNode, BpmnMessageEventNode, BpmnGatewayNode, BpmnDataObjectNode, BpmnAnnotationNode, BpmnPoolNode, BpmnLaneNode, BpmnGroupNode, BpmnSequenceEdge, BpmnMessageEdge, BpmnAssociationEdge, } from "../../types/bpmn"; import { ErEntityNode } from "../../types/er/ErEntityNode"; import { ErRelationshipEdge } from "../../types/er/ErRelationshipEdge"; import { OrgchartPersonNode } from "../../types/orgchart/OrgchartPersonNode"; import { OrgchartHierarchyEdge } from "../../types/orgchart/OrgchartHierarchyEdge"; import { ArchServiceNode } from "../../types/architecture/ArchServiceNode"; import { ArchDatabaseNode } from "../../types/architecture/ArchDatabaseNode"; import { ArchQueueNode } from "../../types/architecture/ArchQueueNode"; import { ArchLoadBalancerNode } from "../../types/architecture/ArchLoadBalancerNode"; import { ArchExternalNode } from "../../types/architecture/ArchExternalNode"; import { ArchConnectionEdge } from "../../types/architecture/ArchConnectionEdge"; import { SeqParticipantNode } from "../../types/sequence/SeqParticipantNode"; import { SeqFragmentNode } from "../../types/sequence/SeqFragmentNode"; import { SeqSyncEdge } from "../../types/sequence/SeqSyncEdge"; import { SeqAsyncEdge } from "../../types/sequence/SeqAsyncEdge"; import { SeqReturnEdge } from "../../types/sequence/SeqReturnEdge"; import { FlowProcessNode } from "../../types/flowchart/FlowProcessNode"; import { FlowDecisionNode } from "../../types/flowchart/FlowDecisionNode"; import { FlowTerminalNode } from "../../types/flowchart/FlowTerminalNode"; import { FlowIoNode } from "../../types/flowchart/FlowIoNode"; import { FlowSubprocessNode } from "../../types/flowchart/FlowSubprocessNode"; import { FlowEdge } from "../../types/flowchart/FlowEdge"; const nodeTypes = { bpmnActivity: BpmnActivityNode, bpmnSubprocess: BpmnSubprocessNode, bpmnStartEvent: BpmnStartEventNode, bpmnEndEvent: BpmnEndEventNode, bpmnTimerEvent: BpmnTimerEventNode, bpmnMessageEvent: BpmnMessageEventNode, bpmnGateway: BpmnGatewayNode, bpmnDataObject: BpmnDataObjectNode, bpmnAnnotation: BpmnAnnotationNode, bpmnPool: BpmnPoolNode, bpmnLane: BpmnLaneNode, bpmnGroup: BpmnGroupNode, erEntity: ErEntityNode, orgchartPerson: OrgchartPersonNode, archService: ArchServiceNode, archDatabase: ArchDatabaseNode, archQueue: ArchQueueNode, archLoadBalancer: ArchLoadBalancerNode, archExternal: ArchExternalNode, seqParticipant: SeqParticipantNode, seqFragment: SeqFragmentNode, flowProcess: FlowProcessNode, flowDecision: FlowDecisionNode, flowTerminal: FlowTerminalNode, flowIo: FlowIoNode, flowSubprocess: FlowSubprocessNode, }; const edgeTypes = { bpmnSequence: BpmnSequenceEdge, bpmnMessage: BpmnMessageEdge, bpmnAssociation: BpmnAssociationEdge, erRelationship: ErRelationshipEdge, orgchartHierarchy: OrgchartHierarchyEdge, archConnection: ArchConnectionEdge, seqSync: SeqSyncEdge, seqAsync: SeqAsyncEdge, seqReturn: SeqReturnEdge, flowEdge: FlowEdge, }; /** Container node types that should not participate in BFS highlighting */ const CONTAINER_TYPES = new Set(["bpmnPool", "bpmnLane", "bpmnGroup", "seqFragment"]); function MarkerDefs() { return ( ); } function CanvasInner() { const nodes = useGraphStore((s) => s.nodes); const edges = useGraphStore((s) => s.edges); const onNodesChange = useGraphStore((s) => s.onNodesChange); const onEdgesChange = useGraphStore((s) => s.onEdgesChange); const onViewportChange = useGraphStore((s) => s.onViewportChange); const highlightedNodeId = useGraphStore((s) => s.highlightedNodeId); const setHighlightedNodeId = useGraphStore((s) => s.setHighlightedNodeId); const setNodes = useGraphStore((s) => s.setNodes); const setEdges = useGraphStore((s) => s.setEdges); const { isLayouting } = useAutoLayout(); const clearHighlight = useCallback(() => { if (!highlightedNodeId) return; setHighlightedNodeId(null); setNodes(nodes.map((n) => ({ ...n, className: undefined }))); setEdges(edges.map((e) => ({ ...e, className: undefined }))); }, [highlightedNodeId, nodes, edges, setHighlightedNodeId, setNodes, setEdges]); const handleNodeClick = useCallback( (_: React.MouseEvent, node: Node) => { // Skip container nodes (pools, lanes, groups) if (CONTAINER_TYPES.has(node.type ?? "")) return; const store = useGraphStore.getState(); // Toggle off if clicking the same node if (store.highlightedNodeId === node.id) { store.setHighlightedNodeId(null); store.setNodes( store.nodes.map((n) => ({ ...n, className: undefined })), ); store.setEdges( store.edges.map((e) => ({ ...e, className: undefined })), ); return; } // Compute BFS path from clicked node const graphEdges = store.edges .filter((e) => e.type !== "bpmnGroup") .map((e) => ({ from: e.source, to: e.target })); const { nodeSet, edgeSet } = bfsPath(node.id, graphEdges); // Apply highlight/dim classes store.setHighlightedNodeId(node.id); store.setNodes( store.nodes.map((n) => { if (CONTAINER_TYPES.has(n.type ?? "")) { return { ...n, className: undefined }; } return { ...n, className: nodeSet.has(n.id) ? "highlighted" : "dimmed", }; }), ); store.setEdges( store.edges.map((e) => ({ ...e, className: edgeSet.has(`${e.source}->${e.target}`) ? "highlighted" : "dimmed", })), ); }, [], ); return (