feat: enhance FieldList component with sorting and dynamic input field management

This commit is contained in:
twwu 2025-04-22 12:56:22 +08:00
parent 5b8c43052e
commit efb27eb443
2 changed files with 87 additions and 37 deletions

View File

@ -2,12 +2,14 @@ import type { InputVar } from '@/app/components/workflow/types'
import { RiAddLine } from '@remixicon/react'
import FieldItem from './field-item'
import cn from '@/utils/classnames'
import { useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import InputFieldEditor from '../editor'
import { ReactSortable } from 'react-sortablejs'
type FieldListProps = {
LabelRightContent: React.ReactNode
inputFields?: InputVar[]
inputFields: InputVar[]
handleInputFieldsChange: (value: InputVar[]) => void
readonly?: boolean
labelClassName?: string
}
@ -15,22 +17,44 @@ type FieldListProps = {
const FieldList = ({
LabelRightContent,
inputFields,
handleInputFieldsChange,
readonly,
labelClassName,
}: FieldListProps) => {
const [showInputFieldEditor, setShowInputFieldEditor] = useState(false)
const optionList = useMemo(() => {
return inputFields.map((content, index) => {
return ({
id: index,
name: content.variable,
})
})
}, [inputFields])
const handleListSortChange = useCallback((list: Array<{ id: number, name: string }>) => {
const newInputFields = list.map((item) => {
return inputFields.find(field => field.variable === item.name)
})
handleInputFieldsChange(newInputFields as InputVar[])
}, [handleInputFieldsChange, inputFields])
const handleRemoveField = useCallback((index: number) => {
const newInputFields = inputFields.filter((_, i) => i !== index)
handleInputFieldsChange(newInputFields)
}, [handleInputFieldsChange, inputFields])
const handleAddField = () => {
setShowInputFieldEditor(true)
}
const handleEditField = (index: number) => {
const handleEditField = useCallback((index: number) => {
setShowInputFieldEditor(true)
}
}, [])
const handleCloseEditor = () => {
const handleCloseEditor = useCallback(() => {
setShowInputFieldEditor(false)
}
}, [])
return (
<div className='flex flex-col'>
@ -48,19 +72,25 @@ const FieldList = ({
<RiAddLine className='h-4 w-4 text-text-tertiary' />
</button>
</div>
<div className='flex flex-col gap-y-1 px-4 pb-2'>
<ReactSortable
className='flex flex-col gap-y-1 px-4 pb-2'
list={optionList}
setList={list => handleListSortChange(list)}
handle='.handle'
ghostClass="opacity-50"
animation={150}
disabled={readonly}
>
{inputFields?.map((item, index) => (
<FieldItem
key={index}
readonly={readonly}
payload={item}
onRemove={() => {
// Handle remove action
}}
onRemove={handleRemoveField.bind(null, index)}
onClickEdit={handleEditField.bind(null, index)}
/>
))}
</div>
</ReactSortable>
{showInputFieldEditor && (
<InputFieldEditor
show={showInputFieldEditor}

View File

@ -1,10 +1,12 @@
import {
memo,
useCallback,
useState,
} from 'react'
import { useStore } from '@/app/components/workflow/store'
import { RiCloseLine } from '@remixicon/react'
import { Jina } from '@/app/components/base/icons/src/public/llm'
import type { InputVar } from '@/app/components/workflow/types'
import { InputVarType } from '@/app/components/workflow/types'
import Tooltip from '@/app/components/base/tooltip'
import DialogWrapper from './dialog-wrapper'
@ -13,13 +15,51 @@ import FooterTip from './footer-tip'
type InputFieldDialogProps = {
readonly?: boolean
initialInputFieldsMap?: Record<string, InputVar[]>
}
const InputFieldDialog = ({
readonly = false,
initialInputFieldsMap,
}: InputFieldDialogProps) => {
const showInputFieldDialog = useStore(state => state.showInputFieldDialog)
const setShowInputFieldDialog = useStore(state => state.setShowInputFieldDialog)
// TODO: delete mock data
const [inputFieldsMap, setInputFieldsMap] = useState(initialInputFieldsMap || {
jina: [{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}, {
variable: 'num',
label: 'num',
type: InputVarType.number,
required: true,
}],
firecrawl: [{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}],
shared: [{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}],
})
const updateInputFields = useCallback((key: string, value: InputVar[]) => {
setInputFieldsMap(prev => ({
...prev,
[key]: value,
}))
}, [])
const closePanel = useCallback(() => {
setShowInputFieldDialog?.(false)
@ -58,20 +98,10 @@ const InputFieldDialog = ({
<span className='system-sm-medium text-text-secondary'>Jina Reader</span>
</div>
)}
inputFields={[{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}, {
variable: 'num',
label: 'num',
type: InputVarType.number,
required: true,
}]}
inputFields={inputFieldsMap.jina}
readonly={readonly}
labelClassName='pt-2 pb-1'
handleInputFieldsChange={updateInputFields.bind(null, 'jina')}
/>
{/* Firecrawl Field List */}
<FieldList
@ -83,15 +113,10 @@ const InputFieldDialog = ({
<span className='system-sm-medium text-text-secondary'>Firecrawl</span>
</div>
)}
inputFields={[{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}]}
inputFields={inputFieldsMap.firecrawl}
readonly={readonly}
labelClassName='pt-2 pb-1'
handleInputFieldsChange={updateInputFields.bind(null, 'firecrawl')}
/>
{/* Shared Inputs */}
<FieldList
@ -104,15 +129,10 @@ const InputFieldDialog = ({
/>
</div>
)}
inputFields={[{
variable: 'name',
label: 'name',
type: InputVarType.textInput,
required: true,
max_length: 12,
}]}
inputFields={inputFieldsMap.shared}
readonly={readonly}
labelClassName='pt-1 pb-2'
handleInputFieldsChange={updateInputFields.bind(null, 'shared')}
/>
</div>
<FooterTip />