|
|
import React, { useCallback, useEffect } from 'react';
|
|
|
import {
|
|
|
applyNodeChanges,
|
|
|
applyEdgeChanges,
|
|
|
addEdge,
|
|
|
reconnectEdge,
|
|
|
Node,
|
|
|
Edge,
|
|
|
} from '@xyflow/react';
|
|
|
import { getAppInfoNew, setMainFlowNew, setSubFlowNew } from '@/api/appRes';
|
|
|
import { Message } from '@arco-design/web-react';
|
|
|
import {
|
|
|
convertFlowData,
|
|
|
reverseConvertFlowData,
|
|
|
revertFlowData,
|
|
|
} from '@/utils/convertFlowData';
|
|
|
import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData';
|
|
|
import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
|
|
|
import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode';
|
|
|
import {
|
|
|
updateCanvasDataMap,
|
|
|
resetNodeStatus,
|
|
|
updateIsRunning,
|
|
|
updateIsPaused,
|
|
|
updateEventListOld,
|
|
|
addRuntimeLog,
|
|
|
clearRuntimeLogs,
|
|
|
updateRuntimeId,
|
|
|
updateFlowData,
|
|
|
} from '@/store/ideContainer';
|
|
|
import {
|
|
|
validateAllNodes,
|
|
|
showValidationErrors,
|
|
|
validateAllEdges,
|
|
|
} from '@/components/FlowEditor/nodeEditors/validators/nodeValidators';
|
|
|
import { getHandleType, validateDataType } from '@/utils/flowCommon';
|
|
|
import { projectFlowHandle } from '@/pages/flowEditor/utils/projectFlowHandle';
|
|
|
import { appFLowHandle } from '@/pages/flowEditor/utils/appFlowhandle';
|
|
|
import {
|
|
|
handelEventNodeList,
|
|
|
updateEvent,
|
|
|
upDatePublish,
|
|
|
} from '@/pages/flowEditor/utils/common';
|
|
|
import { getCurrentAppKey } from '@/utils/flow/runtime';
|
|
|
import {
|
|
|
ensureNodeTypeRegistered,
|
|
|
resolveNodeComponent,
|
|
|
} from '@/utils/flow/nodeRegistry';
|
|
|
import {
|
|
|
dispatchFlowSnapshot,
|
|
|
dispatchFlowSnapshotAsync,
|
|
|
} from '@/utils/flow/snapshot';
|
|
|
import {
|
|
|
createLoopGroupEdge,
|
|
|
createLoopInsertConnectionEdges,
|
|
|
createLoopNodePair,
|
|
|
} from '@/utils/flow/loopFactory';
|
|
|
import {
|
|
|
buildInsertedNodeEdges,
|
|
|
resolveInsertedNodeHandles,
|
|
|
} from '@/utils/flow/edgeInsertionFactory';
|
|
|
import {
|
|
|
buildRuntimeNode,
|
|
|
resolveNodeDefinition,
|
|
|
} from '@/utils/flow/nodeOnboarding';
|
|
|
import {
|
|
|
shouldPersistCanvas,
|
|
|
shouldUseCachedCanvas,
|
|
|
} from '@/utils/flow/canvasCache';
|
|
|
|
|
|
import { Dispatch } from 'redux';
|
|
|
import {
|
|
|
getAppListBySceneId,
|
|
|
runMainFlow,
|
|
|
runSubFlow,
|
|
|
stopApp,
|
|
|
pauseApp,
|
|
|
resumeApp,
|
|
|
reRunApp,
|
|
|
} from '@/api/apps';
|
|
|
import store from '@/store';
|
|
|
import {
|
|
|
updateAppEvent,
|
|
|
updateAppEventChannel,
|
|
|
updateAppFlowData,
|
|
|
} from '@/api/appEvent';
|
|
|
import { getUrlParams, sleep } from '@/utils/common';
|
|
|
import {
|
|
|
queryEventItemBySceneIdOld,
|
|
|
deleteEventSub,
|
|
|
deleteEventPub,
|
|
|
} from '@/api/event';
|
|
|
|
|
|
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]);
|
|
|
|
|
|
const refreshAppList = useCallback(() => {
|
|
|
document.dispatchEvent(new CustomEvent('refreshAppList'));
|
|
|
}, []);
|
|
|
|
|
|
// 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 sourceNode = nodes.find((node) => node.id === params.source);
|
|
|
const targetNode = nodes.find((node) => node.id === params.target);
|
|
|
|
|
|
// 如果找不到节点,不创建连接
|
|
|
if (!sourceNode || !targetNode) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 不允许链接到自己的节点上
|
|
|
if (sourceNode.id === targetNode.id) {
|
|
|
console.warn("不允许自旋链接");
|
|
|
return;
|
|
|
}
|
|
|
// 获取源节点和目标节点的参数信息
|
|
|
const sourceParams: any = sourceNode.data?.parameters || {};
|
|
|
const targetParams: any = targetNode.data?.parameters || {};
|
|
|
|
|
|
// 获取源handle和目标handle的类型 (api或data)
|
|
|
const sourceHandleType = getHandleType(params.sourceHandle, sourceParams);
|
|
|
const targetHandleType = getHandleType(params.targetHandle, targetParams);
|
|
|
|
|
|
// 验证连接类型是否匹配 (api只能连api, data只能连data)
|
|
|
if (sourceHandleType !== targetHandleType) {
|
|
|
console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 验证数据类型是否匹配
|
|
|
if (
|
|
|
!validateDataType(
|
|
|
sourceNode,
|
|
|
targetNode,
|
|
|
params.sourceHandle,
|
|
|
params.targetHandle
|
|
|
)
|
|
|
) {
|
|
|
console.warn('数据类型不匹配');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 如果验证通过,创建连接
|
|
|
setEdges((edgesSnapshot: Edge[]) => {
|
|
|
// 创建带有事件信息的连接
|
|
|
const edgeParams = { ...params, type: 'custom' };
|
|
|
|
|
|
// 添加lineType字段,用于区分API连接和数据连接
|
|
|
edgeParams.data = {
|
|
|
...edgeParams.data,
|
|
|
lineType: sourceHandleType, // 'api' 或 'data'
|
|
|
};
|
|
|
|
|
|
// 对于数据类型的边,需要额外验证dataIns和dataOuts中的数据类型是否一致
|
|
|
if (sourceHandleType === 'data') {
|
|
|
// 查找源节点的dataOuts中对应的数据
|
|
|
const sourceDataOut = (sourceParams.dataOuts || []).find(
|
|
|
(dataOut: any) =>
|
|
|
dataOut.name === params.sourceHandle ||
|
|
|
dataOut.id === params.sourceHandle
|
|
|
);
|
|
|
|
|
|
// 查找目标节点的dataIns中对应的数据
|
|
|
const targetDataIn = (targetParams.dataIns || []).find(
|
|
|
(dataIn: any) =>
|
|
|
dataIn.name === params.targetHandle ||
|
|
|
dataIn.id === params.targetHandle
|
|
|
);
|
|
|
|
|
|
// 验证数据类型是否一致
|
|
|
if (
|
|
|
sourceDataOut &&
|
|
|
targetDataIn &&
|
|
|
sourceDataOut.dataType !== targetDataIn.dataType
|
|
|
) {
|
|
|
console.warn(
|
|
|
'数据类型不匹配,源节点数据类型:',
|
|
|
sourceDataOut.dataType,
|
|
|
'目标节点数据类型:',
|
|
|
targetDataIn.dataType
|
|
|
);
|
|
|
Message.warning(
|
|
|
`数据类型不匹配,源节点数据类型: ${sourceDataOut.dataType},目标节点数据类型: ${targetDataIn.dataType}`
|
|
|
);
|
|
|
return edgesSnapshot; // 不创建连接
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 检查源节点和目标节点是否都有事件信息
|
|
|
const sourceApi = (sourceParams.apiOuts || []).find(
|
|
|
(api: any) =>
|
|
|
(api?.eventId || api.name || api.id) === params.sourceHandle
|
|
|
);
|
|
|
const targetApi = (targetParams.apiIns || []).find(
|
|
|
(api: any) =>
|
|
|
(api?.eventId || api.name || api.id) === params.targetHandle
|
|
|
);
|
|
|
|
|
|
// 如果源节点有事件topic信息
|
|
|
if (sourceApi && sourceApi.topic) {
|
|
|
// 如果目标节点的topic是**empty**或没有topic,则使用源节点的事件信息
|
|
|
if (
|
|
|
!targetApi ||
|
|
|
!targetApi.topic ||
|
|
|
targetApi.topic.includes('**empty**')
|
|
|
) {
|
|
|
edgeParams.data = {
|
|
|
...edgeParams.data,
|
|
|
lineType: 'api',
|
|
|
displayData: {
|
|
|
name: sourceApi.eventName,
|
|
|
eventId: sourceApi.eventId,
|
|
|
topic: sourceApi.topic,
|
|
|
},
|
|
|
};
|
|
|
}
|
|
|
// 如果两个节点都有非empty的topic,则以源节点为准
|
|
|
else if (
|
|
|
sourceApi.topic &&
|
|
|
targetApi.topic &&
|
|
|
!sourceApi.topic.includes('**empty**') &&
|
|
|
!targetApi.topic.includes('**empty**')
|
|
|
) {
|
|
|
edgeParams.data = {
|
|
|
...edgeParams.data,
|
|
|
lineType: 'api',
|
|
|
displayData: {
|
|
|
name: sourceApi.eventName,
|
|
|
eventId: sourceApi.eventId,
|
|
|
topic: sourceApi.topic,
|
|
|
},
|
|
|
};
|
|
|
}
|
|
|
}
|
|
|
// 如果源节点没有事件信息,但目标节点有
|
|
|
else if (
|
|
|
targetApi &&
|
|
|
targetApi.topic &&
|
|
|
!targetApi.topic.includes('**empty**')
|
|
|
) {
|
|
|
edgeParams.data = {
|
|
|
...edgeParams.data,
|
|
|
lineType: 'api',
|
|
|
displayData: {
|
|
|
name: targetApi.eventName,
|
|
|
eventId: targetApi.eventId,
|
|
|
topic: targetApi.topic,
|
|
|
},
|
|
|
};
|
|
|
}
|
|
|
|
|
|
const newEdges = addEdge(edgeParams, edgesSnapshot);
|
|
|
|
|
|
// 连接建立后记录历史
|
|
|
dispatchFlowSnapshotAsync({ nodes: [...nodes], edges: [...newEdges] });
|
|
|
|
|
|
return newEdges;
|
|
|
});
|
|
|
},
|
|
|
[nodes]
|
|
|
);
|
|
|
// 边重新连接处理
|
|
|
const onReconnect = useCallback(
|
|
|
(oldEdge: Edge, newConnection: any) => {
|
|
|
// 获取源节点和目标节点
|
|
|
const sourceNode = nodes.find((node) => node.id === newConnection.source);
|
|
|
const targetNode = nodes.find((node) => node.id === newConnection.target);
|
|
|
|
|
|
// 如果找不到节点,不创建连接
|
|
|
if (!sourceNode || !targetNode) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 获取源节点和目标节点的参数信息
|
|
|
const sourceParams = sourceNode.data?.parameters || {};
|
|
|
const targetParams = targetNode.data?.parameters || {};
|
|
|
|
|
|
// 获取源handle和目标handle的类型 (api或data)
|
|
|
const sourceHandleType = getHandleType(
|
|
|
newConnection.sourceHandle,
|
|
|
sourceParams
|
|
|
);
|
|
|
const targetHandleType = getHandleType(
|
|
|
newConnection.targetHandle,
|
|
|
targetParams
|
|
|
);
|
|
|
|
|
|
// 验证连接类型是否匹配 (api只能连api, data只能连data)
|
|
|
if (sourceHandleType !== targetHandleType) {
|
|
|
console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 验证数据类型是否匹配
|
|
|
if (
|
|
|
!validateDataType(
|
|
|
sourceNode,
|
|
|
targetNode,
|
|
|
newConnection.sourceHandle,
|
|
|
newConnection.targetHandle
|
|
|
)
|
|
|
) {
|
|
|
console.warn('数据类型不匹配');
|
|
|
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 =
|
|
|
appKey && appRuntimeData[appKey]?.isRunning;
|
|
|
|
|
|
if (
|
|
|
appKey &&
|
|
|
!isCurrentAppRunning &&
|
|
|
shouldPersistCanvas({ nodes, edges })
|
|
|
) {
|
|
|
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 selectedNodes = nodes.filter((n) => n.selected || n.id === node.id);
|
|
|
|
|
|
// 过滤掉开始和结束节点
|
|
|
const nodesToCopy = selectedNodes.filter(
|
|
|
(n) => n.type !== 'start' && n.type !== 'end'
|
|
|
);
|
|
|
|
|
|
if (nodesToCopy.length === 0) {
|
|
|
console.warn('没有可复制的节点(开始和结束节点不能复制)');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 获取这些节点之间的边
|
|
|
const nodeIds = new Set(nodesToCopy.map((n) => n.id));
|
|
|
const edgesToCopy = edges.filter(
|
|
|
(e) => nodeIds.has(e.source) && nodeIds.has(e.target)
|
|
|
);
|
|
|
|
|
|
// 清除运行时状态
|
|
|
const cleanedNodes = nodesToCopy.map((n) => ({
|
|
|
...n,
|
|
|
selected: false,
|
|
|
dragging: false,
|
|
|
}));
|
|
|
|
|
|
const cleanedEdges = edgesToCopy.map((e) => ({
|
|
|
...e,
|
|
|
selected: false,
|
|
|
}));
|
|
|
|
|
|
// 存储复制的数据
|
|
|
const copiedData = {
|
|
|
nodes: cleanedNodes,
|
|
|
edges: cleanedEdges,
|
|
|
appId: initialData?.appId,
|
|
|
};
|
|
|
|
|
|
localStorage.setItem('copiedFlowData', JSON.stringify(copiedData));
|
|
|
console.log(
|
|
|
`已复制 ${nodesToCopy.length} 个节点和 ${edgesToCopy.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 { nodes: copiedNodes, edges: copiedEdges } = copiedData;
|
|
|
|
|
|
if (!copiedNodes || copiedNodes.length === 0) {
|
|
|
console.warn('没有可粘贴的节点');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 计算所有节点的边界框中心点
|
|
|
const minX = Math.min(...copiedNodes.map((n: Node) => n.position.x));
|
|
|
const minY = Math.min(...copiedNodes.map((n: Node) => n.position.y));
|
|
|
const maxX = Math.max(...copiedNodes.map((n: Node) => n.position.x));
|
|
|
const maxY = Math.max(...copiedNodes.map((n: Node) => n.position.y));
|
|
|
const centerX = (minX + maxX) / 2;
|
|
|
const centerY = (minY + maxY) / 2;
|
|
|
|
|
|
// 计算偏移量,使粘贴的节点组以鼠标位置为中心
|
|
|
const offsetX = position.x - centerX;
|
|
|
const offsetY = position.y - centerY;
|
|
|
|
|
|
// 创建旧ID到新ID的映射
|
|
|
const idMap = new Map<string, string>();
|
|
|
const timestamp = Date.now();
|
|
|
|
|
|
// 创建新节点
|
|
|
const newNodes = copiedNodes.map((node: Node, index: number) => {
|
|
|
const newId = `${node.type}-${timestamp}-${index}`;
|
|
|
idMap.set(node.id, newId);
|
|
|
|
|
|
const newNode = {
|
|
|
...node,
|
|
|
id: newId,
|
|
|
position: {
|
|
|
x: node.position.x + offsetX,
|
|
|
y: node.position.y + offsetY,
|
|
|
},
|
|
|
selected: false,
|
|
|
dragging: false,
|
|
|
appId: undefined,
|
|
|
};
|
|
|
|
|
|
// 注册节点类型
|
|
|
ensureNodeTypeRegistered(
|
|
|
newNode.type,
|
|
|
(newNode.data?.title as string) || newNode.type,
|
|
|
resolveNodeComponent(newNode.type)
|
|
|
);
|
|
|
|
|
|
return newNode;
|
|
|
});
|
|
|
|
|
|
// 创建新边,使用新的节点ID
|
|
|
const newEdges = copiedEdges
|
|
|
.map((edge: Edge, index: number) => {
|
|
|
const newSourceId = idMap.get(edge.source);
|
|
|
const newTargetId = idMap.get(edge.target);
|
|
|
|
|
|
if (!newSourceId || !newTargetId) {
|
|
|
console.warn('边的源节点或目标节点未找到:', edge);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
...edge,
|
|
|
id: `e${newSourceId}-${newTargetId}-${timestamp}-${index}`,
|
|
|
source: newSourceId,
|
|
|
target: newTargetId,
|
|
|
selected: false,
|
|
|
};
|
|
|
})
|
|
|
.filter(Boolean); // 过滤掉null值
|
|
|
|
|
|
// 更新节点和边
|
|
|
setNodes((nds: Node[]) => {
|
|
|
const updatedNodes = [...nds, ...newNodes];
|
|
|
|
|
|
// 添加节点后记录历史
|
|
|
dispatchFlowSnapshotAsync({
|
|
|
nodes: [...updatedNodes],
|
|
|
edges: [...edges, ...newEdges],
|
|
|
});
|
|
|
|
|
|
return updatedNodes;
|
|
|
});
|
|
|
|
|
|
setEdges((eds: Edge[]) => {
|
|
|
const updatedEdges = [...eds, ...newEdges];
|
|
|
return updatedEdges;
|
|
|
});
|
|
|
|
|
|
console.log(
|
|
|
`已粘贴 ${newNodes.length} 个节点和 ${newEdges.length} 条边`
|
|
|
);
|
|
|
}
|
|
|
// 处理旧格式(单节点)- 保持向后兼容
|
|
|
else if (copiedNodeStr) {
|
|
|
const copiedNode = JSON.parse(copiedNodeStr);
|
|
|
|
|
|
// 检查是否为同一应用
|
|
|
if (
|
|
|
copiedNode.appId &&
|
|
|
initialData?.appId &&
|
|
|
copiedNode.appId !== initialData.appId
|
|
|
) {
|
|
|
console.warn('不能在不同应用之间粘贴节点');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 创建新节点,更新ID和位置
|
|
|
const newNode = {
|
|
|
...copiedNode,
|
|
|
id: `${copiedNode.type}-${Date.now()}`,
|
|
|
position,
|
|
|
selected: false,
|
|
|
dragging: false,
|
|
|
appId: undefined,
|
|
|
};
|
|
|
|
|
|
// 特殊处理循环节点
|
|
|
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 || 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;
|
|
|
}
|
|
|
|
|
|
// 处理循环节点删除逻辑
|
|
|
if (node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END') {
|
|
|
// 获取关联的另一个循环节点
|
|
|
let relatedNodeId = null;
|
|
|
|
|
|
// 类型断言,将component从unknown转换为具有customDef属性的对象
|
|
|
const component = node.data?.component as
|
|
|
| { customDef?: string }
|
|
|
| undefined;
|
|
|
|
|
|
if (node.data?.type === 'LOOP_START' && component?.customDef) {
|
|
|
try {
|
|
|
const customDef = JSON.parse(component.customDef);
|
|
|
relatedNodeId = customDef.loopEndNodeId;
|
|
|
} catch (e) {
|
|
|
console.error('解析循环开始节点数据失败:', e);
|
|
|
}
|
|
|
} else if (node.data?.type === 'LOOP_END' && component?.customDef) {
|
|
|
try {
|
|
|
const customDef = JSON.parse(component.customDef);
|
|
|
relatedNodeId = customDef.loopStartNodeId;
|
|
|
} catch (e) {
|
|
|
console.error('解析循环结束节点数据失败:', e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 删除两个节点及相关边
|
|
|
setNodes((nds: Node[]) => {
|
|
|
const updatedNodes = nds.filter(
|
|
|
(n) => n.id !== node.id && n.id !== relatedNodeId
|
|
|
);
|
|
|
return updatedNodes;
|
|
|
});
|
|
|
|
|
|
setEdges((eds: Edge[]) => {
|
|
|
const updatedEdges = eds.filter(
|
|
|
(e) =>
|
|
|
e.source !== node.id &&
|
|
|
e.target !== node.id &&
|
|
|
e.source !== relatedNodeId &&
|
|
|
e.target !== relatedNodeId
|
|
|
);
|
|
|
return updatedEdges;
|
|
|
});
|
|
|
|
|
|
// 删除节点后记录历史
|
|
|
setTimeout(() => {
|
|
|
const updatedNodes = nodes.filter(
|
|
|
(n) => n.id !== node.id && n.id !== relatedNodeId
|
|
|
);
|
|
|
const updatedEdges = edges.filter(
|
|
|
(e) =>
|
|
|
e.source !== node.id &&
|
|
|
e.target !== node.id &&
|
|
|
e.source !== relatedNodeId &&
|
|
|
e.target !== relatedNodeId
|
|
|
);
|
|
|
|
|
|
dispatchFlowSnapshot({
|
|
|
nodes: [...updatedNodes],
|
|
|
edges: [...updatedEdges],
|
|
|
});
|
|
|
}, 0);
|
|
|
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 普通节点删除逻辑
|
|
|
setNodes((nds: Node[]) => nds.filter((n) => n.id !== node.id));
|
|
|
setEdges((eds: Edge[]) =>
|
|
|
eds.filter((e) => e.source !== node.id && e.target !== node.id)
|
|
|
);
|
|
|
|
|
|
// 删除节点后记录历史
|
|
|
setTimeout(() => {
|
|
|
dispatchFlowSnapshot({
|
|
|
nodes: [...nodes.filter((n) => n.id !== node.id)],
|
|
|
edges: [
|
|
|
...edges.filter(
|
|
|
(e) => e.source !== node.id && e.target !== node.id
|
|
|
),
|
|
|
],
|
|
|
});
|
|
|
}, 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 () => {
|
|
|
if (useDefault) {
|
|
|
try {
|
|
|
// 首先校验所有节点数据是否完整
|
|
|
const nodeValidation = validateAllNodes(nodes);
|
|
|
if (!nodeValidation.isValid) {
|
|
|
showValidationErrors(nodeValidation.errors);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 然后校验所有连接线是否有效
|
|
|
const edgeValidation = validateAllEdges(edges, nodes);
|
|
|
if (!edgeValidation.isValid) {
|
|
|
showValidationErrors(edgeValidation.errors);
|
|
|
return;
|
|
|
}
|
|
|
// 转换会原始数据类型
|
|
|
const revertedData = revertFlowData(nodes, edges);
|
|
|
const upDatePublishCB = await upDatePublish(revertedData.nodeConfigs);
|
|
|
const newRevertedData = reverseConvertFlowData(
|
|
|
nodes,
|
|
|
edges,
|
|
|
upDatePublishCB
|
|
|
);
|
|
|
const { flowData, currentAppData, info } =
|
|
|
store.getState().ideContainer;
|
|
|
const { deleteEventSendNodeList, deleteEventlisteneList } =
|
|
|
handelEventNodeList(newRevertedData);
|
|
|
|
|
|
let params = {};
|
|
|
// 更新复合组件/子流程
|
|
|
if (currentAppData.key.includes('sub')) {
|
|
|
const appEventDefinition = updateEvent(
|
|
|
revertedData.nodeConfigs,
|
|
|
initialData.appId
|
|
|
);
|
|
|
params = {
|
|
|
...(currentAppData?.compData || {}),
|
|
|
components: newRevertedData,
|
|
|
appEventDefinition,
|
|
|
sceneId: info.id,
|
|
|
};
|
|
|
const res: any = await setSubFlowNew(
|
|
|
params,
|
|
|
currentAppData.parentAppId
|
|
|
);
|
|
|
if (res.code === 200) {
|
|
|
Message.success('保存成功');
|
|
|
// 更新事件枚举表
|
|
|
const res1: any = await queryEventItemBySceneIdOld(info.id);
|
|
|
if (res1.code === 200) dispatch(updateEventListOld(res1.data));
|
|
|
|
|
|
const appRes: any = await getAppInfoNew(currentAppData.parentAppId);
|
|
|
// 更新 flowData 中的数据
|
|
|
dispatch(
|
|
|
updateFlowData({ [currentAppData.parentAppId]: appRes.data })
|
|
|
);
|
|
|
// 同步更新主流程到 canvasDataMap(使用父应用ID)
|
|
|
if (appRes.data.main?.components) {
|
|
|
const { nodes: parentNodes, edges: parentEdges } =
|
|
|
convertFlowData(appRes.data.main.components, true);
|
|
|
dispatch(
|
|
|
updateCanvasDataMap({
|
|
|
...canvasDataMap,
|
|
|
[currentAppData.parentAppId]: {
|
|
|
nodes: parentNodes,
|
|
|
edges: parentEdges,
|
|
|
},
|
|
|
})
|
|
|
);
|
|
|
}
|
|
|
// 同步更新子流程到 canvasDataMap(使用子流程key)
|
|
|
dispatch(
|
|
|
updateCanvasDataMap({
|
|
|
...canvasDataMap,
|
|
|
[currentAppData.key]: { nodes, edges },
|
|
|
})
|
|
|
);
|
|
|
} else {
|
|
|
Message.error(res.message);
|
|
|
}
|
|
|
}
|
|
|
// 更新主流程
|
|
|
else {
|
|
|
const appEventDefinition = updateEvent(
|
|
|
revertedData.nodeConfigs,
|
|
|
initialData.appId
|
|
|
);
|
|
|
params = {
|
|
|
...(flowData[currentAppData.id]?.main || {}),
|
|
|
components: newRevertedData,
|
|
|
appEventDefinition,
|
|
|
sceneId: info.id,
|
|
|
};
|
|
|
|
|
|
const res: any = await setMainFlowNew(params, initialData.appId);
|
|
|
if (res.code === 200) {
|
|
|
Message.success('保存成功');
|
|
|
// 更新事件枚举表
|
|
|
const res1: any = await queryEventItemBySceneIdOld(info.id);
|
|
|
if (res1.code === 200) dispatch(updateEventListOld(res1.data));
|
|
|
|
|
|
// 更新缓存数据
|
|
|
dispatch(
|
|
|
updateCanvasDataMap({
|
|
|
...canvasDataMap,
|
|
|
[currentAppData.id]: { nodes, edges },
|
|
|
})
|
|
|
);
|
|
|
const appRes: any = await getAppInfoNew(currentAppData.id);
|
|
|
// 更新 flowData 中的数据
|
|
|
dispatch(updateFlowData({ [currentAppData.id]: appRes.data }));
|
|
|
// 同步更新到 canvasDataMap
|
|
|
if (appRes.data.main?.components) {
|
|
|
const { nodes, edges } = convertFlowData(
|
|
|
appRes.data.main.components,
|
|
|
true
|
|
|
);
|
|
|
setNodes(nodes);
|
|
|
setEdges(edges);
|
|
|
dispatch(
|
|
|
updateCanvasDataMap({
|
|
|
...canvasDataMap,
|
|
|
[currentAppData.id]: { nodes, edges },
|
|
|
})
|
|
|
);
|
|
|
}
|
|
|
} else {
|
|
|
Message.error(res.message);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 事件节点变动数据有长度就通知后端,主流程和子流程(复合节点)通用
|
|
|
if (
|
|
|
deleteEventSendNodeList.length > 0 ||
|
|
|
deleteEventlisteneList.length > 0
|
|
|
) {
|
|
|
deleteEventSendNodeList.length > 0 &&
|
|
|
deleteEventPub({
|
|
|
appId: currentAppData.id,
|
|
|
topics: deleteEventSendNodeList,
|
|
|
});
|
|
|
deleteEventlisteneList.length > 0 &&
|
|
|
deleteEventSub({
|
|
|
appId: currentAppData.id,
|
|
|
topics: deleteEventlisteneList,
|
|
|
});
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('Error saving flow data:', error);
|
|
|
Message.error('保存失败');
|
|
|
}
|
|
|
} else {
|
|
|
const appFlowParams = {
|
|
|
appEventList: {},
|
|
|
eventEdges: [],
|
|
|
};
|
|
|
|
|
|
nodes.forEach((node) => {
|
|
|
appFlowParams.appEventList[node.id] = {
|
|
|
x: node.position.x,
|
|
|
y: node.position.y,
|
|
|
};
|
|
|
});
|
|
|
|
|
|
const eventMap = new Map();
|
|
|
edges.forEach((edge: any) => {
|
|
|
// 处理事件连线
|
|
|
appFlowParams.eventEdges.push({
|
|
|
id: edge.id,
|
|
|
source: edge.source,
|
|
|
target: edge.target,
|
|
|
lineType: 'data',
|
|
|
data: {
|
|
|
displayData: {
|
|
|
...edge.data.displayData,
|
|
|
},
|
|
|
},
|
|
|
});
|
|
|
|
|
|
// 应用组件的桩点id就是事件id
|
|
|
const sourceId = edge.sourceHandle;
|
|
|
const targetId = edge.targetHandle;
|
|
|
const topic = edge.data.displayData?.topic;
|
|
|
|
|
|
if (eventMap.has(topic)) {
|
|
|
// 如果topic已存在,将eventId添加到数组中
|
|
|
eventMap.get(topic).eventId.push(sourceId);
|
|
|
eventMap.get(topic).eventId.push(targetId);
|
|
|
} else {
|
|
|
// 如果topic不存在,创建新的条目
|
|
|
eventMap.set(topic, {
|
|
|
eventId: [sourceId, targetId],
|
|
|
topic: topic,
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
// 对eventId数组进行去重处理
|
|
|
const appEventParams = Array.from(eventMap.values()).map((item) => ({
|
|
|
...item,
|
|
|
eventId: Array.from(new Set(item.eventId)),
|
|
|
}));
|
|
|
try {
|
|
|
updateAppFlowData(appFlowParams);
|
|
|
if (appEventParams.length > 0) {
|
|
|
for (const item of appEventParams) {
|
|
|
if (item.topic) {
|
|
|
await sleep(500);
|
|
|
await updateAppEventChannel(item);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Message.success('保存成功');
|
|
|
} catch (error) {
|
|
|
console.error('保存失败:', error);
|
|
|
Message.error('保存失败: ' + error.message);
|
|
|
}
|
|
|
}
|
|
|
}, [nodes, edges, initialData?.appId]);
|
|
|
|
|
|
// 运行处理函数
|
|
|
const handleRun = useCallback(
|
|
|
async (running: boolean) => {
|
|
|
const { currentAppData, socketId, appRuntimeData } =
|
|
|
store.getState().ideContainer;
|
|
|
const appKey = getCurrentFlowAppKey();
|
|
|
|
|
|
if (running) {
|
|
|
// 子流程运行
|
|
|
if (currentAppData.key.includes('sub')) {
|
|
|
// 启动运行
|
|
|
const params = {
|
|
|
appId: currentAppData.parentAppId,
|
|
|
socketId,
|
|
|
subflowId: currentAppData.key,
|
|
|
};
|
|
|
const res: any = await runSubFlow(params);
|
|
|
|
|
|
if (res.code === 200) {
|
|
|
// 设置运行状态为true
|
|
|
dispatch(updateIsRunning(true));
|
|
|
|
|
|
// 重置节点状态
|
|
|
dispatch(resetNodeStatus());
|
|
|
|
|
|
// 更新运行ID
|
|
|
dispatch(updateRuntimeId(res.data));
|
|
|
refreshAppList();
|
|
|
|
|
|
// 开始运行时动画
|
|
|
setEdges((eds) =>
|
|
|
eds.map((edge) => ({
|
|
|
...edge,
|
|
|
data: {
|
|
|
...edge.data,
|
|
|
isRunning: true,
|
|
|
animationProgress: 0,
|
|
|
},
|
|
|
}))
|
|
|
);
|
|
|
} else {
|
|
|
Message.error(res.message);
|
|
|
}
|
|
|
}
|
|
|
// 主流程运行
|
|
|
else {
|
|
|
// 启动运行
|
|
|
const params = {
|
|
|
appId: currentAppData.id,
|
|
|
socketId,
|
|
|
};
|
|
|
const res: any = await runMainFlow(params);
|
|
|
if (res.code === 200) {
|
|
|
// 设置运行状态为true
|
|
|
dispatch(updateIsRunning(true));
|
|
|
|
|
|
// 重置节点状态
|
|
|
dispatch(resetNodeStatus());
|
|
|
|
|
|
// 更新运行ID
|
|
|
dispatch(updateRuntimeId(res.data));
|
|
|
refreshAppList();
|
|
|
|
|
|
// 开始运行时动画
|
|
|
setEdges((eds) =>
|
|
|
eds.map((edge) => ({
|
|
|
...edge,
|
|
|
data: {
|
|
|
...edge.data,
|
|
|
isRunning: true,
|
|
|
animationProgress: 0,
|
|
|
},
|
|
|
}))
|
|
|
);
|
|
|
} else {
|
|
|
Message.error(res.message);
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
// 设置运行状态为false
|
|
|
dispatch(updateIsRunning(false));
|
|
|
|
|
|
// 使用正确的 appKey 获取 runId
|
|
|
const runId =
|
|
|
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
|
|
|
if (runId) {
|
|
|
await stopApp(runId);
|
|
|
} else {
|
|
|
// 特殊停止逻辑,持久化运行的应用使用这里的入参
|
|
|
await stopApp(currentAppData.instanceId);
|
|
|
}
|
|
|
refreshAppList();
|
|
|
|
|
|
// 重置节点状态
|
|
|
dispatch(resetNodeStatus());
|
|
|
|
|
|
// 更新运行ID
|
|
|
dispatch(updateRuntimeId(''));
|
|
|
|
|
|
// 停止运行
|
|
|
setEdges((eds) =>
|
|
|
eds.map((edge) => ({
|
|
|
...edge,
|
|
|
data: {
|
|
|
...edge.data,
|
|
|
isRunning: false,
|
|
|
animationProgress: 0,
|
|
|
},
|
|
|
}))
|
|
|
);
|
|
|
|
|
|
// 清空当前应用的运行日志
|
|
|
if (appKey) {
|
|
|
dispatch(clearRuntimeLogs({ appId: appKey }));
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
[getCurrentFlowAppKey, refreshAppList]
|
|
|
);
|
|
|
|
|
|
// 暂停/恢复应用
|
|
|
const handlePause = useCallback(
|
|
|
async (isPaused: boolean) => {
|
|
|
const { currentAppData, appRuntimeData } = store.getState().ideContainer;
|
|
|
const appKey = getCurrentFlowAppKey();
|
|
|
|
|
|
if (!currentAppData) {
|
|
|
Message.warning('请先选择一个应用');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 获取runId
|
|
|
const runId =
|
|
|
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
|
|
|
|
|
|
if (!runId) {
|
|
|
Message.warning('应用未运行');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
if (isPaused) {
|
|
|
// 当前已暂停,执行恢复操作
|
|
|
const res: any = await resumeApp({ id: runId });
|
|
|
if (res.code === 200) {
|
|
|
Message.success('应用已恢复');
|
|
|
// 更新暂停状态为 false
|
|
|
dispatch(updateIsPaused(false));
|
|
|
} else {
|
|
|
Message.error(res.msg || '恢复失败');
|
|
|
}
|
|
|
} else {
|
|
|
// 当前正在运行,执行暂停操作
|
|
|
const res: any = await pauseApp({ id: runId });
|
|
|
if (res.code === 200) {
|
|
|
Message.success('应用已暂停');
|
|
|
// 更新暂停状态为 true
|
|
|
dispatch(updateIsPaused(true));
|
|
|
} else {
|
|
|
Message.error(res.msg || '暂停失败');
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('暂停/恢复失败:', error);
|
|
|
Message.error('操作失败');
|
|
|
}
|
|
|
},
|
|
|
[getCurrentFlowAppKey]
|
|
|
);
|
|
|
|
|
|
// 重跑应用
|
|
|
const handleReRun = useCallback(async () => {
|
|
|
const { currentAppData, appRuntimeData, socketId } =
|
|
|
store.getState().ideContainer;
|
|
|
const appKey = getCurrentFlowAppKey();
|
|
|
|
|
|
if (!currentAppData) {
|
|
|
Message.warning('请先选择一个应用');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 获取runId (instanceId)
|
|
|
const instanceId =
|
|
|
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
|
|
|
|
|
|
if (!instanceId) {
|
|
|
Message.warning('应用未运行');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
// 判断是主流程还是子流程
|
|
|
const appId =
|
|
|
currentAppData.key && currentAppData.key.includes('sub')
|
|
|
? currentAppData.parentAppId
|
|
|
: currentAppData.id;
|
|
|
|
|
|
const res: any = await reRunApp({
|
|
|
appId,
|
|
|
instanceId,
|
|
|
socketId,
|
|
|
});
|
|
|
|
|
|
if (res.code === 200) {
|
|
|
Message.success('应用重跑成功');
|
|
|
|
|
|
// 重置节点状态
|
|
|
dispatch(resetNodeStatus());
|
|
|
} else {
|
|
|
Message.error(res.msg || '重跑失败');
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('重跑失败:', error);
|
|
|
Message.error('重跑失败');
|
|
|
}
|
|
|
}, [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;
|