import React, { useEffect, useMemo } from 'react'; import { ReactFlow, Background, Panel, SelectionMode, ConnectionLineType, Node, Edge, OnNodesChange, OnEdgesChange, OnConnect, OnReconnect } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import CustomEdge from './components/customEdge'; import CustomConnectionLine from './components/customConnectionLine'; import NodeContextMenu from './components/nodeContextMenu'; import EdgeContextMenu from './components/edgeContextMenu'; import PaneContextMenu from './components/paneContextMenu'; import NodeEditModal from './components/nodeEditModal'; import AddNodeMenu from './components/addNodeMenu'; import ActionBar from './components/actionBar'; import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines'; import { useHistory } from './components/historyContext'; import { NodeTypes } from '@xyflow/react'; import { useSelector } from 'react-redux'; const edgeTypes = { custom: CustomEdge }; interface FlowEditorMainProps { nodes: Node[]; edges: Edge[]; nodeTypes: NodeTypes; setNodes: React.Dispatch>; setEdges: React.Dispatch>; useDefault: boolean; reactFlowInstance: any; reactFlowWrapper: React.RefObject; menu: any; setMenu: React.Dispatch>; editingNode: Node | null; setEditingNode: React.Dispatch>; isEditModalOpen: boolean; setIsEditModalOpen: React.Dispatch>; isDelete: boolean; setIsDelete: React.Dispatch>; edgeForNodeAdd: Edge | null; setEdgeForNodeAdd: React.Dispatch>; positionForNodeAdd: { x: number, y: number } | null; setPositionForNodeAdd: React.Dispatch>; isRunning: boolean; initialData: any; canvasDataMap: any; // Callbacks onNodesChange: OnNodesChange; onEdgesChange: OnEdgesChange; onConnect: OnConnect; onReconnect: OnReconnect; onDragOver: (event: React.DragEvent) => void; onDrop: (event: React.DragEvent) => void; onNodeDrag: (event: React.MouseEvent, node: Node) => void; onNodeDragStop: () => void; onNodeContextMenu: (event: React.MouseEvent, node: Node) => void; onNodeDoubleClick: (event: React.MouseEvent, node: Node) => void; onEdgeContextMenu: (event: React.MouseEvent, edge: Edge) => void; onPaneContextMenu: (event: React.MouseEvent) => void; onPaneClick: () => void; closeEditModal: () => void; saveNodeEdit: (updatedData: any) => void; deleteNode: (node: Node) => void; deleteEdge: (edge: Edge) => void; editNode: (node: Node) => void; editEdge: (edge: Edge) => void; copyNode: (node: Node) => void; addNodeOnEdge: (nodeType: string, node: any) => void; addNodeOnPane: (nodeType: string, position: { x: number, y: number }, node?: any) => void; handleAddNode: (nodeType: string, node: any) => void; saveFlowDataToServer: () => void; handleRun: (running: boolean) => void; } const FlowEditorMain: React.FC = (props) => { const { nodes, edges, nodeTypes, setNodes, setEdges, useDefault, reactFlowInstance, reactFlowWrapper, menu, setMenu, editingNode, setEditingNode, isEditModalOpen, setIsEditModalOpen, isDelete, setIsDelete, edgeForNodeAdd, setEdgeForNodeAdd, positionForNodeAdd, setPositionForNodeAdd, isRunning, initialData, canvasDataMap, onNodesChange, onEdgesChange, onConnect, onReconnect, onDragOver, onDrop, onNodeDrag, onNodeDragStop, onNodeContextMenu, onNodeDoubleClick, onEdgeContextMenu, onPaneContextMenu, onPaneClick, closeEditModal, saveNodeEdit, deleteNode, deleteEdge, editNode, editEdge, copyNode, addNodeOnEdge, addNodeOnPane, handleAddNode, saveFlowDataToServer, handleRun } = props; const { getGuidelines, clearGuidelines, AlignmentGuides } = useAlignmentGuidelines(); const { undo, redo, canUndo, canRedo } = useHistory(); const reactFlowId = useMemo(() => new Date().getTime().toString(), []); // 用于存储隐藏的节点ID const [hiddenNodes, setHiddenNodes] = React.useState>(new Set()); // 监听自定义事件以隐藏/显示节点 useEffect(() => { const handleToggleNodeVisibility = (event: CustomEvent) => { const { appId, isVisible } = event.detail; if (isVisible) { // 显示节点 - 从隐藏节点集合中移除 setHiddenNodes(prev => { const newSet = new Set(prev); newSet.delete(appId); return newSet; }); } else { // 隐藏节点 - 添加到隐藏节点集合 setHiddenNodes(prev => new Set(prev).add(appId)); } }; document.addEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener); return () => { document.removeEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener); }; }, []); // 从Redux store中获取当前应用的运行状态 const { appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id] ? appRuntimeData[currentAppData.id].isRunning : false; // 在应用编排模式下(useDefault为false)禁用删除功能 const isDeleteDisabled = currentAppIsRunning; // 监听自定义事件以隐藏/显示节点 useEffect(() => { const handleToggleNodeVisibility = (event: CustomEvent) => { const { appId, isVisible } = event.detail; if (isVisible) { // 显示节点 - 从隐藏节点集合中移除 setHiddenNodes(prev => { const newSet = new Set(prev); newSet.delete(appId); return newSet; }); } else { // 隐藏节点 - 添加到隐藏节点集合 setHiddenNodes(prev => new Set(prev).add(appId)); } }; document.addEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener); return () => { document.removeEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener); }; }, []); // 监听键盘事件实现快捷键 useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Ctrl+Z 撤销 if (e.ctrlKey && e.key === 'z' && !e.shiftKey && canUndo) { e.preventDefault(); undo(); } // Ctrl+Shift+Z 重做 if (e.ctrlKey && e.shiftKey && e.key === 'Z' && canRedo) { e.preventDefault(); redo(); } // Ctrl+Y 重做 if (e.ctrlKey && e.key === 'y' && canRedo) { e.preventDefault(); redo(); } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [undo, redo, canUndo, canRedo]); // 监听节点和边的变化以拍摄快照 useEffect(() => { // 获取 HistoryProvider 中的 takeSnapshot 方法 const event = new CustomEvent('takeSnapshot', { detail: { nodes: [...nodes], edges: [...edges] } }); document.dispatchEvent(event); }, [nodes, edges]); return (
e.preventDefault()} // 添加点击事件处理器,用于关闭添加节点菜单 onClick={() => { if (edgeForNodeAdd || positionForNodeAdd) { setEdgeForNodeAdd(null); setPositionForNodeAdd(null); } }}> { // 检查节点是否应该被隐藏 const isHidden = hiddenNodes.has(node.id); // 应用透明度样式 const style = isHidden ? { opacity: 0.3 } : {}; return { ...node, draggable: !currentAppIsRunning, style: { ...node.style, ...style } }; })} edges={edges.map(edge => { // 检查边连接的节点是否被隐藏 const isSourceHidden = hiddenNodes.has(edge.source); const isTargetHidden = hiddenNodes.has(edge.target); // 如果源节点或目标节点被隐藏,则边也应用透明度 const style = (isSourceHidden || isTargetHidden) ? { opacity: 0.3 } : {}; // 更新边的数据,确保选择框也应用透明度 return { ...edge, style: { ...edge.style, ...style }, data: { ...edge.data, ...(isSourceHidden || isTargetHidden ? { hidden: true, opacity: 0.3 } : { opacity: 1 }) } }; })} nodeTypes={nodeTypes} edgeTypes={edgeTypes} snapToGrid={true} snapGrid={[2, 2]} nodesConnectable={!currentAppIsRunning} // 运行时禁用节点连接 nodesDraggable={!currentAppIsRunning} // 运行时禁用节点拖拽 elementsSelectable={!currentAppIsRunning} // 运行时禁用元素选择 connectOnClick={!currentAppIsRunning} // 运行时禁用点击连接 disableKeyboardA11y={currentAppIsRunning} // 运行时禁用键盘交互 onBeforeDelete={async ({ nodes, edges }) => { // 在应用编排模式下(useDefault为false)只允许删除边,不允许删除节点 if (!useDefault && nodes.length > 0) { console.warn('在应用编排模式下不允许删除节点'); return false; // 阻止删除节点操作 } // 检查是否有开始或结束节点 const hasStartOrEndNode = nodes.some(node => node.type === 'start' || node.type === 'end'); if (hasStartOrEndNode) { console.warn('开始和结束节点不允许删除'); return false; // 阻止删除操作 } // 检查是否有循环节点(这里只是检查,实际删除逻辑在onNodesDelete中处理) const loopNodes = nodes.filter(node => node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END' ); // 允许删除操作继续进行 return !currentAppIsRunning; // 在运行时禁止删除任何元素 }} onNodesDelete={(deleted) => { // 在应用编排模式下(useDefault为false)不允许删除节点 if (!useDefault) { console.warn('在应用编排模式下不允许删除节点'); return; } // 如果在运行时,禁止删除 if (currentAppIsRunning) { return; } // 检查是否有循环节点 const loopNodes = deleted.filter(node => node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END' ); if (loopNodes.length > 0) { // 处理循环节点删除 let nodesToRemove = [...deleted]; // 为每个循环节点找到其配对节点 loopNodes.forEach(loopNode => { const component = loopNode.data?.component as { customDef?: string } | undefined; if (loopNode.data?.type === 'LOOP_START' && component?.customDef) { try { const customDef = JSON.parse(component.customDef); const relatedNodeId = customDef.loopEndNodeId; // 添加关联的结束节点到删除列表 const relatedNode = nodes.find(n => n.id === relatedNodeId); if (relatedNode) { nodesToRemove.push(relatedNode); } } catch (e) { console.error('解析循环开始节点数据失败:', e); } } else if (loopNode.data?.type === 'LOOP_END' && component?.customDef) { try { const customDef = JSON.parse(component.customDef); const relatedNodeId = customDef.loopStartNodeId; // 添加关联的开始节点到删除列表 const relatedNode = nodes.find(n => n.id === relatedNodeId); if (relatedNode) { nodesToRemove.push(relatedNode); } } catch (e) { console.error('解析循环结束节点数据失败:', e); } } }); // 去重 nodesToRemove = nodesToRemove.filter((node, index, self) => index === self.findIndex(n => n.id === node.id) ); // 删除所有相关节点和边 setNodes((nds) => nds.filter((n) => !nodesToRemove.find((d) => d.id === n.id))); // 删除与这些节点相关的所有边 const nodeIdsToRemove = nodesToRemove.map(node => node.id); setEdges((eds) => eds.filter((e) => !nodeIdsToRemove.includes(e.source) && !nodeIdsToRemove.includes(e.target) )); } else { // 普通节点删除 setNodes((nds) => nds.filter((n) => !deleted.find((d) => d.id === n.id))); } setIsEditModalOpen(false); }} onNodesChange={!currentAppIsRunning ? onNodesChange : undefined} // 运行时禁用节点变更 onEdgesChange={!currentAppIsRunning ? onEdgesChange : undefined} // 运行时禁用边变更 onConnect={!currentAppIsRunning ? onConnect : undefined} // 运行时禁用连接 onReconnect={!currentAppIsRunning ? onReconnect : undefined} // 运行时禁用重新连接 onDragOver={!currentAppIsRunning ? onDragOver : undefined} // 运行时禁用拖拽 onDrop={!currentAppIsRunning ? onDrop : undefined} // 运行时禁用放置 onNodeDrag={!currentAppIsRunning ? onNodeDrag : undefined} // 运行时禁用节点拖拽 connectionLineType={ConnectionLineType.SmoothStep} connectionLineComponent={CustomConnectionLine} onNodeDragStop={!currentAppIsRunning ? onNodeDragStop : undefined} // 运行时禁用节点拖拽停止 onNodeContextMenu={(!currentAppIsRunning && useDefault) ? onNodeContextMenu : undefined} // 运行时或应用编排模式下禁用节点上下文菜单 onNodeDoubleClick={!currentAppIsRunning ? onNodeDoubleClick : undefined} // 运行时禁用节点双击 onEdgeContextMenu={(!currentAppIsRunning && useDefault) ? onEdgeContextMenu : undefined} // 运行时或应用编排模式下禁用边上下文菜单 onPaneClick={!currentAppIsRunning ? onPaneClick : undefined} // 运行时禁用面板点击 onPaneContextMenu={(!currentAppIsRunning && useDefault) ? onPaneContextMenu : undefined} // 运行时或应用编排模式下禁用面板上下文菜单 onEdgeMouseEnter={(_event, edge) => { setEdges((eds) => eds.map(e => { if (e.id === edge.id) { return { ...e, data: { ...e.data, hovered: true } }; } return e; })); }} onEdgeMouseLeave={(_event, edge) => { setEdges((eds) => eds.map(e => { if (e.id === edge.id) { return { ...e, data: { ...e.data, hovered: false } }; } return e; })); }} fitView selectionOnDrag={!currentAppIsRunning} // 运行时禁用拖拽选择 selectionMode={!currentAppIsRunning ? SelectionMode.Partial : undefined} // 运行时禁用选择模式 > {/*节点右键上下文 - 仅在默认模式且非运行时显示*/} {!currentAppIsRunning && useDefault && menu && menu.type === 'node' && (
n.id === menu.id)!} onDelete={deleteNode} onEdit={editNode} onCopy={copyNode} onCloseMenu={setMenu} onCloseOpenModal={setIsEditModalOpen} />
)} {/*边右键上下文 - 在非运行时显示,应用编排模式下也允许*/} {!currentAppIsRunning && menu && menu.type === 'edge' && (
e.id === menu.id)!} onDelete={deleteEdge} onEdit={editEdge} onAddNode={(edge) => { setEdgeForNodeAdd(edge); setIsEditModalOpen(false); setMenu(null); // 关闭上下文菜单 }} />
)} {/*画布右键上下文 - 仅在默认模式且非运行时显示*/} {!currentAppIsRunning && useDefault && menu && menu.type === 'pane' && (
{ addNodeOnPane(nodeType, position, node); setIsEditModalOpen(false); setMenu(null); // 关闭上下文菜单 }} />
)} {/*节点点击/节点编辑上下文*/} {/*统一的添加节点菜单 - 仅在默认模式且非运行时显示*/} {!currentAppIsRunning && useDefault && (edgeForNodeAdd || positionForNodeAdd) && (
e.stopPropagation()} > { handleAddNode(nodeType, node); // 关闭菜单 setEdgeForNodeAdd(null); setPositionForNodeAdd(null); }} position={positionForNodeAdd || undefined} edgeId={edgeForNodeAdd?.id} />
)}
); }; export default FlowEditorMain;