diff --git a/web/app/components/workflow/nodes/start/components/var-list.tsx b/web/app/components/workflow/nodes/start/components/var-list.tsx index 90a7809f01b629..801ab9d65623d2 100644 --- a/web/app/components/workflow/nodes/start/components/var-list.tsx +++ b/web/app/components/workflow/nodes/start/components/var-list.tsx @@ -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 = ({ +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 = ({ + index, + item, readonly, - list, + moveItem, onChange, + onRemove, + varKeys, }) => { + const ref = useRef(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({ + 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 ( +
+ +
+ ) +} + +const VarList: FC = ({ 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 ( -
+
{t('workflow.nodes.start.noVarTip')}
) } return ( -
- {list.map((item, index) => ( - item.variable)} - /> - ))} -
+ +
+ {list.map((item: InputVar, index: number) => ( + item.variable)} + /> + ))} +
+
) } + export default React.memo(VarList) diff --git a/web/package.json b/web/package.json index 25cf231a95ed63..174a86dd823404 100644 --- a/web/package.json +++ b/web/package.json @@ -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",