feat: workflow interaction (#4214)

This commit is contained in:
zxhlyh
2024-05-09 17:18:51 +08:00
committed by GitHub
parent 487ce7c82a
commit 9b24f12bf5
54 changed files with 1955 additions and 431 deletions

View File

@@ -1,3 +1,4 @@
import type { MouseEvent } from 'react'
import { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import produce from 'immer'
@@ -11,6 +12,7 @@ import type {
import {
getConnectedEdges,
getOutgoers,
useReactFlow,
useStoreApi,
} from 'reactflow'
import type { ToolDefaultValue } from '../block-selector/types'
@@ -29,6 +31,7 @@ import {
import {
generateNewNode,
getNodesConnectedSourceOrTargetHandleIdsMap,
getTopLeftNodePosition,
} from '../utils'
import { useNodesExtraData } from './use-nodes-data'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
@@ -41,6 +44,7 @@ export const useNodesInteractions = () => {
const { t } = useTranslation()
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const reactflow = useReactFlow()
const nodesExtraData = useNodesExtraData()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const {
@@ -705,132 +709,6 @@ export const useNodesInteractions = () => {
handleSyncWorkflowDraft()
}, [store, handleSyncWorkflowDraft, getNodesReadOnly, t])
const handleNodeCopySelected = useCallback((): undefined | Node[] => {
if (getNodesReadOnly())
return
const {
setClipboardElements,
shortcutsDisabled,
showFeaturesPanel,
} = workflowStore.getState()
if (shortcutsDisabled || showFeaturesPanel)
return
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const nodesToCopy = nodes.filter(node => node.data.selected && node.data.type !== BlockEnum.Start)
setClipboardElements(nodesToCopy)
return nodesToCopy
}, [getNodesReadOnly, store, workflowStore])
const handleNodePaste = useCallback((): undefined | Node[] => {
if (getNodesReadOnly())
return
const {
clipboardElements,
shortcutsDisabled,
showFeaturesPanel,
} = workflowStore.getState()
if (shortcutsDisabled || showFeaturesPanel)
return
const {
getNodes,
setNodes,
} = store.getState()
const nodesToPaste: Node[] = []
const nodes = getNodes()
for (const nodeToPaste of clipboardElements) {
const nodeType = nodeToPaste.data.type
const nodesWithSameType = nodes.filter(node => node.data.type === nodeType)
const newNode = generateNewNode({
data: {
...NODES_INITIAL_DATA[nodeType],
...nodeToPaste.data,
_connectedSourceHandleIds: [],
_connectedTargetHandleIds: [],
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
selected: true,
},
position: {
x: nodeToPaste.position.x + 10,
y: nodeToPaste.position.y + 10,
},
})
nodesToPaste.push(newNode)
}
setNodes([...nodes.map((n: Node) => ({ ...n, selected: false, data: { ...n.data, selected: false } })), ...nodesToPaste])
handleSyncWorkflowDraft()
return nodesToPaste
}, [getNodesReadOnly, handleSyncWorkflowDraft, store, t, workflowStore])
const handleNodeDuplicateSelected = useCallback(() => {
if (getNodesReadOnly())
return
handleNodeCopySelected()
handleNodePaste()
}, [getNodesReadOnly, handleNodeCopySelected, handleNodePaste])
const handleNodeCut = useCallback(() => {
if (getNodesReadOnly())
return
const nodesToCut = handleNodeCopySelected()
if (!nodesToCut)
return
for (const node of nodesToCut)
handleNodeDelete(node.id)
}, [getNodesReadOnly, handleNodeCopySelected, handleNodeDelete])
const handleNodeDeleteSelected = useCallback(() => {
if (getNodesReadOnly())
return
const {
shortcutsDisabled,
showFeaturesPanel,
} = workflowStore.getState()
if (shortcutsDisabled || showFeaturesPanel)
return
const {
getNodes,
edges,
} = store.getState()
const currentEdgeIndex = edges.findIndex(edge => edge.selected)
if (currentEdgeIndex > -1)
return
const nodes = getNodes()
const nodesToDelete = nodes.filter(node => node.data.selected)
if (!nodesToDelete)
return
for (const node of nodesToDelete)
handleNodeDelete(node.id)
}, [getNodesReadOnly, handleNodeDelete, store, workflowStore])
const handleNodeCancelRunningStatus = useCallback(() => {
const {
getNodes,
@@ -861,6 +739,173 @@ export const useNodesInteractions = () => {
setNodes(newNodes)
}, [store])
const handleNodeContextMenu = useCallback((e: MouseEvent, node: Node) => {
e.preventDefault()
const container = document.querySelector('#workflow-container')
const { x, y } = container!.getBoundingClientRect()
workflowStore.setState({
nodeMenu: {
top: e.clientY - y,
left: e.clientX - x,
nodeId: node.id,
},
})
handleNodeSelect(node.id)
}, [workflowStore, handleNodeSelect])
const handleNodesCopy = useCallback(() => {
if (getNodesReadOnly())
return
const {
setClipboardElements,
shortcutsDisabled,
showFeaturesPanel,
} = workflowStore.getState()
if (shortcutsDisabled || showFeaturesPanel)
return
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.End)
if (bundledNodes.length) {
setClipboardElements(bundledNodes)
return
}
const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.End)
if (selectedNode)
setClipboardElements([selectedNode])
}, [getNodesReadOnly, store, workflowStore])
const handleNodesPaste = useCallback(() => {
if (getNodesReadOnly())
return
const {
clipboardElements,
shortcutsDisabled,
showFeaturesPanel,
mousePosition,
} = workflowStore.getState()
if (shortcutsDisabled || showFeaturesPanel)
return
const {
getNodes,
setNodes,
} = store.getState()
const nodesToPaste: Node[] = []
const nodes = getNodes()
if (clipboardElements.length) {
const { x, y } = getTopLeftNodePosition(clipboardElements)
const { screenToFlowPosition } = reactflow
const currentPosition = screenToFlowPosition({ x: mousePosition.pageX, y: mousePosition.pageY })
const offsetX = currentPosition.x - x
const offsetY = currentPosition.y - y
clipboardElements.forEach((nodeToPaste, index) => {
const nodeType = nodeToPaste.data.type
const nodesWithSameType = nodes.filter(node => node.data.type === nodeType)
const newNode = generateNewNode({
data: {
...NODES_INITIAL_DATA[nodeType],
...nodeToPaste.data,
selected: false,
_isBundled: false,
_connectedSourceHandleIds: [],
_connectedTargetHandleIds: [],
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
},
position: {
x: nodeToPaste.position.x + offsetX,
y: nodeToPaste.position.y + offsetY,
},
})
newNode.id = newNode.id + index
nodesToPaste.push(newNode)
})
setNodes([...nodes, ...nodesToPaste])
handleSyncWorkflowDraft()
}
}, [t, getNodesReadOnly, store, workflowStore, handleSyncWorkflowDraft, reactflow])
const handleNodesDuplicate = useCallback(() => {
if (getNodesReadOnly())
return
const {
getNodes,
setNodes,
} = store.getState()
const nodes = getNodes()
const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.End)
if (selectedNode) {
const nodeType = selectedNode.data.type
const nodesWithSameType = nodes.filter(node => node.data.type === nodeType)
const newNode = generateNewNode({
data: {
...NODES_INITIAL_DATA[nodeType as BlockEnum],
...selectedNode.data,
selected: false,
_isBundled: false,
_connectedSourceHandleIds: [],
_connectedTargetHandleIds: [],
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${nodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${nodeType}`),
},
position: {
x: selectedNode.position.x + selectedNode.width! + 10,
y: selectedNode.position.y,
},
})
setNodes([...nodes, newNode])
}
}, [store, t, getNodesReadOnly])
const handleNodesDelete = useCallback(() => {
if (getNodesReadOnly())
return
const {
shortcutsDisabled,
showFeaturesPanel,
} = workflowStore.getState()
if (shortcutsDisabled || showFeaturesPanel)
return
const {
getNodes,
} = store.getState()
const nodes = getNodes()
const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.End)
if (bundledNodes.length) {
bundledNodes.forEach(node => handleNodeDelete(node.id))
return
}
const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.End)
if (selectedNode)
handleNodeDelete(selectedNode.id)
}, [store, workflowStore, getNodesReadOnly, handleNodeDelete])
return {
handleNodeDragStart,
handleNodeDrag,
@@ -875,12 +920,12 @@ export const useNodesInteractions = () => {
handleNodeDelete,
handleNodeChange,
handleNodeAdd,
handleNodeDuplicateSelected,
handleNodeCopySelected,
handleNodeCut,
handleNodeDeleteSelected,
handleNodePaste,
handleNodeCancelRunningStatus,
handleNodesCancelSelected,
handleNodeContextMenu,
handleNodesCopy,
handleNodesPaste,
handleNodesDuplicate,
handleNodesDelete,
}
}