Commit 58d8b0dd authored by StyleZhang's avatar StyleZhang

node handle connection line

parent 49f78bac
......@@ -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}
......
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
......@@ -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,
})
......
......@@ -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]}
......
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>
</>
)
}
......@@ -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>
......
......@@ -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'
/>
)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment