mirror of https://github.com/langgenius/dify.git
next step
This commit is contained in:
parent
76ff004ea5
commit
f1b868d5d9
|
|
@ -8,14 +8,26 @@ const initialNodes = [
|
|||
{
|
||||
id: '1',
|
||||
type: 'custom',
|
||||
// position: { x: 130, y: 130 },
|
||||
position: { x: 130, y: 130 },
|
||||
data: { type: 'start' },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'custom',
|
||||
position: { x: 434, y: 130 },
|
||||
data: { type: 'if-else' },
|
||||
data: {
|
||||
type: 'if-else',
|
||||
branches: [
|
||||
{
|
||||
id: 'if-true',
|
||||
name: 'IS TRUE',
|
||||
},
|
||||
{
|
||||
id: 'if-false',
|
||||
name: 'IS FALSE',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
|
|
@ -50,7 +62,7 @@ const initialEdges = [
|
|||
id: '1',
|
||||
type: 'custom',
|
||||
source: '2',
|
||||
sourceHandle: 'condition1',
|
||||
sourceHandle: 'if-true',
|
||||
target: '3',
|
||||
targetHandle: 'target',
|
||||
},
|
||||
|
|
@ -58,7 +70,7 @@ const initialEdges = [
|
|||
id: '2',
|
||||
type: 'custom',
|
||||
source: '2',
|
||||
sourceHandle: 'condition2',
|
||||
sourceHandle: 'if-false',
|
||||
target: '4',
|
||||
targetHandle: 'target',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -92,3 +92,7 @@ export const NodeInitialData = {
|
|||
desc: '',
|
||||
},
|
||||
}
|
||||
|
||||
export const NODE_WIDTH = 220
|
||||
export const X_OFFSET = 64
|
||||
export const Y_OFFSET = 39
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from 'reactflow'
|
||||
import type {
|
||||
BlockEnum,
|
||||
Node,
|
||||
SelectedNode,
|
||||
} from './types'
|
||||
import { NodeInitialData } from './constants'
|
||||
|
|
@ -123,13 +124,14 @@ export const useWorkflow = () => {
|
|||
setNodes(newNodes)
|
||||
}
|
||||
}, [setSelectedNode, store])
|
||||
|
||||
const handleUpdateNodeData = useCallback(({ id, data }: SelectedNode) => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
const currentNode = draft.find(n => n.id === id)!
|
||||
const currentNode = draft.find(node => node.id === id)!
|
||||
|
||||
currentNode.data = { ...currentNode.data, ...data }
|
||||
})
|
||||
|
|
@ -137,7 +139,7 @@ export const useWorkflow = () => {
|
|||
setSelectedNode({ id, data })
|
||||
}, [store, setSelectedNode])
|
||||
|
||||
const handleAddNextNode = useCallback((currentNodeId: string, nodeType: BlockEnum, branchId?: string) => {
|
||||
const handleAddNextNode = useCallback((currentNodeId: string, nodeType: BlockEnum, sourceHandle: string) => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
|
|
@ -146,35 +148,85 @@ export const useWorkflow = () => {
|
|||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const currentNode = nodes.find(node => node.id === currentNodeId)!
|
||||
const nextNode = {
|
||||
const nextNode: Node = {
|
||||
id: `${Date.now()}`,
|
||||
type: 'custom',
|
||||
data: { ...NodeInitialData[nodeType], selected: true },
|
||||
data: {
|
||||
...NodeInitialData[nodeType],
|
||||
selected: true,
|
||||
},
|
||||
position: {
|
||||
x: currentNode.position.x + 304,
|
||||
y: currentNode.position.y,
|
||||
},
|
||||
}
|
||||
const newEdge = {
|
||||
id: `${currentNode.id}-${nextNode.id}`,
|
||||
type: 'custom',
|
||||
source: currentNode.id,
|
||||
sourceHandle,
|
||||
target: nextNode.id,
|
||||
targetHandle: 'target',
|
||||
}
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((item) => {
|
||||
item.data = { ...item.data, selected: false }
|
||||
draft.forEach((node) => {
|
||||
node.data = { ...node.data, selected: false }
|
||||
})
|
||||
draft.push(nextNode)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft.push({
|
||||
id: `${currentNode.id}-${nextNode.id}`,
|
||||
type: 'custom',
|
||||
source: currentNode.id,
|
||||
sourceHandle: branchId || 'source',
|
||||
target: nextNode.id,
|
||||
targetHandle: 'target',
|
||||
})
|
||||
draft.push(newEdge)
|
||||
})
|
||||
setEdges(newEdges)
|
||||
setSelectedNode(nextNode)
|
||||
}, [store, setSelectedNode])
|
||||
|
||||
const handleChangeCurrentNode = useCallback((parentNodeId: string, currentNodeId: string, nodeType: BlockEnum, sourceHandle: string) => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const currentNode = nodes.find(node => node.id === currentNodeId)!
|
||||
const connectedEdges = getConnectedEdges([currentNode], edges)
|
||||
const newCurrentNode: Node = {
|
||||
id: `${Date.now()}`,
|
||||
type: 'custom',
|
||||
data: {
|
||||
...NodeInitialData[nodeType],
|
||||
selected: true,
|
||||
},
|
||||
position: {
|
||||
x: currentNode.position.x,
|
||||
y: currentNode.position.y,
|
||||
},
|
||||
}
|
||||
const newEdge = {
|
||||
id: `${parentNodeId}-${newCurrentNode.id}`,
|
||||
type: 'custom',
|
||||
source: parentNodeId,
|
||||
sourceHandle,
|
||||
target: newCurrentNode.id,
|
||||
targetHandle: 'target',
|
||||
}
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const index = draft.findIndex(node => node.id === currentNodeId)
|
||||
|
||||
draft.splice(index, 1, newCurrentNode)
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
const filtered = draft.filter(edge => !connectedEdges.find(connectedEdge => connectedEdge.id === edge.id))
|
||||
filtered.push(newEdge)
|
||||
|
||||
return filtered
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
||||
const handleInitialLayoutNodes = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
|
|
@ -191,6 +243,43 @@ export const useWorkflow = () => {
|
|||
}))
|
||||
}, [store])
|
||||
|
||||
const handleUpdateNodesPosition = useCallback(() => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
|
||||
const nodes = getNodes()
|
||||
const groups = nodes.reduce((acc, cur) => {
|
||||
const x = cur.data.position.x
|
||||
|
||||
if (!acc[x])
|
||||
acc[x] = [cur]
|
||||
else
|
||||
acc[x].push(cur)
|
||||
|
||||
return acc
|
||||
}, {} as Record<string, Node[]>)
|
||||
const heightMap: Record<string, number> = {}
|
||||
|
||||
Object.keys(groups).forEach((key) => {
|
||||
let baseHeight = 0
|
||||
groups[key].sort((a, b) => a.data.position!.y - b.data.position!.y).forEach((node) => {
|
||||
heightMap[node.id] = baseHeight
|
||||
baseHeight = node.height! + 39
|
||||
})
|
||||
})
|
||||
setNodes(produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
node.position = {
|
||||
...node.position,
|
||||
x: node.data.position.x * (220 + 64),
|
||||
y: heightMap[node.id],
|
||||
}
|
||||
})
|
||||
}))
|
||||
}, [store])
|
||||
|
||||
return {
|
||||
handleEnterNode,
|
||||
handleLeaveNode,
|
||||
|
|
@ -199,6 +288,8 @@ export const useWorkflow = () => {
|
|||
handleSelectNode,
|
||||
handleUpdateNodeData,
|
||||
handleAddNextNode,
|
||||
handleChangeCurrentNode,
|
||||
handleInitialLayoutNodes,
|
||||
handleUpdateNodesPosition,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import ReactFlow, {
|
|||
Background,
|
||||
ReactFlowProvider,
|
||||
useEdgesState,
|
||||
useNodesInitialized,
|
||||
// useNodesInitialized,
|
||||
useNodesState,
|
||||
} from 'reactflow'
|
||||
import 'reactflow/dist/style.css'
|
||||
|
|
@ -70,10 +70,10 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||
needUpdatePosition: true,
|
||||
}
|
||||
}, [initialNodes, initialEdges])
|
||||
const nodesInitialized = useNodesInitialized({
|
||||
includeHiddenNodes: true,
|
||||
})
|
||||
const [nodes] = useNodesState(initialData.nodes)
|
||||
// const nodesInitialized = useNodesInitialized({
|
||||
// includeHiddenNodes: true,
|
||||
// })
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialData.nodes)
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialData.edges)
|
||||
|
||||
const {
|
||||
|
|
@ -85,11 +85,10 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||
handleInitialLayoutNodes,
|
||||
} = useWorkflow()
|
||||
|
||||
useEffect(() => {
|
||||
console.log(nodesInitialized, '2')
|
||||
if (nodesInitialized && initialData.needUpdatePosition)
|
||||
handleInitialLayoutNodes()
|
||||
}, [nodesInitialized])
|
||||
// useEffect(() => {
|
||||
// if (nodesInitialized)
|
||||
// handleInitialLayoutNodes()
|
||||
// }, [nodesInitialized])
|
||||
|
||||
useEffect(() => {
|
||||
if (initialSelectedNodeId) {
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import {
|
||||
getOutgoers,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import BlockIcon from '../../../block-icon'
|
||||
import type { Node } from '../../../types'
|
||||
import { useStore } from '../../../store'
|
||||
import BlockSelector from '../../../block-selector'
|
||||
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
const NextStep = () => {
|
||||
const store = useStoreApi()
|
||||
const selectedNode = useStore(state => state.selectedNode)
|
||||
const outgoers: Node[] = getOutgoers(selectedNode as Node, store.getState().getNodes(), store.getState().edges)
|
||||
const svgHeight = outgoers.length > 1 ? (outgoers.length + 1) * 36 + 12 * outgoers.length : 36
|
||||
|
||||
const renderAddNextNodeTrigger = useCallback((open: boolean) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
relative flex items-center px-2 w-[328px] h-9 rounded-lg border border-dashed border-gray-200 bg-gray-50
|
||||
hover:bg-gray-100 text-xs text-gray-500 cursor-pointer
|
||||
${open && '!bg-gray-100'}
|
||||
`}
|
||||
>
|
||||
<div className='flex items-center justify-center mr-1.5 w-5 h-5 rounded-[5px] bg-gray-200'>
|
||||
<Plus className='w-3 h-3' />
|
||||
</div>
|
||||
SELECT NEXT BLOCK
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
|
||||
const renderChangeCurrentNodeTrigger = useCallback((open: boolean) => {
|
||||
return (
|
||||
<Button
|
||||
className={`
|
||||
hidden group-hover:flex px-2 py-0 h-6 bg-white text-xs text-gray-700 font-medium rounded-md
|
||||
${open && '!bg-gray-100 !flex'}
|
||||
`}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='flex py-1'>
|
||||
<div className='shrink-0 relative flex items-center justify-center w-9 h-9 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-xs'>
|
||||
<BlockIcon type={selectedNode!.data.type} />
|
||||
</div>
|
||||
<svg className='shrink-0 w-6' style={{ height: svgHeight }}>
|
||||
{
|
||||
outgoers.length < 2 && (
|
||||
<g>
|
||||
<path
|
||||
d='M0,18 L24,18'
|
||||
strokeWidth={1}
|
||||
stroke='#D0D5DD'
|
||||
fill='none'
|
||||
/>
|
||||
<rect
|
||||
x={0}
|
||||
y={16}
|
||||
width={1}
|
||||
height={4}
|
||||
fill='#98A2B3'
|
||||
/>
|
||||
<rect
|
||||
x={23}
|
||||
y={16}
|
||||
width={1}
|
||||
height={4}
|
||||
fill='#98A2B3'
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
{
|
||||
outgoers.length > 1 && (
|
||||
<g>
|
||||
{
|
||||
Array(outgoers.length + 1).fill(0).map((_, index) => (
|
||||
<g key={index}>
|
||||
{
|
||||
index === 0 && (
|
||||
<path
|
||||
d='M0,18 L24,18'
|
||||
strokeWidth={1}
|
||||
stroke='#D0D5DD'
|
||||
fill='none'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
index > 0 && (
|
||||
<path
|
||||
d={`M0,18 Q12,18 12,28 L12,${index * 48 + 18 - 10} Q12,${index * 48 + 18} 24,${index * 48 + 18}`}
|
||||
strokeWidth={1}
|
||||
stroke='#D0D5DD'
|
||||
fill='none'
|
||||
/>
|
||||
)
|
||||
}
|
||||
<rect
|
||||
x={23}
|
||||
y={index * 48 + 18 - 2}
|
||||
width={1}
|
||||
height={4}
|
||||
fill='#98A2B3'
|
||||
/>
|
||||
</g>
|
||||
))
|
||||
}
|
||||
<rect
|
||||
x={0}
|
||||
y={16}
|
||||
width={1}
|
||||
height={4}
|
||||
fill='#98A2B3'
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
</svg>
|
||||
<div className='grow'>
|
||||
{
|
||||
!!outgoers.length && outgoers.map(outgoer => (
|
||||
<div
|
||||
key={outgoer.id}
|
||||
className='relative group flex items-center mb-3 last-of-type:mb-0 px-2 h-9 rounded-lg border-[0.5px] border-gray-200 bg-white hover:bg-gray-50 shadow-xs text-xs text-gray-700 cursor-pointer'
|
||||
>
|
||||
<div className='absolute left-1 -top-[7.5px] flex items-center px-0.5 h-3 bg-white text-[10px] text-gray-500 font-semibold rounded-[5px]'>
|
||||
IS TRUE
|
||||
</div>
|
||||
<BlockIcon
|
||||
type={outgoer.data.type}
|
||||
className='shrink-0 mr-1.5'
|
||||
/>
|
||||
<div className='grow'>{outgoer.data.title}</div>
|
||||
<BlockSelector
|
||||
onSelect={() => {}}
|
||||
placement='top-end'
|
||||
offset={{
|
||||
mainAxis: 6,
|
||||
crossAxis: 8,
|
||||
}}
|
||||
trigger={renderChangeCurrentNodeTrigger}
|
||||
popupClassName='!w-[328px]'
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{
|
||||
(!outgoers.length || outgoers.length > 1) && (
|
||||
<BlockSelector
|
||||
onSelect={() => {}}
|
||||
placement='top'
|
||||
offset={0}
|
||||
trigger={renderAddNextNodeTrigger}
|
||||
popupClassName='!w-[328px]'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NextStep)
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import BlockSelector from '../../../../block-selector'
|
||||
import { useWorkflow } from '../../../../hooks'
|
||||
import type { BlockEnum } from '../../../../types'
|
||||
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
type AddProps = {
|
||||
nodeId: string
|
||||
sourceHandle: string
|
||||
branchName?: string
|
||||
}
|
||||
const Add = ({
|
||||
nodeId,
|
||||
sourceHandle,
|
||||
branchName,
|
||||
}: AddProps) => {
|
||||
const { handleAddNextNode } = useWorkflow()
|
||||
|
||||
const handleSelect = useCallback((type: BlockEnum) => {
|
||||
handleAddNextNode(nodeId, type, sourceHandle)
|
||||
}, [nodeId, sourceHandle, handleAddNextNode])
|
||||
|
||||
const renderTrigger = useCallback((open: boolean) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
relative flex items-center px-2 w-[328px] h-9 rounded-lg border border-dashed border-gray-200 bg-gray-50
|
||||
hover:bg-gray-100 text-xs text-gray-500 cursor-pointer
|
||||
${open && '!bg-gray-100'}
|
||||
`}
|
||||
>
|
||||
{
|
||||
branchName && (
|
||||
<div className='absolute left-1 -top-[7.5px] flex items-center px-0.5 h-3 bg-white text-[10px] text-gray-500 font-semibold rounded-[5px]'>
|
||||
{branchName.toLocaleUpperCase()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='flex items-center justify-center mr-1.5 w-5 h-5 rounded-[5px] bg-gray-200'>
|
||||
<Plus className='w-3 h-3' />
|
||||
</div>
|
||||
SELECT NEXT BLOCK
|
||||
</div>
|
||||
)
|
||||
}, [branchName])
|
||||
|
||||
return (
|
||||
<BlockSelector
|
||||
onSelect={handleSelect}
|
||||
placement='top'
|
||||
offset={0}
|
||||
trigger={renderTrigger}
|
||||
popupClassName='!w-[328px]'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Add)
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import { memo } from 'react'
|
||||
import {
|
||||
getConnectedEdges,
|
||||
getOutgoers,
|
||||
useEdges,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import BlockIcon from '../../../../block-icon'
|
||||
import type { Node } from '../../../../types'
|
||||
import { useStore } from '../../../../store'
|
||||
import Add from './add'
|
||||
import Item from './item'
|
||||
import Line from './line'
|
||||
|
||||
const NextStep = () => {
|
||||
const store = useStoreApi()
|
||||
const selectedNode = useStore(state => state.selectedNode)
|
||||
const branches = selectedNode?.data.branches
|
||||
const edges = useEdges()
|
||||
const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges)
|
||||
const connectedEdges = getConnectedEdges([selectedNode] as Node[], edges).filter(edge => edge.source === selectedNode!.id)
|
||||
|
||||
return (
|
||||
<div className='flex py-1'>
|
||||
<div className='shrink-0 relative flex items-center justify-center w-9 h-9 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-xs'>
|
||||
<BlockIcon type={selectedNode!.data.type} />
|
||||
</div>
|
||||
<Line linesNumber={branches ? branches.length : 1} />
|
||||
<div className='grow'>
|
||||
{
|
||||
!branches && !!outgoers.length && (
|
||||
<Item
|
||||
parentNodeId={selectedNode!.id}
|
||||
nodeId={outgoers[0].id}
|
||||
sourceHandle='source'
|
||||
data={outgoers[0].data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!branches && !outgoers.length && (
|
||||
<Add
|
||||
nodeId={selectedNode!.id}
|
||||
sourceHandle='source'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
branches?.length && (
|
||||
branches.map((branch) => {
|
||||
const connected = connectedEdges.find(edge => edge.sourceHandle === branch.id)
|
||||
const target = outgoers.find(outgoer => outgoer.id === connected?.target)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={branch.id}
|
||||
className='mb-3 last-of-type:mb-0'
|
||||
>
|
||||
{
|
||||
connected && (
|
||||
<Item
|
||||
data={target!.data!}
|
||||
parentNodeId={selectedNode!.id}
|
||||
nodeId={target!.id}
|
||||
sourceHandle={branch.id}
|
||||
branchName={branch.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!connected && (
|
||||
<Add
|
||||
key={branch.id}
|
||||
nodeId={selectedNode!.id}
|
||||
sourceHandle={branch.id}
|
||||
branchName={branch.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NextStep)
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import type {
|
||||
BlockEnum,
|
||||
CommonNodeType,
|
||||
} from '../../../../types'
|
||||
import BlockIcon from '../../../../block-icon'
|
||||
import BlockSelector from '../../../../block-selector'
|
||||
import { useWorkflow } from '../../../../hooks'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type ItemProps = {
|
||||
parentNodeId: string
|
||||
nodeId: string
|
||||
sourceHandle: string
|
||||
branchName?: string
|
||||
data: CommonNodeType
|
||||
}
|
||||
const Item = ({
|
||||
parentNodeId,
|
||||
nodeId,
|
||||
sourceHandle,
|
||||
branchName,
|
||||
data,
|
||||
}: ItemProps) => {
|
||||
const { handleChangeCurrentNode } = useWorkflow()
|
||||
const handleSelect = useCallback((type: BlockEnum) => {
|
||||
handleChangeCurrentNode(parentNodeId, nodeId, type, sourceHandle)
|
||||
}, [parentNodeId, nodeId, sourceHandle, handleChangeCurrentNode])
|
||||
const renderTrigger = useCallback((open: boolean) => {
|
||||
return (
|
||||
<Button
|
||||
className={`
|
||||
hidden group-hover:flex px-2 py-0 h-6 bg-white text-xs text-gray-700 font-medium rounded-md
|
||||
${open && '!bg-gray-100 !flex'}
|
||||
`}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='relative group flex items-center mb-3 last-of-type:mb-0 px-2 h-9 rounded-lg border-[0.5px] border-gray-200 bg-white hover:bg-gray-50 shadow-xs text-xs text-gray-700 cursor-pointer'
|
||||
>
|
||||
{
|
||||
branchName && (
|
||||
<div className='absolute left-1 -top-[7.5px] flex items-center px-0.5 h-3 bg-white text-[10px] text-gray-500 font-semibold rounded-[5px]'>
|
||||
{branchName.toLocaleUpperCase()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<BlockIcon
|
||||
type={data.type}
|
||||
className='shrink-0 mr-1.5'
|
||||
/>
|
||||
<div className='grow'>{data.title}</div>
|
||||
<BlockSelector
|
||||
onSelect={handleSelect}
|
||||
placement='top-end'
|
||||
offset={{
|
||||
mainAxis: 6,
|
||||
crossAxis: 8,
|
||||
}}
|
||||
trigger={renderTrigger}
|
||||
popupClassName='!w-[328px]'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Item)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { memo } from 'react'
|
||||
|
||||
type LineProps = {
|
||||
linesNumber: number
|
||||
}
|
||||
const Line = ({
|
||||
linesNumber,
|
||||
}: LineProps) => {
|
||||
const svgHeight = linesNumber * 36 + (linesNumber - 1) * 12
|
||||
|
||||
return (
|
||||
<svg className='shrink-0 w-6' style={{ height: svgHeight }}>
|
||||
{
|
||||
Array(linesNumber).fill(0).map((_, index) => (
|
||||
<g key={index}>
|
||||
{
|
||||
index === 0 && (
|
||||
<>
|
||||
<rect
|
||||
x={0}
|
||||
y={16}
|
||||
width={1}
|
||||
height={4}
|
||||
fill='#98A2B3'
|
||||
/>
|
||||
<path
|
||||
d='M0,18 L24,18'
|
||||
strokeWidth={1}
|
||||
stroke='#D0D5DD'
|
||||
fill='none'
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
index > 0 && (
|
||||
<path
|
||||
d={`M0,18 Q12,18 12,28 L12,${index * 48 + 18 - 10} Q12,${index * 48 + 18} 24,${index * 48 + 18}`}
|
||||
strokeWidth={1}
|
||||
stroke='#D0D5DD'
|
||||
fill='none'
|
||||
/>
|
||||
)
|
||||
}
|
||||
<rect
|
||||
x={23}
|
||||
y={index * 48 + 18 - 2}
|
||||
width={1}
|
||||
height={4}
|
||||
fill='#98A2B3'
|
||||
/>
|
||||
</g>
|
||||
))
|
||||
}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Line)
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
Handle,
|
||||
Position,
|
||||
getConnectedEdges,
|
||||
useStoreApi,
|
||||
useEdges,
|
||||
} from 'reactflow'
|
||||
import { BlockEnum } from '../../../types'
|
||||
import type { Node } from '../../../types'
|
||||
|
|
@ -15,7 +15,7 @@ import BlockSelector from '../../../block-selector'
|
|||
import { useWorkflow } from '../../../hooks'
|
||||
|
||||
type NodeHandleProps = {
|
||||
handleId?: string
|
||||
handleId: string
|
||||
handleClassName?: string
|
||||
nodeSelectorClassName?: string
|
||||
} & Pick<NodeProps, 'id' | 'data'>
|
||||
|
|
@ -28,9 +28,10 @@ export const NodeTargetHandle = ({
|
|||
nodeSelectorClassName,
|
||||
}: NodeHandleProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const store = useStoreApi()
|
||||
const connectedEdges = getConnectedEdges([{ id } as Node], store.getState().edges)
|
||||
const edges = useEdges()
|
||||
const connectedEdges = getConnectedEdges([{ id } as Node], edges)
|
||||
const connected = connectedEdges.find(edge => edge.targetHandle === handleId && edge.target === id)
|
||||
|
||||
const handleOpenChange = useCallback((v: boolean) => {
|
||||
setOpen(v)
|
||||
}, [])
|
||||
|
|
@ -86,8 +87,8 @@ export const NodeSourceHandle = ({
|
|||
}: NodeHandleProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { handleAddNextNode } = useWorkflow()
|
||||
const store = useStoreApi()
|
||||
const connectedEdges = getConnectedEdges([{ id } as Node], store.getState().edges)
|
||||
const edges = useEdges()
|
||||
const connectedEdges = getConnectedEdges([{ id } as Node], edges)
|
||||
const connected = connectedEdges.find(edge => edge.sourceHandle === handleId && edge.source === id)
|
||||
const handleOpenChange = useCallback((v: boolean) => {
|
||||
setOpen(v)
|
||||
|
|
@ -97,8 +98,8 @@ export const NodeSourceHandle = ({
|
|||
handleOpenChange(!open)
|
||||
}
|
||||
const handleSelect = useCallback((type: BlockEnum) => {
|
||||
handleAddNextNode(id, type)
|
||||
}, [handleAddNextNode, id])
|
||||
handleAddNextNode(id, type, handleId)
|
||||
}, [handleAddNextNode, id, handleId])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const IfElseNode: FC<Pick<NodeProps, 'id' | 'data'>> = (props) => {
|
|||
<div className='w-full text-right text-gray-700 text-xs font-semibold'>IF</div>
|
||||
<NodeSourceHandle
|
||||
{...props}
|
||||
handleId='condition1'
|
||||
handleId='if-true'
|
||||
handleClassName='!top-1 !-right-[21px]'
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -41,7 +41,7 @@ const IfElseNode: FC<Pick<NodeProps, 'id' | 'data'>> = (props) => {
|
|||
<div className='w-full text-right text-gray-700 text-xs font-semibold'>ELSE</div>
|
||||
<NodeSourceHandle
|
||||
{...props}
|
||||
handleId='condition2'
|
||||
handleId='if-false'
|
||||
handleClassName='!top-1 !-right-[21px]'
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue