Skip to content

Commit 95e3d4c

Browse files
committed
feat: drag drop node
1 parent 6cbdb99 commit 95e3d4c

File tree

21 files changed

+485
-194
lines changed

21 files changed

+485
-194
lines changed

.vscode/settings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"editor.codeActionsOnSave": {
3+
"source.fixAll.eslint": "always",
4+
"source.organizeImports": "always"
5+
}
6+
}

client/package-lock.json

Lines changed: 29 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@tanstack/react-router": "^1.16.6",
4949
"@tanstack/react-table": "^8.12.0",
5050
"@tanstack/router-devtools": "^1.16.6",
51+
"@xyflow/react": "^12.0.0-next.11",
5152
"axios": "^1.6.7",
5253
"class-variance-authority": "^0.7.0",
5354
"clsx": "^2.1.0",

client/src/components/pages/chatbot-detail/flow-provider.tsx

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { Button } from '@/components/ui'
2+
import { cn } from '@/lib/utils'
3+
import { EActionTypes } from '@/types/flow'
4+
import { X } from 'lucide-react'
5+
import { DragEvent, useCallback, useLayoutEffect, useRef } from 'react'
6+
import { useFlowCtx } from '.'
7+
import { ACTIONS, MAP_ACTION_TO_LABEL } from './constant'
8+
9+
export const Actions = () => {
10+
const { openActions, toggleActions } = useFlowCtx()
11+
const actionsRef = useRef<HTMLDivElement>(null)
12+
13+
const handleDragStart = useCallback(
14+
(e: DragEvent<HTMLDivElement>, type: EActionTypes) => {
15+
console.log('drag start')
16+
e.dataTransfer.setData('application/reactflow', type)
17+
e.dataTransfer.effectAllowed = 'move'
18+
},
19+
[],
20+
)
21+
22+
useLayoutEffect(() => {
23+
const toolbar = document.querySelector('#flow-toolbar')
24+
25+
if (!toolbar || !actionsRef.current) return
26+
27+
const toolbarRect = toolbar.getBoundingClientRect()
28+
29+
actionsRef.current.style.top = `${toolbarRect.bottom + 16}px`
30+
}, [])
31+
32+
return (
33+
<div
34+
className={cn(
35+
'fixed right-4 z-10 rounded-md bg-card shadow w-80 transition-transform duration-300 bottom-4 p-4 flex flex-col gap-4',
36+
{
37+
'translate-x-[110%]': !openActions,
38+
},
39+
)}
40+
ref={actionsRef}
41+
>
42+
<div>
43+
<div className='flex items-center justify-between'>
44+
<span className='text-lg font-semibold leading-none tracking-tight'>
45+
Actions
46+
</span>
47+
<Button
48+
className='w-4 h-4 p-0 hover:bg-transparent'
49+
variant='ghost'
50+
onClick={toggleActions}
51+
>
52+
<X className='w-4 h-4' />
53+
</Button>
54+
</div>
55+
<p className='text-sm text-muted-foreground'>
56+
Drag and drop actions to create your node
57+
</p>
58+
</div>
59+
<div className='grid gap-2'>
60+
<div
61+
onDragStart={(e) => handleDragStart(e, EActionTypes.CHECK_VARIABLES)}
62+
>
63+
hi
64+
</div>
65+
{ACTIONS.map((action) => {
66+
return (
67+
<div
68+
key={action.type}
69+
className='flex items-center gap-2 relative rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground cursor-grab'
70+
onDragStart={(e) => handleDragStart(e, action.type)}
71+
draggable
72+
>
73+
{action.Icon()}
74+
<span>{MAP_ACTION_TO_LABEL[action.type]}</span>
75+
</div>
76+
)
77+
})}
78+
</div>
79+
</div>
80+
)
81+
}
82+
83+
export default Actions
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { EActionTypes } from '@/types/flow'
2+
import {
3+
GitPullRequest,
4+
HelpCircle,
5+
MessageSquareMore,
6+
Variable,
7+
} from 'lucide-react'
8+
9+
type TAction = {
10+
type: EActionTypes
11+
Icon: () => JSX.Element
12+
}
13+
14+
export const ACTIONS: TAction[] = [
15+
{
16+
type: EActionTypes.MESSAGE,
17+
Icon: () => <MessageSquareMore className='w-5 h-5' />,
18+
},
19+
{
20+
type: EActionTypes.PROMPT_AND_COLLECT,
21+
Icon: () => <HelpCircle className='w-5 h-5' />,
22+
},
23+
{
24+
type: EActionTypes.CHECK_VARIABLES,
25+
Icon: () => <Variable className='w-5 h-5' />,
26+
},
27+
{
28+
type: EActionTypes.HTTP_REQUEST,
29+
Icon: () => <GitPullRequest className='w-5 h-5' />,
30+
},
31+
]
32+
33+
export const MAP_ACTION_TO_LABEL: Record<EActionTypes, string> = {
34+
[EActionTypes.MESSAGE]: 'Message',
35+
[EActionTypes.PROMPT_AND_COLLECT]: 'Prompt & Collect',
36+
[EActionTypes.CHECK_VARIABLES]: 'Check Variables',
37+
[EActionTypes.HTTP_REQUEST]: 'HTTP Request',
38+
[EActionTypes.FALLBACK]: 'Fallback',
39+
[EActionTypes.START]: 'Start',
40+
[EActionTypes.SUB_FLOW]: 'Sub Flow',
41+
[EActionTypes.SEND_MAIL]: 'Send Mail',
42+
}

client/src/components/pages/chatbot-detail/controls.tsx renamed to client/src/components/pages/flow-detail/controls.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Button } from '@/components/ui'
22
import { Maximize, Minus, Plus } from 'lucide-react'
33
import { useLayoutEffect } from 'react'
4-
import { Panel, useViewport, useReactFlow } from 'reactflow'
4+
import { Panel, useReactFlow, useViewport } from 'reactflow'
55

66
const MAX_ZOOM = 2
77
const MIN_ZOOM = 0.5
@@ -38,7 +38,7 @@ export const Controls = () => {
3838

3939
useLayoutEffect(() => {
4040
setViewport({
41-
zoom: 1.5,
41+
zoom: 1.25,
4242
x: 0,
4343
y: 0,
4444
})

client/src/components/pages/chatbot-detail/edge.tsx renamed to client/src/components/pages/flow-detail/edge.tsx

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ export const Edge = ({
1414
targetPosition,
1515
style = {},
1616
markerEnd,
17-
}: EdgeProps) => {
17+
data = {
18+
deletable: true,
19+
},
20+
}: EdgeProps<{
21+
deletable?: boolean
22+
}>) => {
1823
const { setEdges } = useReactFlow()
1924
const [edgePath, labelX, labelY] = getBezierPath({
2025
sourceX,
@@ -25,7 +30,9 @@ export const Edge = ({
2530
targetPosition,
2631
})
2732

28-
const onEdgeClick = () => {
33+
const handleDelete = () => {
34+
if (!data?.deletable) return
35+
2936
setEdges((edges) => edges.filter((edge) => edge.id !== id))
3037
}
3138

@@ -39,23 +46,25 @@ export const Edge = ({
3946
d={edgePath}
4047
markerEnd={markerEnd}
4148
/>
42-
<foreignObject
43-
width={foreignObjectSize}
44-
height={foreignObjectSize}
45-
x={labelX - foreignObjectSize / 2}
46-
y={labelY - foreignObjectSize / 2}
47-
className='edgebutton flex items-center justify-center '
48-
requiredExtensions='http://www.w3.org/1999/xhtml'
49-
>
50-
<Button
51-
size='icon'
52-
className='w-4 h-4 opacity-0 scale-50 invisible btn'
53-
variant='destructive'
54-
onClick={onEdgeClick}
49+
{data?.deletable && (
50+
<foreignObject
51+
width={foreignObjectSize}
52+
height={foreignObjectSize}
53+
x={labelX - foreignObjectSize / 2}
54+
y={labelY - foreignObjectSize / 2}
55+
className='edgebutton flex items-center justify-center '
56+
requiredExtensions='http://www.w3.org/1999/xhtml'
5557
>
56-
<X className='w-2 h-2' />
57-
</Button>
58-
</foreignObject>
58+
<Button
59+
size='icon'
60+
className='w-4 h-4 opacity-0 scale-50 invisible btn'
61+
variant='destructive'
62+
onClick={handleDelete}
63+
>
64+
<X className='w-2 h-2' />
65+
</Button>
66+
</foreignObject>
67+
)}
5968
</>
6069
)
6170
}

0 commit comments

Comments
 (0)