Skip to content

Commit

Permalink
feat: The list of input field variables in the Start node panel can b…
Browse files Browse the repository at this point in the history
…e dragged and dropped
  • Loading branch information
ldq1220 committed Dec 21, 2024
1 parent 6ded06c commit 770d3ce
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 38 deletions.
194 changes: 156 additions & 38 deletions web/app/components/workflow/nodes/start/components/var-list.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,188 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import React, { useCallback, useRef } from 'react'
import produce from 'immer'
import { useTranslation } from 'react-i18next'
import { DndProvider, useDrag, useDrop } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import VarItem from './var-item'
import { ChangeType, type InputVar, type MoreInfo } from '@/app/components/workflow/types'
import { type InputVar, type MoreInfo } from '@/app/components/workflow/types'

type Props = {
readonly: boolean
list: InputVar[]
onChange: (list: InputVar[], moreInfo?: { index: number; payload: MoreInfo }) => void
onChange: (newList: InputVar[], moreInfo?: any) => void
}

const ItemTypes = {
VAR_ITEM: 'VAR_ITEM',
}

type DragItem = {
index: number
id: string
type: string
}

const VarList: FC<Props> = ({
type DraggableVarItemProps = {
index: number
item: InputVar
readonly: boolean
moveItem: (dragIndex: number, hoverIndex: number) => void
onChange: (payload: InputVar, moreInfo?: MoreInfo) => void
onRemove: () => void
varKeys: string[]
}

const DraggableVarItem: FC<DraggableVarItemProps> = ({
index,
item,
readonly,
list,
moveItem,
onChange,
onRemove,
varKeys,
}) => {
const ref = useRef<HTMLDivElement>(null)

const [{ isDragging }, drag] = useDrag({
type: ItemTypes.VAR_ITEM,
item: { type: ItemTypes.VAR_ITEM, id: item.variable, index },
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
canDrag: !readonly,
})

const [, drop] = useDrop<DragItem, void, {}>({
accept: ItemTypes.VAR_ITEM,
hover(item: DragItem, monitor) {
if (!ref.current)
return

const dragIndex = item.index
const hoverIndex = index

if (dragIndex === hoverIndex)
return

const hoverBoundingRect = ref.current?.getBoundingClientRect()
const hoverMiddleY
= (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
const clientOffset = monitor.getClientOffset()
const hoverClientY = clientOffset!.y - hoverBoundingRect.top

if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY)
return
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY)
return

moveItem(dragIndex, hoverIndex)
item.index = hoverIndex
},
})

drag(drop(ref))

return (
<div
ref={ref}
style={{ opacity: isDragging ? 0.5 : 1 }}
className="transition-opacity duration-200"
>
<VarItem
readonly={readonly}
payload={item}
onChange={onChange}
onRemove={onRemove}
varKeys={varKeys}
/>
</div>
)
}

const VarList: FC<Props> = ({ readonly, list, onChange }) => {
const { t } = useTranslation()

const handleVarChange = useCallback((index: number) => {
return (payload: InputVar, moreInfo?: MoreInfo) => {
const newList = produce(list, (draft) => {
draft[index] = payload
})
onChange(newList, moreInfo ? { index, payload: moreInfo } : undefined)
}
}, [list, onChange])

const handleVarRemove = useCallback((index: number) => {
return () => {
const newList = produce(list, (draft) => {
draft.splice(index, 1)
})
onChange(newList, {
index,
payload: {
type: ChangeType.remove,
const handleVarChange = useCallback(
(index: number) => {
return (payload: InputVar, moreInfo?: MoreInfo) => {
const newList = produce(list, (draft: InputVar[]) => {
draft[index] = payload
})
onChange(
newList,
moreInfo
? ({ index, payload: moreInfo } as unknown as MoreInfo)
: undefined,
)
}
},
[list, onChange],
)

const handleVarRemove = useCallback(
(index: number) => {
return () => {
const newList = produce(list, (draft: InputVar[]) => {
draft.splice(index, 1)
})
onChange(newList, {
type: 'remove',
payload: {
beforeKey: list[index].variable,
},
})
}
},
[list, onChange],
)

const moveItem = useCallback(
(dragIndex: number, hoverIndex: number) => {
const newList = produce(list, (draft: InputVar[]) => {
const dragItem = draft[dragIndex]
draft.splice(dragIndex, 1)
draft.splice(hoverIndex, 0, dragItem)
})

onChange(newList, {
type: 'move',
payload: {
beforeKey: list[dragIndex].variable,
afterKey: list[hoverIndex].variable,
},
})
}
}, [list, onChange])
},
[list, onChange],
)

if (list.length === 0) {
return (
<div className='flex rounded-md bg-gray-50 items-center h-[42px] justify-center leading-[18px] text-xs font-normal text-gray-500'>
<div className="flex rounded-md bg-gray-50 items-center h-[42px] justify-center leading-[18px] text-xs font-normal text-gray-500">
{t('workflow.nodes.start.noVarTip')}
</div>
)
}

return (
<div className='space-y-1'>
{list.map((item, index) => (
<VarItem
key={index}
readonly={readonly}
payload={item}
onChange={handleVarChange(index)}
onRemove={handleVarRemove(index)}
varKeys={list.map(item => item.variable)}
/>
))}
</div>
<DndProvider backend={HTML5Backend}>
<div className="space-y-1">
{list.map((item: InputVar, index: number) => (
<DraggableVarItem
key={item.variable}
index={index}
item={item}
readonly={readonly}
moveItem={moveItem}
onChange={handleVarChange(index)}
onRemove={handleVarRemove(index)}
varKeys={list.map((item: InputVar) => item.variable)}
/>
))}
</div>
</DndProvider>
)
}

export default React.memo(VarList)
2 changes: 2 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
"rc-textarea": "^1.5.2",
"react": "~18.2.0",
"react-18-input-autosize": "^3.0.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "~18.2.0",
"react-easy-crop": "^5.0.8",
"react-error-boundary": "^4.0.2",
Expand Down

0 comments on commit 770d3ce

Please sign in to comment.