diff --git a/packages/web/components/common/DndDrag/index.tsx b/packages/web/components/common/DndDrag/index.tsx index fc64f3a3e..43930e17c 100644 --- a/packages/web/components/common/DndDrag/index.tsx +++ b/packages/web/components/common/DndDrag/index.tsx @@ -1,23 +1,35 @@ -import { Box } from '@chakra-ui/react'; -import React, { useState } from 'react'; +import { Box, Tbody } from '@chakra-ui/react'; +import React, { ReactNode, useState } from 'react'; import { DragDropContext, - DroppableProps, Droppable, DraggableChildrenFn, DragStart, - DropResult + DropResult, + DroppableProvided, + DroppableStateSnapshot } from 'react-beautiful-dnd'; export * from 'react-beautiful-dnd'; type Props = { onDragEndCb: (result: T[]) => void; renderClone?: DraggableChildrenFn; - children: DroppableProps['children']; + children: + | ((provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ReactNode) + | ReactNode; dataList: T[]; + isTable?: boolean; + zoom?: number; }; -function DndDrag({ children, renderClone, onDragEndCb, dataList }: Props) { +function DndDrag({ + children, + renderClone, + onDragEndCb, + dataList, + isTable = false, + zoom = 1 +}: Props) { const [draggingItemHeight, setDraggingItemHeight] = useState(0); const onDragStart = (start: DragStart) => { @@ -45,10 +57,15 @@ function DndDrag({ children, renderClone, onDragEndCb, dataList }: Props) {(provided, snapshot) => { - return ( + return isTable ? ( + + {typeof children !== 'function' && children} + {snapshot.isDraggingOver && } + + ) : ( - {children(provided, snapshot)} - {snapshot.isDraggingOver && } + {typeof children === 'function' && children(provided, snapshot)} + {snapshot.isDraggingOver && } ); }} diff --git a/projects/app/src/components/core/app/VariableEdit.tsx b/projects/app/src/components/core/app/VariableEdit.tsx index 30ae8d83b..5a0f791bb 100644 --- a/projects/app/src/components/core/app/VariableEdit.tsx +++ b/projects/app/src/components/core/app/VariableEdit.tsx @@ -1,11 +1,10 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Box, Button, Flex, Table, Thead, - Tbody, Tr, Th, Td, @@ -20,9 +19,8 @@ import { } from '@fastgpt/global/core/workflow/constants'; import type { VariableItemType } from '@fastgpt/global/core/app/type.d'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import { useForm } from 'react-hook-form'; +import { useForm, UseFormReset } from 'react-hook-form'; import { customAlphabet } from 'nanoid'; -const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); import MyModal from '@fastgpt/web/components/common/MyModal'; import { useTranslation } from 'next-i18next'; import { useToast } from '@fastgpt/web/hooks/useToast'; @@ -32,6 +30,14 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import InputTypeConfig from '@/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig'; import MyIconButton from '@fastgpt/web/components/common/Icon/button'; +import { useReactFlow, useViewport } from 'reactflow'; +import DndDrag, { + Draggable, + DraggableProvided, + DraggableStateSnapshot +} from '@fastgpt/web/components/common/DndDrag'; + +const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); export const defaultVariable: VariableItemType = { id: nanoid(), @@ -61,6 +67,7 @@ const VariableEdit = ({ }) => { const { t } = useTranslation(); const { toast } = useToast(); + const { zoom } = useViewport(); const form = useForm(); const { setValue, reset, watch, getValues } = form; @@ -166,7 +173,7 @@ const VariableEdit = ({ ); return ( - + {/* Row box */} @@ -200,48 +207,40 @@ const VariableEdit = ({ {t('common:common.Operation')} - + + onDragEndCb={(list) => { + onChange(list); + }} + dataList={formatVariables} + renderClone={(provided, snapshot, rubric) => ( + + )} + isTable + zoom={zoom} + > {formatVariables.map((item, index) => ( - - - - - {item.key} - - - - - {item.required ? ( - - ) : ( - '' - )} - - - - - { - const formattedItem = { - ...item, - list: item.enums || [] - }; - reset(formattedItem); - }} - /> - - onChange(variables.filter((variable) => variable.id !== item.id)) - } - /> - - - + + {(provided, snapshot) => ( + + )} + ))} - + )} @@ -339,4 +338,65 @@ const VariableEdit = ({ ); }; +const TableItem = ({ + provided, + snapshot, + item, + reset, + onChange, + variables +}: { + provided: DraggableProvided; + snapshot: DraggableStateSnapshot; + item: VariableItemType & { + icon?: string; + }; + reset: UseFormReset; + onChange: (data: VariableItemType[]) => void; + variables: VariableItemType[]; +}) => { + return ( + + + + + {item.key} + + + + + {item.required ? : ''} + + + + + { + const formattedItem = { + ...item, + list: item.enums || [] + }; + reset(formattedItem); + }} + /> + onChange(variables.filter((variable) => variable.id !== item.id))} + /> + + + + ); +}; + export default React.memo(VariableEdit); diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeFormInput/index.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeFormInput/index.tsx index 7e0d7bc7d..27b0b552f 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeFormInput/index.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeFormInput/index.tsx @@ -1,6 +1,6 @@ import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node'; import React, { useMemo, useState } from 'react'; -import { NodeProps } from 'reactflow'; +import { NodeProps, useViewport } from 'reactflow'; import NodeCard from '../render/NodeCard'; import Container from '../../components/Container'; import RenderInput from '../render/RenderInput'; @@ -38,11 +38,17 @@ import InputFormEditModal, { defaultFormInput } from './InputFormEditModal'; import RenderOutput from '../render/RenderOutput'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import MyIconButton from '@fastgpt/web/components/common/Icon/button'; +import DndDrag, { + Draggable, + DraggableProvided, + DraggableStateSnapshot +} from '@fastgpt/web/components/common/DndDrag'; const NodeFormInput = ({ data, selected }: NodeProps) => { const { nodeId, inputs, outputs } = data; const { t } = useTranslation(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const { zoom } = useViewport(); const [editField, setEditField] = useState(); @@ -159,53 +165,79 @@ const NodeFormInput = ({ data, selected }: NodeProps) => { {t('user:operations')} - + + onDragEndCb={(list) => { + const sortedOutputs = [ + outputs[0], + ...outputs.slice(1).sort((a, b) => { + const aIndex = list.findIndex((item) => item.key === a.key); + const bIndex = list.findIndex((item) => item.key === b.key); + return aIndex - bIndex; + }) + ]; + + onChangeNode({ + nodeId, + type: 'updateInput', + key, + value: { + ...props, + key, + value: list + } + }); + onChangeNode({ + nodeId, + type: 'attr', + key: 'outputs', + value: sortedOutputs + }); + }} + dataList={inputs} + renderClone={(provided, snapshot, rubric) => { + const item = inputs[rubric.source.index]; + const icon = FlowNodeInputMap[item.type as FlowNodeInputTypeEnum]?.icon; + + return ( + + ); + }} + isTable + zoom={zoom} + > {inputs.map((item, index) => { const icon = FlowNodeInputMap[item.type as FlowNodeInputTypeEnum]?.icon; return ( - - - - {!!icon && ( - - )} - {item.label} - - - {item.description || '-'} - - {item.required ? ( - - - - ) : ( - '-' - )} - - - - setEditField(item)} - /> - onDelete(item.key)} - /> - - - + + {(provided, snapshot) => ( + + )} + ); })} - + ); } }), - [t, editField, onChangeNode, nodeId, outputs] + [t, editField, zoom, onChangeNode, nodeId, outputs] ); return ( @@ -223,3 +255,54 @@ const NodeFormInput = ({ data, selected }: NodeProps) => { }; export default React.memo(NodeFormInput); + +const TableItem = ({ + provided, + snapshot, + item, + icon, + setEditField, + onDelete +}: { + provided: DraggableProvided; + snapshot: DraggableStateSnapshot; + item: UserInputFormItemType; + icon: string; + setEditField: (item: UserInputFormItemType) => void; + onDelete: (valueKey: string) => void; +}) => { + return ( + + + + {!!icon && } + {item.label} + + + {item.description || '-'} + + {item.required ? ( + + + + ) : ( + '-' + )} + + + + setEditField(item)} /> + onDelete(item.key)} /> + + + + ); +}; diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeIfElse/index.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeIfElse/index.tsx index 882739d23..911681819 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeIfElse/index.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeIfElse/index.tsx @@ -3,7 +3,7 @@ import NodeCard from '../render/NodeCard'; import { useTranslation } from 'next-i18next'; import { Box, Button, Flex } from '@chakra-ui/react'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { NodeProps, Position } from 'reactflow'; +import { NodeProps, Position, useViewport } from 'reactflow'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node'; import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type'; import { useContextSelector } from 'use-context-selector'; @@ -18,6 +18,7 @@ import { IfElseResultEnum } from '@fastgpt/global/core/workflow/template/system/ const NodeIfElse = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, inputs = [] } = data; + const { zoom } = useViewport(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const elseHandleId = getHandleId(nodeId, 'source', IfElseResultEnum.ELSE); @@ -63,6 +64,7 @@ const NodeIfElse = ({ data, selected }: NodeProps) => { nodeId={nodeId} /> )} + zoom={zoom} > {(provided) => ( diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx index e3fff3ec3..25a880e2f 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/NodeUserSelect.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { NodeProps, Position } from 'reactflow'; +import { NodeProps, Position, useViewport } from 'reactflow'; import { Box, Button, HStack, Input } from '@chakra-ui/react'; import NodeCard from './render/NodeCard'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d'; @@ -18,86 +18,72 @@ import { WorkflowContext } from '../../context'; import { UserSelectOptionItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import IOTitle from '../components/IOTitle'; import RenderOutput from './render/RenderOutput'; +import DndDrag, { + Draggable, + DraggableProvided, + DraggableStateSnapshot +} from '@fastgpt/web/components/common/DndDrag'; const NodeUserSelect = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, inputs, outputs } = data; const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const { zoom } = useViewport(); const CustomComponent = useMemo( () => ({ - [NodeInputKeyEnum.userSelectOptions]: ({ - key: optionKey, - value = [], - ...props - }: FlowNodeInputItemType) => { + [NodeInputKeyEnum.userSelectOptions]: (v: FlowNodeInputItemType) => { + const { key: optionKey, value, ...props } = v; const options = value as UserSelectOptionItemType[]; + return ( - {options.map((item, i) => ( - - - - { - onChangeNode({ - nodeId, - type: 'updateInput', - key: optionKey, - value: { - ...props, - key: optionKey, - value: options.filter((input) => input.key !== item.key) - } - }); - }} - /> - - - {t('common:option') + (i + 1)} - - - - { - const newVal = options.map((val) => - val.key === item.key - ? { - ...val, - value: e.target.value - } - : val - ); - onChangeNode({ - nodeId, - type: 'updateInput', - key: optionKey, - value: { - ...props, - key: optionKey, - value: newVal - } - }); - }} - /> - + + onDragEndCb={(list) => { + onChangeNode({ + nodeId, + type: 'updateInput', + key: optionKey, + value: { + ...props, + key: optionKey, + value: list + } + }); + }} + dataList={options} + renderClone={(provided, snapshot, rubric) => ( + + )} + zoom={zoom} + > + {(provided) => ( + + {options.map((item, i) => ( + + {(provided, snapshot) => ( + + )} + + ))} - - ))} + )} +