|
|
import React, { useCallback, useEffect } from 'react';
|
|
|
import {
|
|
|
applyNodeChanges,
|
|
|
applyEdgeChanges,
|
|
|
addEdge,
|
|
|
reconnectEdge,
|
|
|
Node,
|
|
|
Edge,
|
|
|
} from '@xyflow/react';
|
|
|
import { Message } from '@arco-design/web-react';
|
|
|
import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData';
|
|
|
import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
|
|
|
import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode';
|
|
|
import { updateCanvasDataMap } from '@/store/ideContainer';
|
|
|
import { projectFlowHandle } from '@/pages/flowEditor/utils/projectFlowHandle';
|
|
|
import { appFLowHandle } from '@/pages/flowEditor/utils/appFlowhandle';
|
|
|
import { getCurrentAppKey } from '@/features/workflow/runtime/flowRuntime';
|
|
|
import {
|
|
|
ensureNodeTypeRegistered,
|
|
|
resolveNodeComponent,
|
|
|
} from '@/features/workflow/registry/nodeRegistry';
|
|
|
import {
|
|
|
dispatchFlowSnapshot,
|
|
|
dispatchFlowSnapshotAsync,
|
|
|
} from '@/features/workflow/operations/snapshot';
|
|
|
import {
|
|
|
createLoopGroupEdge,
|
|
|
createLoopInsertConnectionEdges,
|
|
|
createLoopNodePair,
|
|
|
} from '@/features/workflow/operations/loopOperations';
|
|
|
import {
|
|
|
buildInsertedNodeEdges,
|
|
|
resolveInsertedNodeHandles,
|
|
|
} from '@/features/workflow/operations/edgeOperations';
|
|
|
import {
|
|
|
buildWorkflowConnectionEdge,
|
|
|
validateWorkflowConnection,
|
|
|
} from '@/features/workflow/operations/connectionOperations';
|
|
|
import {
|
|
|
buildCopiedFlowData,
|
|
|
buildPastedFlowData,
|
|
|
buildPastedSingleNode,
|
|
|
} from '@/features/workflow/operations/clipboardOperations';
|
|
|
import {
|
|
|
isLoopBoundaryNode,
|
|
|
removeNodesAndConnectedEdges,
|
|
|
resolveDeletedNodesWithLoopPairs,
|
|
|
} from '@/features/workflow/operations/deleteOperations';
|
|
|
import {
|
|
|
shouldPersistCanvas,
|
|
|
shouldUseCachedCanvas,
|
|
|
} from '@/features/workflow/operations/canvasCache';
|
|
|
import {
|
|
|
buildRuntimeNode,
|
|
|
resolveNodeDefinition,
|
|
|
} from '@/features/workflow/operations/nodeOnboarding';
|
|
|
import { saveWorkflowData } from '@/features/workflow/persistence/flowPersistence';
|
|
|
import {
|
|
|
pauseWorkflow,
|
|
|
rerunWorkflow,
|
|
|
runWorkflow,
|
|
|
} from '@/features/workflow/runtime/runtimeActions';
|
|
|
|
|
|
import { Dispatch } from 'redux';
|
|
|
import store from '@/store';
|
|
|
|
|
|
export const useFlowCallbacks = (
|
|
|
nodes: Node[],
|
|
|
setNodes: React.Dispatch<React.SetStateAction<Node[]>>,
|
|
|
edges: Edge[],
|
|
|
setEdges: React.Dispatch<React.SetStateAction<Edge[]>>,
|
|
|
useDefault: boolean,
|
|
|
reactFlowInstance: any,
|
|
|
canvasDataMap: any,
|
|
|
dispatch: Dispatch<any>,
|
|
|
updateCanvasDataMapDebounced: (
|
|
|
dispatch: Dispatch<any>,
|
|
|
canvasDataMap: any,
|
|
|
id: string,
|
|
|
nodes: Node[],
|
|
|
edges: Edge[]
|
|
|
) => void,
|
|
|
initialData: any,
|
|
|
historyTimeoutRef: React.MutableRefObject<NodeJS.Timeout | null>,
|
|
|
setHistoryInitialized: React.Dispatch<React.SetStateAction<boolean>>,
|
|
|
editingNode: Node | null,
|
|
|
setEditingNode: React.Dispatch<React.SetStateAction<Node | null>>,
|
|
|
setIsEditModalOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
|
|
edgeForNodeAdd: Edge | null,
|
|
|
setEdgeForNodeAdd: React.Dispatch<React.SetStateAction<Edge | null>>,
|
|
|
positionForNodeAdd: { x: number; y: number } | null,
|
|
|
setPositionForNodeAdd: React.Dispatch<
|
|
|
React.SetStateAction<{ x: number; y: number } | null>
|
|
|
>,
|
|
|
setIsDelete: React.Dispatch<React.SetStateAction<boolean>>
|
|
|
) => {
|
|
|
const { getGuidelines, clearGuidelines } = useAlignmentGuidelines();
|
|
|
|
|
|
// 辅助函数:获取当前应用/子流程的唯一标识符
|
|
|
const getCurrentFlowAppKey = useCallback(() => {
|
|
|
const { currentAppData } = store.getState().ideContainer;
|
|
|
return getCurrentAppKey(currentAppData) || initialData?.appId;
|
|
|
}, [initialData]);
|
|
|
|
|
|
// region 画布操作
|
|
|
// 节点变更处理,添加防抖机制
|
|
|
const onNodesChange = useCallback(
|
|
|
(changes: any) => {
|
|
|
// 深度克隆节点数组以避免修改冻结的对象
|
|
|
const clonedNodes = JSON.parse(JSON.stringify(nodes));
|
|
|
const newNodes = applyNodeChanges(changes, clonedNodes);
|
|
|
setNodes(newNodes);
|
|
|
// 如果需要在节点变化时执行某些操作,可以在这里添加
|
|
|
|
|
|
// 只有当变化是节点位置变化时才不立即记录历史
|
|
|
const isPositionChange = changes.some(
|
|
|
(change: any) => change.type === 'position' && change.dragging === false
|
|
|
);
|
|
|
|
|
|
// 如果是位置变化结束或者不是位置变化,则记录历史
|
|
|
if (
|
|
|
isPositionChange ||
|
|
|
!changes.some((change: any) => change.type === 'position')
|
|
|
) {
|
|
|
// 清除之前的定时器
|
|
|
if (historyTimeoutRef.current) {
|
|
|
clearTimeout(historyTimeoutRef.current);
|
|
|
}
|
|
|
|
|
|
// 设置新的定时器,延迟记录历史记录
|
|
|
historyTimeoutRef.current = dispatchFlowSnapshotAsync(
|
|
|
{ nodes: [...newNodes], edges: [...edges] },
|
|
|
100
|
|
|
);
|
|
|
}
|
|
|
},
|
|
|
[nodes, edges]
|
|
|
);
|
|
|
|
|
|
// 边变更处理
|
|
|
const onEdgesChange = useCallback(
|
|
|
(changes: any) => {
|
|
|
const newEdges = applyEdgeChanges(changes, edges);
|
|
|
setEdges(newEdges);
|
|
|
// 如果需要在边变化时执行某些操作,可以在这里添加
|
|
|
|
|
|
// 边的变化立即记录历史
|
|
|
dispatchFlowSnapshot({ nodes: [...nodes], edges: [...newEdges] });
|
|
|
},
|
|
|
[edges, nodes]
|
|
|
);
|
|
|
// 节点删除处理
|
|
|
const onNodesDelete = useCallback((deletedNodes: any) => {
|
|
|
setIsDelete(true);
|
|
|
}, []);
|
|
|
// 边连接处理
|
|
|
const onConnect = useCallback(
|
|
|
(params: any) => {
|
|
|
const validation = validateWorkflowConnection(nodes, params);
|
|
|
if (!validation.isValid) {
|
|
|
if (validation.message) console.warn(validation.message);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
setEdges((edgesSnapshot: Edge[]) => {
|
|
|
const result = buildWorkflowConnectionEdge(
|
|
|
nodes,
|
|
|
params,
|
|
|
validation.lineType || 'data'
|
|
|
);
|
|
|
if (!result?.edge) {
|
|
|
if (result?.message) {
|
|
|
console.warn(result.message);
|
|
|
Message.warning(result.message);
|
|
|
}
|
|
|
return edgesSnapshot;
|
|
|
}
|
|
|
|
|
|
const newEdges = addEdge(result.edge, edgesSnapshot);
|
|
|
dispatchFlowSnapshotAsync({ nodes: [...nodes], edges: [...newEdges] });
|
|
|
|
|
|
return newEdges;
|
|
|
});
|
|
|
},
|
|
|
[nodes]
|
|
|
);
|
|
|
// 边重新连接处理
|
|
|
const onReconnect = useCallback(
|
|
|
(oldEdge: Edge, newConnection: any) => {
|
|
|
const validation = validateWorkflowConnection(nodes, newConnection);
|
|
|
if (!validation.isValid) {
|
|
|
if (validation.message) console.warn(validation.message);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
setEdges((els) => reconnectEdge(oldEdge, newConnection, els));
|
|
|
},
|
|
|
[nodes]
|
|
|
);
|
|
|
// 拖动处理
|
|
|
const onDragOver = useCallback((event: React.DragEvent) => {
|
|
|
event.preventDefault();
|
|
|
event.dataTransfer.dropEffect = 'move';
|
|
|
}, []);
|
|
|
|
|
|
const getNodeDefinition = useCallback(
|
|
|
(nodeType: string, fallbackNode?: any) =>
|
|
|
resolveNodeDefinition(localNodeData, nodeType, fallbackNode),
|
|
|
[]
|
|
|
);
|
|
|
|
|
|
const clearAddNodeContext = useCallback(() => {
|
|
|
setEdgeForNodeAdd(null);
|
|
|
setPositionForNodeAdd(null);
|
|
|
}, []);
|
|
|
|
|
|
// 添加循环节点及其开始和结束节点
|
|
|
const addLoopNodeWithStartEnd = useCallback(
|
|
|
(position: { x: number; y: number }) => {
|
|
|
const { loopStartNode, loopEndNode } = createLoopNodePair(position);
|
|
|
|
|
|
// 创建连接边(连接循环开始和结束节点的顶部连接点)
|
|
|
const newEdges = [createLoopGroupEdge(loopStartNode.id, loopEndNode.id)];
|
|
|
|
|
|
// 将未定义的节点动态追加进nodeTypes
|
|
|
ensureNodeTypeRegistered('LOOP', '循环', LoopNode);
|
|
|
|
|
|
setNodes((nds: Node[]) => {
|
|
|
const newNodes = [...nds, loopStartNode, loopEndNode];
|
|
|
|
|
|
// 添加节点后记录历史
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...newNodes],
|
|
|
edges: [...edges, ...newEdges],
|
|
|
});
|
|
|
|
|
|
return newNodes;
|
|
|
});
|
|
|
|
|
|
setEdges((eds: Edge[]) => {
|
|
|
const updatedEdges = [...eds, ...newEdges];
|
|
|
|
|
|
// 添加边后记录历史
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...nodes, loopStartNode, loopEndNode],
|
|
|
edges: [...updatedEdges],
|
|
|
});
|
|
|
|
|
|
return updatedEdges;
|
|
|
});
|
|
|
},
|
|
|
[nodes, edges]
|
|
|
);
|
|
|
|
|
|
const addNodeOnCanvas = useCallback(
|
|
|
(
|
|
|
nodeType: string,
|
|
|
nodeDefinition: any,
|
|
|
position: { x: number; y: number }
|
|
|
) => {
|
|
|
if (nodeType === 'LOOP') {
|
|
|
addLoopNodeWithStartEnd(position);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const { eventList } = store.getState().ideContainer;
|
|
|
const newNode = buildRuntimeNode({
|
|
|
nodeType,
|
|
|
nodeDefinition,
|
|
|
position,
|
|
|
eventList,
|
|
|
eventIdMode: 'eventIdOptional',
|
|
|
});
|
|
|
|
|
|
setNodes((nds: Node[]) => {
|
|
|
const newNodes = nds.concat(newNode);
|
|
|
|
|
|
dispatchFlowSnapshotAsync({ nodes: [...newNodes], edges: [...edges] });
|
|
|
|
|
|
return newNodes;
|
|
|
});
|
|
|
},
|
|
|
[addLoopNodeWithStartEnd, edges]
|
|
|
);
|
|
|
|
|
|
// 侧边栏节点实例
|
|
|
const onDrop = useCallback(
|
|
|
(event: React.DragEvent) => {
|
|
|
event.preventDefault();
|
|
|
|
|
|
if (!reactFlowInstance) return;
|
|
|
|
|
|
const callBack = event.dataTransfer.getData('application/reactflow');
|
|
|
const nodeData = JSON.parse(callBack);
|
|
|
if (typeof nodeData.nodeType === 'undefined' || !nodeData.nodeType) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const position = reactFlowInstance.screenToFlowPosition({
|
|
|
x: event.clientX,
|
|
|
y: event.clientY,
|
|
|
});
|
|
|
|
|
|
const nodeDefinition = getNodeDefinition(nodeData.nodeType, nodeData);
|
|
|
if (!nodeDefinition) return;
|
|
|
|
|
|
addNodeOnCanvas(nodeData.nodeType, nodeDefinition, position);
|
|
|
},
|
|
|
[reactFlowInstance, getNodeDefinition, addNodeOnCanvas]
|
|
|
);
|
|
|
// 节点拖拽处理
|
|
|
const onNodeDrag = useCallback(
|
|
|
(_: any, node: Node) => {
|
|
|
// 获取对齐线
|
|
|
getGuidelines(node, nodes);
|
|
|
},
|
|
|
[nodes, getGuidelines]
|
|
|
);
|
|
|
// 节点拖拽结束处理
|
|
|
const onNodeDragStop = useCallback(() => {
|
|
|
// 清除对齐线
|
|
|
clearGuidelines();
|
|
|
}, [clearGuidelines]);
|
|
|
// endregion
|
|
|
|
|
|
// region 画布数据处理
|
|
|
// 初始化画布数据
|
|
|
const initializeCanvasData = useCallback(() => {
|
|
|
const appKey = getCurrentFlowAppKey();
|
|
|
const cachedCanvas = appKey ? canvasDataMap[appKey] : null;
|
|
|
if (shouldUseCachedCanvas({ cachedCanvas, initialData, useDefault })) {
|
|
|
const { edges, nodes } = cachedCanvas;
|
|
|
setNodes(nodes);
|
|
|
setEdges(edges);
|
|
|
} else {
|
|
|
// 首次进入
|
|
|
if (useDefault)
|
|
|
projectFlowHandle(
|
|
|
initialData,
|
|
|
useDefault,
|
|
|
setNodes,
|
|
|
setEdges,
|
|
|
dispatch,
|
|
|
canvasDataMap
|
|
|
);
|
|
|
else appFLowHandle(initialData, useDefault, setNodes, setEdges, dispatch);
|
|
|
}
|
|
|
|
|
|
// 标记历史记录已初始化
|
|
|
setHistoryInitialized(true);
|
|
|
}, [initialData, useDefault, canvasDataMap, getCurrentFlowAppKey]);
|
|
|
// 实时更新 canvasDataMap
|
|
|
const updateCanvasDataMapEffect = useCallback(() => {
|
|
|
const appKey = getCurrentFlowAppKey();
|
|
|
const { appRuntimeData } = store.getState().ideContainer;
|
|
|
const isCurrentAppRunning = Boolean(
|
|
|
appKey && appRuntimeData[appKey]?.isRunning
|
|
|
);
|
|
|
|
|
|
if (
|
|
|
appKey &&
|
|
|
shouldPersistCanvas({ nodes, edges, isRunning: isCurrentAppRunning })
|
|
|
) {
|
|
|
updateCanvasDataMapDebounced(
|
|
|
dispatch,
|
|
|
canvasDataMap,
|
|
|
appKey,
|
|
|
nodes,
|
|
|
edges
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// 清理函数,在组件卸载时取消防抖
|
|
|
return () => {
|
|
|
// 取消防抖函数
|
|
|
};
|
|
|
}, [nodes, edges, dispatch, canvasDataMap, getCurrentFlowAppKey]);
|
|
|
// 关闭编辑弹窗
|
|
|
const closeEditModal = useCallback(() => {
|
|
|
setIsEditModalOpen(false);
|
|
|
setEditingNode(null);
|
|
|
}, []);
|
|
|
// 保存节点编辑
|
|
|
const saveNodeEdit = useCallback(
|
|
|
(updatedData: any) => {
|
|
|
console.log('updatedData:', updatedData);
|
|
|
const updatedNodes = nodes.map((node) => {
|
|
|
if (node.id === editingNode?.id) {
|
|
|
return {
|
|
|
...node,
|
|
|
data: { ...node.data, ...updatedData },
|
|
|
};
|
|
|
}
|
|
|
return node;
|
|
|
});
|
|
|
|
|
|
setNodes(updatedNodes);
|
|
|
closeEditModal();
|
|
|
},
|
|
|
[nodes, editingNode, closeEditModal]
|
|
|
);
|
|
|
// 编辑节点
|
|
|
const editNode = useCallback((node: Node) => {
|
|
|
setEditingNode(node);
|
|
|
setIsEditModalOpen(true);
|
|
|
}, []);
|
|
|
// 编辑边
|
|
|
const editEdge = useCallback((edge: Edge) => {
|
|
|
// 这里可以实现边编辑逻辑
|
|
|
console.log('编辑边:', edge);
|
|
|
}, []);
|
|
|
// 复制节点(支持多节点和多边)
|
|
|
const copyNode = useCallback(
|
|
|
(node: Node) => {
|
|
|
const copiedData = buildCopiedFlowData(
|
|
|
node,
|
|
|
nodes,
|
|
|
edges,
|
|
|
initialData?.appId
|
|
|
);
|
|
|
if (!copiedData) {
|
|
|
console.warn('没有可复制的节点(开始和结束节点不能复制)');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
localStorage.setItem('copiedFlowData', JSON.stringify(copiedData));
|
|
|
console.log(
|
|
|
`已复制 ${copiedData.nodes.length} 个节点和 ${copiedData.edges.length} 条边`
|
|
|
);
|
|
|
},
|
|
|
[nodes, edges, initialData?.appId]
|
|
|
);
|
|
|
|
|
|
// 粘贴节点(支持多节点和多边)
|
|
|
const pasteNode = useCallback(
|
|
|
(position: { x: number; y: number }) => {
|
|
|
// 尝试读取新格式的复制数据(多节点+边)
|
|
|
const copiedFlowDataStr = localStorage.getItem('copiedFlowData');
|
|
|
|
|
|
// 兼容旧格式的单节点复制
|
|
|
const copiedNodeStr = localStorage.getItem('copiedNode');
|
|
|
|
|
|
if (!copiedFlowDataStr && !copiedNodeStr) {
|
|
|
console.warn('没有找到复制的节点数据');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
// 处理新格式(多节点+边)
|
|
|
if (copiedFlowDataStr) {
|
|
|
const copiedData = JSON.parse(copiedFlowDataStr);
|
|
|
|
|
|
// 检查是否为同一应用
|
|
|
if (
|
|
|
copiedData.appId &&
|
|
|
initialData?.appId &&
|
|
|
copiedData.appId !== initialData.appId
|
|
|
) {
|
|
|
console.warn('不能在不同应用之间粘贴节点');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const pastedData = buildPastedFlowData(copiedData, position);
|
|
|
if (!pastedData) {
|
|
|
console.warn('没有可粘贴的节点');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
pastedData.nodes.forEach((newNode: Node) => {
|
|
|
ensureNodeTypeRegistered(
|
|
|
newNode.type,
|
|
|
(newNode.data?.title as string) || newNode.type,
|
|
|
resolveNodeComponent(newNode.type)
|
|
|
);
|
|
|
});
|
|
|
|
|
|
// 更新节点和边
|
|
|
setNodes((nds: Node[]) => {
|
|
|
const updatedNodes = [...nds, ...pastedData.nodes];
|
|
|
|
|
|
// 添加节点后记录历史
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...updatedNodes],
|
|
|
edges: [...edges, ...pastedData.edges],
|
|
|
});
|
|
|
|
|
|
return updatedNodes;
|
|
|
});
|
|
|
|
|
|
setEdges((eds: Edge[]) => {
|
|
|
const updatedEdges = [...eds, ...pastedData.edges];
|
|
|
return updatedEdges;
|
|
|
});
|
|
|
|
|
|
console.log(
|
|
|
`已粘贴 ${pastedData.nodes.length} 个节点和 ${pastedData.edges.length} 条边`
|
|
|
);
|
|
|
}
|
|
|
// 处理旧格式(单节点)- 保持向后兼容
|
|
|
else if (copiedNodeStr) {
|
|
|
const copiedNode = JSON.parse(copiedNodeStr);
|
|
|
|
|
|
// 检查是否为同一应用
|
|
|
if (
|
|
|
copiedNode.appId &&
|
|
|
initialData?.appId &&
|
|
|
copiedNode.appId !== initialData.appId
|
|
|
) {
|
|
|
console.warn('不能在不同应用之间粘贴节点');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 创建新节点,更新ID和位置
|
|
|
const newNode = buildPastedSingleNode(copiedNode, position);
|
|
|
|
|
|
// 特殊处理循环节点
|
|
|
if (copiedNode.type === 'LOOP') {
|
|
|
setNodes((nds: Node[]) => {
|
|
|
const newNodes = [...nds, newNode];
|
|
|
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...newNodes],
|
|
|
edges: [...edges],
|
|
|
});
|
|
|
|
|
|
return newNodes;
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 注册节点类型
|
|
|
ensureNodeTypeRegistered(
|
|
|
newNode.type,
|
|
|
(newNode.data?.title as string) || newNode.type,
|
|
|
resolveNodeComponent(newNode.type)
|
|
|
);
|
|
|
|
|
|
setNodes((nds: Node[]) => {
|
|
|
const newNodes = [...nds, newNode];
|
|
|
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...newNodes],
|
|
|
edges: [...edges],
|
|
|
});
|
|
|
|
|
|
return newNodes;
|
|
|
});
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('粘贴节点时出错:', error);
|
|
|
}
|
|
|
},
|
|
|
[edges, initialData?.appId, nodes]
|
|
|
);
|
|
|
// endregion
|
|
|
|
|
|
// region 节点/边操作
|
|
|
// 删除节点函数
|
|
|
const deleteNode = useCallback(
|
|
|
(node: Node) => {
|
|
|
// 在应用编排模式下(useDefault为false)不允许删除节点
|
|
|
if (!useDefault) {
|
|
|
console.warn('在应用编排模式下不允许删除节点');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 开始和结束节点不允许删除
|
|
|
if (node.type === 'start' || node.type === 'end') {
|
|
|
console.warn('开始和结束节点不允许删除');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const nodesToRemove = isLoopBoundaryNode(node)
|
|
|
? resolveDeletedNodesWithLoopPairs([node], nodes)
|
|
|
: [node];
|
|
|
const nextGraph = removeNodesAndConnectedEdges(
|
|
|
nodes,
|
|
|
edges,
|
|
|
nodesToRemove
|
|
|
);
|
|
|
|
|
|
setNodes(nextGraph.nodes);
|
|
|
setEdges(nextGraph.edges);
|
|
|
|
|
|
// 删除节点后记录历史
|
|
|
setTimeout(() => {
|
|
|
dispatchFlowSnapshot({
|
|
|
nodes: [...nextGraph.nodes],
|
|
|
edges: [...nextGraph.edges],
|
|
|
});
|
|
|
}, 0);
|
|
|
},
|
|
|
[nodes, edges, useDefault]
|
|
|
);
|
|
|
// 删除边函数
|
|
|
const deleteEdge = useCallback(
|
|
|
(edge: Edge) => {
|
|
|
// 获取当前应用的运行状态
|
|
|
const { appRuntimeData, currentAppData } = store.getState().ideContainer;
|
|
|
const currentAppIsRunning =
|
|
|
currentAppData?.id && appRuntimeData[currentAppData.id]
|
|
|
? appRuntimeData[currentAppData.id].isRunning
|
|
|
: false;
|
|
|
|
|
|
// 在运行时禁止删除边
|
|
|
if (currentAppIsRunning) {
|
|
|
console.warn('在运行时不允许删除边');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edge.id));
|
|
|
|
|
|
// 删除边后记录历史
|
|
|
setTimeout(() => {
|
|
|
dispatchFlowSnapshot({
|
|
|
nodes: [...nodes],
|
|
|
edges: [...edges.filter((e) => e.id !== edge.id)],
|
|
|
});
|
|
|
}, 0);
|
|
|
},
|
|
|
[nodes, edges]
|
|
|
);
|
|
|
|
|
|
const getEdgeMidpointPosition = useCallback(
|
|
|
(sourceId: string, targetId: string) => {
|
|
|
const sourceNode = nodes.find((n) => n.id === sourceId);
|
|
|
const targetNode = nodes.find((n) => n.id === targetId);
|
|
|
if (!sourceNode || !targetNode) return null;
|
|
|
|
|
|
return {
|
|
|
x: (sourceNode.position.x + targetNode.position.x) / 2,
|
|
|
y: (sourceNode.position.y + targetNode.position.y) / 2,
|
|
|
};
|
|
|
},
|
|
|
[nodes]
|
|
|
);
|
|
|
|
|
|
// 在边上添加节点的具体实现
|
|
|
const addNodeOnEdge = useCallback(
|
|
|
(nodeType: string, node: any) => {
|
|
|
if (!edgeForNodeAdd || !reactFlowInstance) return;
|
|
|
|
|
|
// 查找节点定义
|
|
|
const nodeDefinition = getNodeDefinition(nodeType, node);
|
|
|
if (!nodeDefinition) return;
|
|
|
|
|
|
// 特殊处理循环节点,添加开始和结束节点
|
|
|
if (nodeType === 'LOOP') {
|
|
|
const position = getEdgeMidpointPosition(
|
|
|
edgeForNodeAdd.source,
|
|
|
edgeForNodeAdd.target
|
|
|
);
|
|
|
if (!position) return;
|
|
|
|
|
|
const { loopStartNode, loopEndNode } = createLoopNodePair(position);
|
|
|
|
|
|
// 创建连接边(连接循环开始和结束节点的顶部连接点)
|
|
|
const groupEdge = createLoopGroupEdge(loopStartNode.id, loopEndNode.id);
|
|
|
|
|
|
// 创建连接原有节点到循环开始节点,以及循环结束节点到目标节点的边
|
|
|
const connectionEdges = createLoopInsertConnectionEdges({
|
|
|
sourceId: edgeForNodeAdd.source,
|
|
|
sourceHandle: edgeForNodeAdd.sourceHandle,
|
|
|
targetId: edgeForNodeAdd.target,
|
|
|
targetHandle: edgeForNodeAdd.targetHandle,
|
|
|
loopStartId: loopStartNode.id,
|
|
|
loopEndId: loopEndNode.id,
|
|
|
});
|
|
|
|
|
|
// 将未定义的节点动态追加进nodeTypes
|
|
|
ensureNodeTypeRegistered('LOOP', '循环', LoopNode);
|
|
|
|
|
|
// 更新节点和边
|
|
|
setNodes((nds: Node[]) => {
|
|
|
const newNodes = [...nds, loopStartNode, loopEndNode];
|
|
|
|
|
|
// 添加节点后记录历史
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...newNodes],
|
|
|
edges: [...edges, groupEdge, ...connectionEdges],
|
|
|
});
|
|
|
|
|
|
return newNodes;
|
|
|
});
|
|
|
|
|
|
setEdges((eds: Edge[]) => {
|
|
|
// 删除原来的边,添加新的边
|
|
|
const updatedEdges = [
|
|
|
...eds.filter((e) => e.id !== edgeForNodeAdd.id),
|
|
|
groupEdge,
|
|
|
...connectionEdges,
|
|
|
];
|
|
|
|
|
|
// 添加边后记录历史
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...nodes, loopStartNode, loopEndNode],
|
|
|
edges: [...updatedEdges],
|
|
|
});
|
|
|
|
|
|
return updatedEdges;
|
|
|
});
|
|
|
|
|
|
// 关闭菜单
|
|
|
clearAddNodeContext();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const position = getEdgeMidpointPosition(
|
|
|
edgeForNodeAdd.source,
|
|
|
edgeForNodeAdd.target
|
|
|
);
|
|
|
if (!position) return;
|
|
|
|
|
|
const { eventList } = store.getState().ideContainer;
|
|
|
const newNode = buildRuntimeNode({
|
|
|
nodeType,
|
|
|
nodeDefinition,
|
|
|
position,
|
|
|
eventList,
|
|
|
eventIdMode: 'id',
|
|
|
});
|
|
|
|
|
|
// 添加新节点
|
|
|
setNodes((nds: Node[]) => [...nds, newNode]);
|
|
|
|
|
|
// 删除旧边
|
|
|
setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edgeForNodeAdd.id));
|
|
|
|
|
|
const { sourceHandle, targetHandle } =
|
|
|
resolveInsertedNodeHandles(newNode);
|
|
|
|
|
|
const newEdges = buildInsertedNodeEdges({
|
|
|
baseEdges: edges,
|
|
|
removedEdgeId: edgeForNodeAdd.id,
|
|
|
sourceId: edgeForNodeAdd.source,
|
|
|
sourceHandle: edgeForNodeAdd.sourceHandle,
|
|
|
targetId: edgeForNodeAdd.target,
|
|
|
targetHandle: edgeForNodeAdd.targetHandle,
|
|
|
insertedNodeId: newNode.id,
|
|
|
insertedNodeSourceHandle: sourceHandle,
|
|
|
insertedNodeTargetHandle: targetHandle,
|
|
|
});
|
|
|
|
|
|
setEdges(newEdges);
|
|
|
|
|
|
// 关闭菜单
|
|
|
clearAddNodeContext();
|
|
|
|
|
|
// 添加节点后记录历史
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...nodes, newNode],
|
|
|
edges: [...newEdges],
|
|
|
});
|
|
|
},
|
|
|
[
|
|
|
edgeForNodeAdd,
|
|
|
reactFlowInstance,
|
|
|
edges,
|
|
|
addLoopNodeWithStartEnd,
|
|
|
getEdgeMidpointPosition,
|
|
|
nodes,
|
|
|
clearAddNodeContext,
|
|
|
]
|
|
|
);
|
|
|
// 在画布上添加节点
|
|
|
const addNodeOnPane = useCallback(
|
|
|
(nodeType: string, position: { x: number; y: number }, node?: any) => {
|
|
|
if (!reactFlowInstance) return;
|
|
|
|
|
|
// 查找节点定义
|
|
|
const nodeDefinition = getNodeDefinition(nodeType, node);
|
|
|
if (!nodeDefinition) return;
|
|
|
|
|
|
addNodeOnCanvas(nodeType, nodeDefinition, position);
|
|
|
},
|
|
|
[reactFlowInstance, getNodeDefinition, addNodeOnCanvas]
|
|
|
);
|
|
|
// 处理添加节点的统一方法
|
|
|
const handleAddNode = useCallback(
|
|
|
(nodeType: string, node: any) => {
|
|
|
// 如果是通过边添加节点
|
|
|
if (edgeForNodeAdd) {
|
|
|
addNodeOnEdge(nodeType, node);
|
|
|
}
|
|
|
// 如果是通过画布添加节点
|
|
|
else if (positionForNodeAdd) {
|
|
|
addNodeOnPane(nodeType, positionForNodeAdd, node);
|
|
|
}
|
|
|
|
|
|
// 清除状态
|
|
|
clearAddNodeContext();
|
|
|
},
|
|
|
[
|
|
|
edgeForNodeAdd,
|
|
|
positionForNodeAdd,
|
|
|
addNodeOnEdge,
|
|
|
addNodeOnPane,
|
|
|
clearAddNodeContext,
|
|
|
]
|
|
|
);
|
|
|
// endregion
|
|
|
|
|
|
const saveFlowDataToServer = useCallback(async () => {
|
|
|
await saveWorkflowData({
|
|
|
nodes,
|
|
|
edges,
|
|
|
useDefault,
|
|
|
initialData,
|
|
|
canvasDataMap,
|
|
|
dispatch,
|
|
|
setNodes,
|
|
|
setEdges,
|
|
|
});
|
|
|
}, [nodes, edges, useDefault, initialData, canvasDataMap, dispatch]);
|
|
|
|
|
|
// 运行处理函数
|
|
|
const handleRun = useCallback(
|
|
|
async (running: boolean) => {
|
|
|
await runWorkflow({
|
|
|
running,
|
|
|
dispatch,
|
|
|
getCurrentFlowAppKey,
|
|
|
setEdges,
|
|
|
});
|
|
|
},
|
|
|
[getCurrentFlowAppKey]
|
|
|
);
|
|
|
|
|
|
// 暂停/恢复应用
|
|
|
const handlePause = useCallback(
|
|
|
async (isPaused: boolean) => {
|
|
|
await pauseWorkflow({ isPaused, dispatch, getCurrentFlowAppKey });
|
|
|
},
|
|
|
[getCurrentFlowAppKey]
|
|
|
);
|
|
|
|
|
|
// 重跑应用
|
|
|
const handleReRun = useCallback(async () => {
|
|
|
await rerunWorkflow({ dispatch, getCurrentFlowAppKey });
|
|
|
}, [getCurrentFlowAppKey]);
|
|
|
|
|
|
return {
|
|
|
// Event handlers
|
|
|
onNodesChange,
|
|
|
onEdgesChange,
|
|
|
onConnect,
|
|
|
onReconnect,
|
|
|
onDragOver,
|
|
|
onDrop,
|
|
|
onNodeDrag,
|
|
|
onNodeDragStop,
|
|
|
onNodesDelete,
|
|
|
|
|
|
// Menu handlers
|
|
|
closeEditModal,
|
|
|
saveNodeEdit,
|
|
|
deleteNode,
|
|
|
deleteEdge,
|
|
|
editNode,
|
|
|
editEdge,
|
|
|
copyNode,
|
|
|
pasteNode, // 添加粘贴节点功能
|
|
|
|
|
|
// Node operations
|
|
|
addNodeOnEdge,
|
|
|
addNodeOnPane,
|
|
|
handleAddNode,
|
|
|
|
|
|
// Initialization
|
|
|
initializeCanvasData,
|
|
|
updateCanvasDataMapEffect,
|
|
|
|
|
|
// Actions
|
|
|
saveFlowDataToServer,
|
|
|
handleRun,
|
|
|
handlePause,
|
|
|
handleReRun,
|
|
|
};
|
|
|
};
|
|
|
export default useFlowCallbacks;
|