| | |
| | | CUSTOM_EDGE, |
| | | ITERATION_CHILDREN_Z_INDEX, |
| | | ITERATION_PADDING, |
| | | LOOP_CHILDREN_Z_INDEX, |
| | | LOOP_PADDING, |
| | | NODES_INITIAL_DATA, |
| | | NODE_WIDTH_X_OFFSET, |
| | | X_OFFSET, |
| | |
| | | import { |
| | | genNewNodeTitleFromOld, |
| | | generateNewNode, |
| | | getNodeCustomTypeByNodeDataType, |
| | | getNodesConnectedSourceOrTargetHandleIdsMap, |
| | | getTopLeftNodePosition, |
| | | } from '../utils' |
| | | import { CUSTOM_NOTE_NODE } from '../note-node/constants' |
| | | import type { IterationNodeType } from '../nodes/iteration/types' |
| | | import type { LoopNodeType } from '../nodes/loop/types' |
| | | import { CUSTOM_ITERATION_START_NODE } from '../nodes/iteration-start/constants' |
| | | import { CUSTOM_LOOP_START_NODE } from '../nodes/loop-start/constants' |
| | | import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types' |
| | | import { useNodeIterationInteractions } from '../nodes/iteration/use-interactions' |
| | | import { useNodeLoopInteractions } from '../nodes/loop/use-interactions' |
| | | import { useWorkflowHistoryStore } from '../workflow-history-store' |
| | | import { useNodesSyncDraft } from './use-nodes-sync-draft' |
| | | import { useHelpline } from './use-helpline' |
| | |
| | | handleNodeIterationChildDrag, |
| | | handleNodeIterationChildrenCopy, |
| | | } = useNodeIterationInteractions() |
| | | const { |
| | | handleNodeLoopChildDrag, |
| | | handleNodeLoopChildrenCopy, |
| | | } = useNodeLoopInteractions() |
| | | const dragNodeStartPosition = useRef({ x: 0, y: 0 } as { x: number; y: number }) |
| | | |
| | | const { saveStateToHistory, undo, redo } = useWorkflowHistory() |
| | |
| | | if (node.type === CUSTOM_ITERATION_START_NODE || node.type === CUSTOM_NOTE_NODE) |
| | | return |
| | | |
| | | if (node.type === CUSTOM_LOOP_START_NODE || node.type === CUSTOM_NOTE_NODE) |
| | | return |
| | | |
| | | dragNodeStartPosition.current = { x: node.position.x, y: node.position.y } |
| | | }, [workflowStore, getNodesReadOnly]) |
| | | |
| | |
| | | return |
| | | |
| | | if (node.type === CUSTOM_ITERATION_START_NODE) |
| | | return |
| | | |
| | | if (node.type === CUSTOM_LOOP_START_NODE) |
| | | return |
| | | |
| | | const { |
| | |
| | | const nodes = getNodes() |
| | | |
| | | const { restrictPosition } = handleNodeIterationChildDrag(node) |
| | | const { restrictPosition: restrictLoopPosition } = handleNodeLoopChildDrag(node) |
| | | |
| | | const { |
| | | showHorizontalHelpLineNodes, |
| | |
| | | currentNode.position.x = showVerticalHelpLineNodes[0].position.x |
| | | else if (restrictPosition.x !== undefined) |
| | | currentNode.position.x = restrictPosition.x |
| | | else if (restrictLoopPosition.x !== undefined) |
| | | currentNode.position.x = restrictLoopPosition.x |
| | | else |
| | | currentNode.position.x = node.position.x |
| | | |
| | |
| | | currentNode.position.y = showHorizontalHelpLineNodes[0].position.y |
| | | else if (restrictPosition.y !== undefined) |
| | | currentNode.position.y = restrictPosition.y |
| | | else if (restrictLoopPosition.y !== undefined) |
| | | currentNode.position.y = restrictLoopPosition.y |
| | | else |
| | | currentNode.position.y = node.position.y |
| | | }) |
| | | |
| | | setNodes(newNodes) |
| | | }, [getNodesReadOnly, store, handleNodeIterationChildDrag, handleNodeLoopChildDrag, handleSetHelpline]) |
| | | }, [store, getNodesReadOnly, handleSetHelpline, handleNodeIterationChildDrag]) |
| | | |
| | | const handleNodeDragStop = useCallback<NodeDragHandler>((_, node) => { |
| | | const { |
| | |
| | | return |
| | | |
| | | if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE) |
| | | return |
| | | |
| | | if (node.type === CUSTOM_LOOP_START_NODE || node.type === CUSTOM_NOTE_NODE) |
| | | return |
| | | |
| | | const { |
| | |
| | | if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE) |
| | | return |
| | | |
| | | if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_LOOP_START_NODE) |
| | | return |
| | | |
| | | const { |
| | | setEnteringNodePayload, |
| | | } = workflowStore.getState() |
| | |
| | | const handleNodeClick = useCallback<NodeMouseHandler>((_, node) => { |
| | | if (node.type === CUSTOM_ITERATION_START_NODE) |
| | | return |
| | | if (node.type === CUSTOM_LOOP_START_NODE) |
| | | return |
| | | handleNodeSelect(node.id) |
| | | }, [handleNodeSelect]) |
| | | |
| | |
| | | if (edges.find(edge => edge.source === source && edge.sourceHandle === sourceHandle && edge.target === target && edge.targetHandle === targetHandle)) |
| | | return |
| | | |
| | | const parendNode = nodes.find(node => node.id === targetNode?.parentId) |
| | | const isInIteration = parendNode && parendNode.data.type === BlockEnum.Iteration |
| | | const isInLoop = !!parendNode && parendNode.data.type === BlockEnum.Loop |
| | | |
| | | const newEdge = { |
| | | id: `${source}-${sourceHandle}-${target}-${targetHandle}`, |
| | | type: CUSTOM_EDGE, |
| | |
| | | data: { |
| | | sourceType: nodes.find(node => node.id === source)!.data.type, |
| | | targetType: nodes.find(node => node.id === target)!.data.type, |
| | | isInIteration, |
| | | iteration_id: isInIteration ? targetNode?.parentId : undefined, |
| | | isInLoop, |
| | | loop_id: isInLoop ? targetNode?.parentId : undefined, |
| | | isInIteration: !!targetNode?.parentId, |
| | | iteration_id: targetNode?.parentId, |
| | | }, |
| | | zIndex: targetNode?.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0, |
| | | zIndex: targetNode?.parentId ? ITERATION_CHILDREN_Z_INDEX : 0, |
| | | } |
| | | const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap( |
| | | [ |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (currentNode.data.type === BlockEnum.Loop) { |
| | | const loopChildren = nodes.filter(node => node.parentId === currentNode.id) |
| | | |
| | | if (loopChildren.length) { |
| | | if (currentNode.data._isBundled) { |
| | | loopChildren.forEach((child) => { |
| | | handleNodeDelete(child.id) |
| | | }) |
| | | return handleNodeDelete(nodeId) |
| | | } |
| | | else { |
| | | if (loopChildren.length === 1) { |
| | | handleNodeDelete(loopChildren[0].id) |
| | | handleNodeDelete(nodeId) |
| | | |
| | | return |
| | | } |
| | | const { setShowConfirm, showConfirm } = workflowStore.getState() |
| | | |
| | | if (!showConfirm) { |
| | | setShowConfirm({ |
| | | title: t('workflow.nodes.loop.deleteTitle'), |
| | | desc: t('workflow.nodes.loop.deleteDesc') || '', |
| | | onConfirm: () => { |
| | | loopChildren.forEach((child) => { |
| | | handleNodeDelete(child.id) |
| | | }) |
| | | handleNodeDelete(nodeId) |
| | | handleSyncWorkflowDraft() |
| | | setShowConfirm(undefined) |
| | | }, |
| | | }) |
| | | return |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | const connectedEdges = getConnectedEdges([{ id: nodeId } as Node], edges) |
| | | const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(connectedEdges.map(edge => ({ type: 'remove', edge })), nodes) |
| | | const newNodes = produce(nodes, (draft: Node[]) => { |
| | |
| | | } |
| | | |
| | | if (node.id === currentNode.parentId) |
| | | node.data._children = node.data._children?.filter(child => child.nodeId !== nodeId) |
| | | node.data._children = node.data._children?.filter(child => child !== nodeId) |
| | | }) |
| | | draft.splice(currentNodeIndex, 1) |
| | | }) |
| | |
| | | const { |
| | | newNode, |
| | | newIterationStartNode, |
| | | newLoopStartNode, |
| | | } = generateNewNode({ |
| | | type: getNodeCustomTypeByNodeDataType(nodeType), |
| | | data: { |
| | | ...NODES_INITIAL_DATA[nodeType], |
| | | title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`), |
| | |
| | | } |
| | | newNode.parentId = prevNode.parentId |
| | | newNode.extent = prevNode.extent |
| | | |
| | | const parentNode = nodes.find(node => node.id === prevNode.parentId) || null |
| | | const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration |
| | | const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop |
| | | |
| | | if (prevNode.parentId) { |
| | | newNode.data.isInIteration = isInIteration |
| | | newNode.data.isInLoop = isInLoop |
| | | if (isInIteration) { |
| | | newNode.data.iteration_id = parentNode.id |
| | | newNode.zIndex = ITERATION_CHILDREN_Z_INDEX |
| | | } |
| | | if (isInLoop) { |
| | | newNode.data.loop_id = parentNode.id |
| | | newNode.zIndex = LOOP_CHILDREN_Z_INDEX |
| | | } |
| | | if (isInIteration && (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner)) { |
| | | const iterNodeData: IterationNodeType = parentNode.data |
| | | iterNodeData._isShowTips = true |
| | | } |
| | | if (isInLoop && (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner)) { |
| | | const iterNodeData: IterationNodeType = parentNode.data |
| | | newNode.data.isInIteration = true |
| | | newNode.data.iteration_id = prevNode.parentId |
| | | newNode.zIndex = ITERATION_CHILDREN_Z_INDEX |
| | | if (newNode.data.type === BlockEnum.Answer || newNode.data.type === BlockEnum.Tool || newNode.data.type === BlockEnum.Assigner) { |
| | | const parentIterNodeIndex = nodes.findIndex(node => node.id === prevNode.parentId) |
| | | const iterNodeData: IterationNodeType = nodes[parentIterNodeIndex].data |
| | | iterNodeData._isShowTips = true |
| | | } |
| | | } |
| | |
| | | data: { |
| | | sourceType: prevNode.data.type, |
| | | targetType: newNode.data.type, |
| | | isInIteration, |
| | | isInLoop, |
| | | iteration_id: isInIteration ? prevNode.parentId : undefined, |
| | | loop_id: isInLoop ? prevNode.parentId : undefined, |
| | | isInIteration: !!prevNode.parentId, |
| | | iteration_id: prevNode.parentId, |
| | | _connectedNodeIsSelected: true, |
| | | }, |
| | | zIndex: prevNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0, |
| | | zIndex: prevNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0, |
| | | } |
| | | const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap( |
| | | [ |
| | |
| | | } |
| | | |
| | | if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id) |
| | | node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type }) |
| | | |
| | | if (node.data.type === BlockEnum.Loop && prevNode.parentId === node.id) |
| | | node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type }) |
| | | node.data._children?.push(newNode.id) |
| | | }) |
| | | draft.push(newNode) |
| | | |
| | | if (newIterationStartNode) |
| | | draft.push(newIterationStartNode) |
| | | |
| | | if (newLoopStartNode) |
| | | draft.push(newLoopStartNode) |
| | | }) |
| | | |
| | | if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) { |
| | |
| | | } |
| | | newNode.parentId = nextNode.parentId |
| | | newNode.extent = nextNode.extent |
| | | |
| | | const parentNode = nodes.find(node => node.id === nextNode.parentId) || null |
| | | const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration |
| | | const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop |
| | | |
| | | if (parentNode && nextNode.parentId) { |
| | | newNode.data.isInIteration = isInIteration |
| | | newNode.data.isInLoop = isInLoop |
| | | if (isInIteration) { |
| | | newNode.data.iteration_id = parentNode.id |
| | | newNode.zIndex = ITERATION_CHILDREN_Z_INDEX |
| | | } |
| | | if (isInLoop) { |
| | | newNode.data.loop_id = parentNode.id |
| | | newNode.zIndex = LOOP_CHILDREN_Z_INDEX |
| | | } |
| | | if (nextNode.parentId) { |
| | | newNode.data.isInIteration = true |
| | | newNode.data.iteration_id = nextNode.parentId |
| | | newNode.zIndex = ITERATION_CHILDREN_Z_INDEX |
| | | } |
| | | |
| | | let newEdge |
| | | |
| | | if ((nodeType !== BlockEnum.IfElse) && (nodeType !== BlockEnum.QuestionClassifier) && (nodeType !== BlockEnum.LoopEnd)) { |
| | | if ((nodeType !== BlockEnum.IfElse) && (nodeType !== BlockEnum.QuestionClassifier)) { |
| | | newEdge = { |
| | | id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`, |
| | | type: CUSTOM_EDGE, |
| | |
| | | data: { |
| | | sourceType: newNode.data.type, |
| | | targetType: nextNode.data.type, |
| | | isInIteration, |
| | | isInLoop, |
| | | iteration_id: isInIteration ? nextNode.parentId : undefined, |
| | | loop_id: isInLoop ? nextNode.parentId : undefined, |
| | | isInIteration: !!nextNode.parentId, |
| | | iteration_id: nextNode.parentId, |
| | | _connectedNodeIsSelected: true, |
| | | }, |
| | | zIndex: nextNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0, |
| | | zIndex: nextNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0, |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | if (node.data.type === BlockEnum.Iteration && nextNode.parentId === node.id) |
| | | node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type }) |
| | | node.data._children?.push(newNode.id) |
| | | |
| | | if (node.data.type === BlockEnum.Iteration && node.data.start_node_id === nextNodeId) { |
| | | node.data.start_node_id = newNode.id |
| | | node.data.startNodeType = newNode.data.type |
| | | } |
| | | |
| | | if (node.data.type === BlockEnum.Loop && nextNode.parentId === node.id) |
| | | node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type }) |
| | | |
| | | if (node.data.type === BlockEnum.Loop && node.data.start_node_id === nextNodeId) { |
| | | node.data.start_node_id = newNode.id |
| | | node.data.startNodeType = newNode.data.type |
| | | } |
| | |
| | | draft.push(newNode) |
| | | if (newIterationStartNode) |
| | | draft.push(newIterationStartNode) |
| | | if (newLoopStartNode) |
| | | draft.push(newLoopStartNode) |
| | | }) |
| | | if (newEdge) { |
| | | const newEdges = produce(edges, (draft) => { |
| | |
| | | } |
| | | newNode.parentId = prevNode.parentId |
| | | newNode.extent = prevNode.extent |
| | | |
| | | const parentNode = nodes.find(node => node.id === prevNode.parentId) || null |
| | | const isInIteration = !!parentNode && parentNode.data.type === BlockEnum.Iteration |
| | | const isInLoop = !!parentNode && parentNode.data.type === BlockEnum.Loop |
| | | |
| | | if (parentNode && prevNode.parentId) { |
| | | newNode.data.isInIteration = isInIteration |
| | | newNode.data.isInLoop = isInLoop |
| | | if (isInIteration) { |
| | | newNode.data.iteration_id = parentNode.id |
| | | newNode.zIndex = ITERATION_CHILDREN_Z_INDEX |
| | | } |
| | | if (isInLoop) { |
| | | newNode.data.loop_id = parentNode.id |
| | | newNode.zIndex = LOOP_CHILDREN_Z_INDEX |
| | | } |
| | | if (prevNode.parentId) { |
| | | newNode.data.isInIteration = true |
| | | newNode.data.iteration_id = prevNode.parentId |
| | | newNode.zIndex = ITERATION_CHILDREN_Z_INDEX |
| | | } |
| | | |
| | | const currentEdgeIndex = edges.findIndex(edge => edge.source === prevNodeId && edge.target === nextNodeId) |
| | |
| | | data: { |
| | | sourceType: prevNode.data.type, |
| | | targetType: newNode.data.type, |
| | | isInIteration, |
| | | isInLoop, |
| | | iteration_id: isInIteration ? prevNode.parentId : undefined, |
| | | loop_id: isInLoop ? prevNode.parentId : undefined, |
| | | isInIteration: !!prevNode.parentId, |
| | | iteration_id: prevNode.parentId, |
| | | _connectedNodeIsSelected: true, |
| | | }, |
| | | zIndex: prevNode.parentId ? (isInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0, |
| | | zIndex: prevNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0, |
| | | } |
| | | let newNextEdge: Edge | null = null |
| | | |
| | | const nextNodeParentNode = nodes.find(node => node.id === nextNode.parentId) || null |
| | | const isNextNodeInIteration = !!nextNodeParentNode && nextNodeParentNode.data.type === BlockEnum.Iteration |
| | | const isNextNodeInLoop = !!nextNodeParentNode && nextNodeParentNode.data.type === BlockEnum.Loop |
| | | |
| | | if (nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier && nodeType !== BlockEnum.LoopEnd) { |
| | | if (nodeType !== BlockEnum.IfElse && nodeType !== BlockEnum.QuestionClassifier) { |
| | | newNextEdge = { |
| | | id: `${newNode.id}-${sourceHandle}-${nextNodeId}-${nextNodeTargetHandle}`, |
| | | type: CUSTOM_EDGE, |
| | |
| | | data: { |
| | | sourceType: newNode.data.type, |
| | | targetType: nextNode.data.type, |
| | | isInIteration: isNextNodeInIteration, |
| | | isInLoop: isNextNodeInLoop, |
| | | iteration_id: isNextNodeInIteration ? nextNode.parentId : undefined, |
| | | loop_id: isNextNodeInLoop ? nextNode.parentId : undefined, |
| | | isInIteration: !!nextNode.parentId, |
| | | iteration_id: nextNode.parentId, |
| | | _connectedNodeIsSelected: true, |
| | | }, |
| | | zIndex: nextNode.parentId ? (isNextNodeInIteration ? ITERATION_CHILDREN_Z_INDEX : LOOP_CHILDREN_Z_INDEX) : 0, |
| | | zIndex: nextNode.parentId ? ITERATION_CHILDREN_Z_INDEX : 0, |
| | | } |
| | | } |
| | | const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap( |
| | |
| | | node.position.x += NODE_WIDTH_X_OFFSET |
| | | |
| | | if (node.data.type === BlockEnum.Iteration && prevNode.parentId === node.id) |
| | | node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type }) |
| | | if (node.data.type === BlockEnum.Loop && prevNode.parentId === node.id) |
| | | node.data._children?.push({ nodeId: newNode.id, nodeType: newNode.data.type }) |
| | | node.data._children?.push(newNode.id) |
| | | }) |
| | | draft.push(newNode) |
| | | if (newIterationStartNode) |
| | | draft.push(newIterationStartNode) |
| | | if (newLoopStartNode) |
| | | draft.push(newLoopStartNode) |
| | | }) |
| | | setNodes(newNodes) |
| | | if (newNode.data.type === BlockEnum.VariableAssigner || newNode.data.type === BlockEnum.VariableAggregator) { |
| | |
| | | const { |
| | | newNode: newCurrentNode, |
| | | newIterationStartNode, |
| | | newLoopStartNode, |
| | | } = generateNewNode({ |
| | | type: getNodeCustomTypeByNodeDataType(nodeType), |
| | | data: { |
| | | ...NODES_INITIAL_DATA[nodeType], |
| | | title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`), |
| | |
| | | _connectedTargetHandleIds: [], |
| | | selected: currentNode.data.selected, |
| | | isInIteration: currentNode.data.isInIteration, |
| | | isInLoop: currentNode.data.isInLoop, |
| | | iteration_id: currentNode.data.iteration_id, |
| | | loop_id: currentNode.data.loop_id, |
| | | }, |
| | | position: { |
| | | x: currentNode.position.x, |
| | |
| | | draft.splice(index, 1, newCurrentNode) |
| | | if (newIterationStartNode) |
| | | draft.push(newIterationStartNode) |
| | | if (newLoopStartNode) |
| | | draft.push(newLoopStartNode) |
| | | }) |
| | | setNodes(newNodes) |
| | | const newEdges = produce(edges, (draft) => { |
| | |
| | | |
| | | saveStateToHistory(WorkflowHistoryEvent.NodeChange) |
| | | }, [getNodesReadOnly, store, t, handleSyncWorkflowDraft, saveStateToHistory]) |
| | | |
| | | const handleNodeCancelRunningStatus = useCallback(() => { |
| | | const { |
| | | getNodes, |
| | | setNodes, |
| | | } = store.getState() |
| | | |
| | | const nodes = getNodes() |
| | | const newNodes = produce(nodes, (draft) => { |
| | | draft.forEach((node) => { |
| | | node.data._runningStatus = undefined |
| | | node.data._waitingRun = false |
| | | }) |
| | | }) |
| | | setNodes(newNodes) |
| | | }, [store]) |
| | | |
| | | const handleNodesCancelSelected = useCallback(() => { |
| | | const { |
| | |
| | | |
| | | const handleNodeContextMenu = useCallback((e: MouseEvent, node: Node) => { |
| | | if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_ITERATION_START_NODE) |
| | | return |
| | | |
| | | if (node.type === CUSTOM_NOTE_NODE || node.type === CUSTOM_LOOP_START_NODE) |
| | | return |
| | | |
| | | e.preventDefault() |
| | |
| | | |
| | | if (nodeId) { |
| | | // If nodeId is provided, copy that specific node |
| | | const nodeToCopy = nodes.find(node => node.id === nodeId && node.data.type !== BlockEnum.Start |
| | | && node.type !== CUSTOM_ITERATION_START_NODE && node.type !== CUSTOM_LOOP_START_NODE && node.data.type !== BlockEnum.LoopEnd) |
| | | const nodeToCopy = nodes.find(node => node.id === nodeId && node.data.type !== BlockEnum.Start && node.type !== CUSTOM_ITERATION_START_NODE) |
| | | if (nodeToCopy) |
| | | setClipboardElements([nodeToCopy]) |
| | | } |
| | | else { |
| | | // If no nodeId is provided, fall back to the current behavior |
| | | const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start |
| | | && !node.data.isInIteration && !node.data.isInLoop) |
| | | const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && !node.data.isInIteration) |
| | | |
| | | if (bundledNodes.length) { |
| | | setClipboardElements(bundledNodes) |
| | | return |
| | | } |
| | | |
| | | const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.LoopEnd) |
| | | const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start) |
| | | |
| | | if (selectedNode) |
| | | setClipboardElements([selectedNode]) |
| | |
| | | const { |
| | | getNodes, |
| | | setNodes, |
| | | edges, |
| | | setEdges, |
| | | } = store.getState() |
| | | |
| | | const nodesToPaste: Node[] = [] |
| | | const edgesToPaste: Edge[] = [] |
| | | const nodes = getNodes() |
| | | |
| | | if (clipboardElements.length) { |
| | |
| | | const currentPosition = screenToFlowPosition({ x: mousePosition.pageX, y: mousePosition.pageY }) |
| | | const offsetX = currentPosition.x - x |
| | | const offsetY = currentPosition.y - y |
| | | let idMapping: Record<string, string> = {} |
| | | clipboardElements.forEach((nodeToPaste, index) => { |
| | | const nodeType = nodeToPaste.data.type |
| | | |
| | | const { |
| | | newNode, |
| | | newIterationStartNode, |
| | | newLoopStartNode, |
| | | } = generateNewNode({ |
| | | type: nodeToPaste.type, |
| | | data: { |
| | |
| | | newIterationStartNode!.parentId = newNode.id; |
| | | (newNode.data as IterationNodeType).start_node_id = newIterationStartNode!.id |
| | | |
| | | const oldIterationStartNode = nodes |
| | | .find(n => n.parentId === nodeToPaste.id && n.type === CUSTOM_ITERATION_START_NODE) |
| | | idMapping[oldIterationStartNode!.id] = newIterationStartNode!.id |
| | | |
| | | const { copyChildren, newIdMapping } = handleNodeIterationChildrenCopy(nodeToPaste.id, newNode.id, idMapping) |
| | | newChildren = copyChildren |
| | | idMapping = newIdMapping |
| | | newChildren = handleNodeIterationChildrenCopy(nodeToPaste.id, newNode.id) |
| | | newChildren.forEach((child) => { |
| | | newNode.data._children?.push({ nodeId: child.id, nodeType: child.data.type }) |
| | | newNode.data._children?.push(child.id) |
| | | }) |
| | | newChildren.push(newIterationStartNode!) |
| | | } |
| | | |
| | | if (nodeToPaste.data.type === BlockEnum.Loop) { |
| | | newLoopStartNode!.parentId = newNode.id; |
| | | (newNode.data as LoopNodeType).start_node_id = newLoopStartNode!.id |
| | | |
| | | newChildren = handleNodeLoopChildrenCopy(nodeToPaste.id, newNode.id) |
| | | newChildren.forEach((child) => { |
| | | newNode.data._children?.push({ nodeId: child.id, nodeType: child.data.type }) |
| | | }) |
| | | newChildren.push(newLoopStartNode!) |
| | | } |
| | | |
| | | nodesToPaste.push(newNode) |
| | |
| | | nodesToPaste.push(...newChildren) |
| | | }) |
| | | |
| | | edges.forEach((edge) => { |
| | | const sourceId = idMapping[edge.source] |
| | | const targetId = idMapping[edge.target] |
| | | |
| | | if (sourceId && targetId) { |
| | | const newEdge: Edge = { |
| | | ...edge, |
| | | id: `${sourceId}-${edge.sourceHandle}-${targetId}-${edge.targetHandle}`, |
| | | source: sourceId, |
| | | target: targetId, |
| | | data: { |
| | | ...edge.data, |
| | | _connectedNodeIsSelected: false, |
| | | }, |
| | | } |
| | | edgesToPaste.push(newEdge) |
| | | } |
| | | }) |
| | | |
| | | setNodes([...nodes, ...nodesToPaste]) |
| | | setEdges([...edges, ...edgesToPaste]) |
| | | saveStateToHistory(WorkflowHistoryEvent.NodePaste) |
| | | handleSyncWorkflowDraft() |
| | | } |
| | | }, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy, handleNodeLoopChildrenCopy]) |
| | | }, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy]) |
| | | |
| | | const handleNodesDuplicate = useCallback((nodeId?: string) => { |
| | | if (getNodesReadOnly()) |
| | |
| | | |
| | | const nodes = getNodes() |
| | | const currentNode = nodes.find(n => n.id === nodeId)! |
| | | const childrenNodes = nodes.filter(n => currentNode.data._children?.find((c: any) => c.nodeId === n.id)) |
| | | const childrenNodes = nodes.filter(n => currentNode.data._children?.includes(n.id)) |
| | | let rightNode: Node |
| | | let bottomNode: Node |
| | | |
| | |
| | | }) |
| | | |
| | | if (rightNode! && bottomNode!) { |
| | | const parentNode = nodes.find(n => n.id === rightNode.parentId) |
| | | const paddingMap = parentNode?.data.type === BlockEnum.Iteration ? ITERATION_PADDING : LOOP_PADDING |
| | | |
| | | if (width < rightNode!.position.x + rightNode.width! + paddingMap.right) |
| | | if (width < rightNode!.position.x + rightNode.width! + ITERATION_PADDING.right) |
| | | return |
| | | if (height < bottomNode.position.y + bottomNode.height! + paddingMap.bottom) |
| | | if (height < bottomNode.position.y + bottomNode.height! + ITERATION_PADDING.bottom) |
| | | return |
| | | } |
| | | const newNodes = produce(nodes, (draft) => { |
| | |
| | | handleNodeDelete, |
| | | handleNodeChange, |
| | | handleNodeAdd, |
| | | handleNodeCancelRunningStatus, |
| | | handleNodesCancelSelected, |
| | | handleNodeContextMenu, |
| | | handleNodesCopy, |