Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
58d8b0dd
Commit
58d8b0dd
authored
Feb 26, 2024
by
StyleZhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
node handle connection line
parent
49f78bac
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
126 additions
and
48 deletions
+126
-48
index.tsx
web/app/components/workflow/block-selector/index.tsx
+16
-5
custom-connection-line.tsx
web/app/components/workflow/custom-connection-line.tsx
+39
-0
custom-edge.tsx
web/app/components/workflow/custom-edge.tsx
+2
-2
index.tsx
web/app/components/workflow/index.tsx
+2
-0
node-handle.tsx
...omponents/workflow/nodes/_base/components/node-handle.tsx
+64
-35
node.tsx
web/app/components/workflow/nodes/if-else/node.tsx
+2
-4
index.tsx
web/app/components/workflow/nodes/index.tsx
+1
-2
No files found.
web/app/components/workflow/block-selector/index.tsx
View file @
58d8b0dd
...
...
@@ -24,6 +24,8 @@ import {
}
from
'@/app/components/base/icons/src/vender/line/general'
type
NodeSelectorProps
=
{
open
?:
boolean
onOpenChange
?:
(
open
:
boolean
)
=>
void
onSelect
:
(
type
:
BlockEnum
)
=>
void
trigger
?:
(
open
:
boolean
)
=>
React
.
ReactNode
placement
?:
Placement
...
...
@@ -34,6 +36,8 @@ type NodeSelectorProps = {
asChild
?:
boolean
}
const
NodeSelector
:
FC
<
NodeSelectorProps
>
=
({
open
:
openFromProps
,
onOpenChange
,
onSelect
,
trigger
,
placement
=
'right'
,
...
...
@@ -43,18 +47,25 @@ const NodeSelector: FC<NodeSelectorProps> = ({
popupClassName
,
asChild
,
})
=>
{
const
[
open
,
setOpen
]
=
useState
(
false
)
const
[
localOpen
,
setLocalOpen
]
=
useState
(
false
)
const
open
=
openFromProps
===
undefined
?
localOpen
:
openFromProps
const
handleOpenChange
=
useCallback
((
newOpen
:
boolean
)
=>
{
setLocalOpen
(
newOpen
)
if
(
onOpenChange
)
onOpenChange
(
newOpen
)
},
[
onOpenChange
])
const
handleTrigger
=
useCallback
<
MouseEventHandler
<
HTMLDivElement
>>
((
e
)
=>
{
e
.
stopPropagation
()
setOpen
(
v
=>
!
v
)
},
[])
handleOpenChange
(
!
open
)
},
[
open
,
handleOpenChange
])
return
(
<
PortalToFollowElem
placement=
{
placement
}
offset=
{
offset
}
open=
{
open
}
onOpenChange=
{
setOpen
}
onOpenChange=
{
handleOpenChange
}
>
<
PortalToFollowElemTrigger
asChild=
{
asChild
}
...
...
@@ -67,7 +78,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
<
div
className=
{
`
flex items-center justify-center
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10
group-hover:flex
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10
${triggerClassName?.(open)}
`
}
style=
{
triggerStyle
}
...
...
web/app/components/workflow/custom-connection-line.tsx
0 → 100644
View file @
58d8b0dd
import
React
from
'react'
import
type
{
ConnectionLineComponentProps
}
from
'reactflow'
import
{
Position
,
getSimpleBezierPath
,
}
from
'reactflow'
const
CustomConnectionLine
=
({
fromX
,
fromY
,
toX
,
toY
}:
ConnectionLineComponentProps
)
=>
{
const
[
edgePath
,
]
=
getSimpleBezierPath
({
sourceX
:
fromX
,
sourceY
:
fromY
,
sourcePosition
:
Position
.
Right
,
targetX
:
toX
,
targetY
:
toY
,
targetPosition
:
Position
.
Left
,
})
return
(
<
g
>
<
path
fill=
"none"
stroke=
'#D0D5DD'
strokeWidth=
{
2
}
d=
{
edgePath
}
/>
<
rect
x=
{
toX
-
2
}
y=
{
toY
-
4
}
width=
{
2
}
height=
{
8
}
fill=
'#2970FF'
/>
</
g
>
)
}
export
default
CustomConnectionLine
web/app/components/workflow/custom-edge.tsx
View file @
58d8b0dd
...
...
@@ -22,10 +22,10 @@ const CustomEdge = ({
labelX
,
labelY
,
]
=
getSimpleBezierPath
({
sourceX
,
sourceX
:
sourceX
-
8
,
sourceY
,
sourcePosition
:
Position
.
Right
,
targetX
,
targetX
:
targetX
+
8
,
targetY
,
targetPosition
:
Position
.
Left
,
})
...
...
web/app/components/workflow/index.tsx
View file @
58d8b0dd
...
...
@@ -13,6 +13,7 @@ import Header from './header'
import
CustomNode
from
'./nodes'
import
ZoomInOut
from
'./zoom-in-out'
import
CustomEdge
from
'./custom-edge'
import
CustomConnectionLine
from
'./custom-connection-line'
import
Panel
from
'./panel'
import
type
{
Node
}
from
'./types'
...
...
@@ -69,6 +70,7 @@ const Workflow: FC<WorkflowProps> = memo(({
onEdgeMouseEnter=
{
handleEnterEdge
}
onEdgeMouseLeave=
{
handleLeaveEdge
}
multiSelectionKeyCode=
{
null
}
connectionLineComponent=
{
CustomConnectionLine
}
>
<
Background
gap=
{
[
14
,
14
]
}
...
...
web/app/components/workflow/nodes/_base/components/node-handle.tsx
View file @
58d8b0dd
import
{
useCallback
,
useState
,
}
from
'react'
import
type
{
NodeProps
}
from
'reactflow'
import
{
Handle
,
...
...
@@ -14,8 +18,16 @@ export const NodeTargetHandle = ({
id
,
data
,
}:
NodeProps
)
=>
{
const
[
open
,
setOpen
]
=
useState
(
false
)
const
store
=
useStoreApi
()
const
incomers
=
getIncomers
({
id
}
as
Node
,
store
.
getState
().
getNodes
(),
store
.
getState
().
edges
)
const
handleOpenChange
=
useCallback
((
v
:
boolean
)
=>
{
setOpen
(
v
)
},
[])
const
handleHandleClick
=
()
=>
{
if
(
incomers
.
length
===
0
&&
data
.
type
!==
BlockEnum
.
Start
)
handleOpenChange
(
!
open
)
}
return
(
<>
...
...
@@ -23,26 +35,31 @@ export const NodeTargetHandle = ({
type=
'target'
position=
{
Position
.
Left
}
className=
{
`
!top-[17px] !left-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1]
after:absolute after:w-0.5 after:h-2 after:-left-0.5 after:top-1 after:bg-primary-500
${(data.type === BlockEnum.Start || !incomers.length) && 'opacity-0'}
!top-[17px] !-left-2 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1]
after:absolute after:w-0.5 after:h-2 after:left-1.5 after:top-1 after:bg-primary-500
${!incomers.length && 'after:opacity-0'}
${data.type === BlockEnum.Start && 'opacity-0'}
`
}
isConnectable=
{
data
.
type
!==
BlockEnum
.
Start
}
/>
{
incomers
.
length
===
0
&&
data
.
type
!==
BlockEnum
.
Start
&&
(
<
BlockSelector
onSelect=
{
()
=>
{}
}
asChild
placement=
'left'
triggerClassName=
{
open
=>
`
hidden absolute -left-2 top-4
${data.hovering && '!flex'}
${open && '!flex'}
`
}
/>
)
}
onClick=
{
handleHandleClick
}
>
{
incomers
.
length
===
0
&&
data
.
type
!==
BlockEnum
.
Start
&&
(
<
BlockSelector
open=
{
open
}
onOpenChange=
{
handleOpenChange
}
onSelect=
{
()
=>
{}
}
asChild
placement=
'left'
triggerClassName=
{
open
=>
`
hidden absolute left-0 top-0 pointer-events-none
${data.hovering && '!flex'}
${open && '!flex'}
`
}
/>
)
}
</
Handle
>
</>
)
}
...
...
@@ -59,9 +76,17 @@ export const NodeSourceHandle = ({
handleClassName
,
nodeSelectorClassName
,
}:
NodeSourceHandleProps
)
=>
{
const
[
open
,
setOpen
]
=
useState
(
false
)
const
store
=
useStoreApi
()
const
connectedEdges
=
getConnectedEdges
([{
id
}
as
Node
],
store
.
getState
().
edges
)
const
connected
=
connectedEdges
.
find
(
edge
=>
edge
.
sourceHandle
===
handleId
)
const
handleOpenChange
=
useCallback
((
v
:
boolean
)
=>
{
setOpen
(
v
)
},
[])
const
handleHandleClick
=
()
=>
{
if
(
!
connected
)
handleOpenChange
(
!
open
)
}
return
(
<>
...
...
@@ -71,25 +96,29 @@ export const NodeSourceHandle = ({
position=
{
Position
.
Right
}
className=
{
`
!w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1]
after:absolute after:w-0.5 after:h-2 after:
-right-0
.5 after:top-1 after:bg-primary-500
${!connected && 'opacity-0'}
after:absolute after:w-0.5 after:h-2 after:
right-1
.5 after:top-1 after:bg-primary-500
${!connected && '
after:
opacity-0'}
${handleClassName}
`
}
/>
{
!
connected
&&
(
<
BlockSelector
onSelect=
{
()
=>
{}
}
asChild
triggerClassName=
{
open
=>
`
hidden
${nodeSelectorClassName}
${data.hovering && '!flex'}
${open && '!flex'}
`
}
/>
)
}
onClick=
{
handleHandleClick
}
>
{
!
connected
&&
(
<
BlockSelector
open=
{
open
}
onOpenChange=
{
handleOpenChange
}
onSelect=
{
()
=>
{}
}
asChild
triggerClassName=
{
open
=>
`
hidden absolute top-0 left-0 pointer-events-none
${nodeSelectorClassName}
${data.hovering && '!flex'}
${open && '!flex'}
`
}
/>
)
}
</
Handle
>
</>
)
}
web/app/components/workflow/nodes/if-else/node.tsx
View file @
58d8b0dd
...
...
@@ -25,8 +25,7 @@ const Node = (props: Pick<NodeProps, 'id' | 'data'>) => {
<
NodeSourceHandle
{
...
props
}
handleId=
'condition1'
handleClassName=
'!top-1 !-right-3'
nodeSelectorClassName=
'absolute top-1 -right-5'
handleClassName=
'!top-1 !-right-5'
/>
</
div
>
<
div
className=
'mb-0.5 leading-4 text-[10px] font-medium text-gray-500 uppercase'
>
{
t
(
`${i18nPrefix}.conditions`
)
}
</
div
>
...
...
@@ -50,8 +49,7 @@ const Node = (props: Pick<NodeProps, 'id' | 'data'>) => {
<
NodeSourceHandle
{
...
props
}
handleId=
'condition2'
handleClassName=
'!top-1 !-right-3'
nodeSelectorClassName=
'absolute top-1 -right-5'
handleClassName=
'!top-1 !-right-5'
/>
</
div
>
</
div
>
...
...
web/app/components/workflow/nodes/index.tsx
View file @
58d8b0dd
...
...
@@ -26,8 +26,7 @@ const CustomNode = memo((props: NodeProps) => {
nodeData
.
type
!==
BlockEnum
.
IfElse
&&
(
<
NodeSourceHandle
{
...
props
}
handleClassName=
'!top-[17px] !right-0'
nodeSelectorClassName=
'absolute -right-2 top-4'
handleClassName=
'!top-[17px] !-right-2'
handleId=
'source'
/>
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment