You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
flow-playform-react/src/hooks/useFlowCallbacks.ts

881 lines
25 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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;