refactor(variable-list): improve variable list handling with useMemo

Use useMemo for listWithIds to optimize performance by avoiding unnecessary recalculations. Also rename payloadWithIds to listWithIds for better clarity.
pull/22127/head
Mminamiyama 11 months ago
parent 4095b8c11c
commit 292566b121

@ -1,172 +1,173 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import produce from 'immer' import produce from 'immer'
import RemoveButton from '../remove-button' import RemoveButton from '../remove-button'
import VarReferencePicker from './var-reference-picker' import VarReferencePicker from './var-reference-picker'
import Input from '@/app/components/base/input' import Input from '@/app/components/base/input'
import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types' import type { ValueSelector, Var, Variable } from '@/app/components/workflow/types'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types' import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var' import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { ReactSortable } from 'react-sortablejs' import { ReactSortable } from 'react-sortablejs'
import { v4 as uuid4 } from 'uuid' import { v4 as uuid4 } from 'uuid'
import { RiDraggable } from '@remixicon/react' import { RiDraggable } from '@remixicon/react'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
type Props = { type Props = {
nodeId: string nodeId: string
readonly: boolean readonly: boolean
list: Variable[] list: Variable[]
onChange: (list: Variable[]) => void onChange: (list: Variable[]) => void
onVarNameChange?: (oldName: string, newName: string) => void onVarNameChange?: (oldName: string, newName: string) => void
isSupportConstantValue?: boolean isSupportConstantValue?: boolean
onlyLeafNodeVar?: boolean onlyLeafNodeVar?: boolean
filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean
isSupportFileVar?: boolean isSupportFileVar?: boolean
} }
const VarList: FC<Props> = ({ const VarList: FC<Props> = ({
nodeId, nodeId,
readonly, readonly,
list, list,
onChange, onChange,
onVarNameChange, onVarNameChange,
isSupportConstantValue, isSupportConstantValue,
onlyLeafNodeVar, onlyLeafNodeVar,
filterVar, filterVar,
isSupportFileVar = true, isSupportFileVar = true,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const payloadWithIds = list.map((item) => { const listWithIds = useMemo(() => list.map((item) => {
const id = uuid4() const id = uuid4()
return { return {
id, id,
v: { ...item }, variable: { ...item },
} }
}) }), [list])
const handleVarNameChange = useCallback((index: number) => { const handleVarNameChange = useCallback((index: number) => {
return (e: React.ChangeEvent<HTMLInputElement>) => { return (e: React.ChangeEvent<HTMLInputElement>) => {
replaceSpaceWithUnderscreInVarNameInput(e.target) replaceSpaceWithUnderscreInVarNameInput(e.target)
const newKey = e.target.value const newKey = e.target.value
const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true) const { isValid, errorKey, errorMessageKey } = checkKeys([newKey], true)
if (!isValid) { if (!isValid) {
Toast.notify({ Toast.notify({
type: 'error', type: 'error',
message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }), message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }),
}) })
return return
} }
if (list.map(item => item.variable?.trim()).includes(newKey.trim())) { if (list.map(item => item.variable?.trim()).includes(newKey.trim())) {
Toast.notify({ Toast.notify({
type: 'error', type: 'error',
message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }), message: t('appDebug.varKeyError.keyAlreadyExists', { key: newKey }),
}) })
return return
} }
onVarNameChange?.(list[index].variable, newKey) onVarNameChange?.(list[index].variable, newKey)
const newList = produce(list, (draft) => { const newList = produce(list, (draft) => {
draft[index].variable = newKey draft[index].variable = newKey
}) })
onChange(newList) onChange(newList)
} }
}, [list, onVarNameChange, onChange]) }, [list, onVarNameChange, onChange])
const handleVarReferenceChange = useCallback((index: number) => { const handleVarReferenceChange = useCallback((index: number) => {
return (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => { return (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => {
const newList = produce(list, (draft) => { const newList = produce(list, (draft) => {
if (!isSupportConstantValue || varKindType === VarKindType.variable) { if (!isSupportConstantValue || varKindType === VarKindType.variable) {
draft[index].value_selector = value as ValueSelector draft[index].value_selector = value as ValueSelector
draft[index].value_type = varInfo?.type draft[index].value_type = varInfo?.type
if (isSupportConstantValue) if (isSupportConstantValue)
draft[index].variable_type = VarKindType.variable draft[index].variable_type = VarKindType.variable
if (!draft[index].variable) { if (!draft[index].variable) {
const variables = draft.map(v => v.variable) const variables = draft.map(v => v.variable)
let newVarName = value[value.length - 1] let newVarName = value[value.length - 1]
let count = 1 let count = 1
while (variables.includes(newVarName)) { while (variables.includes(newVarName)) {
newVarName = `${value[value.length - 1]}_${count}` newVarName = `${value[value.length - 1]}_${count}`
count++ count++
} }
draft[index].variable = newVarName draft[index].variable = newVarName
} }
} }
else { else {
draft[index].variable_type = VarKindType.constant draft[index].variable_type = VarKindType.constant
draft[index].value_selector = value as ValueSelector draft[index].value_selector = value as ValueSelector
draft[index].value = value as string draft[index].value = value as string
} }
}) })
onChange(newList) onChange(newList)
} }
}, [isSupportConstantValue, list, onChange]) }, [isSupportConstantValue, list, onChange])
const handleVarRemove = useCallback((index: number) => { const handleVarRemove = useCallback((index: number) => {
return () => { return () => {
const newList = produce(list, (draft) => { const newList = produce(list, (draft) => {
draft.splice(index, 1) draft.splice(index, 1)
}) })
onChange(newList) onChange(newList)
} }
}, [list, onChange]) }, [list, onChange])
const varCount = list.length const varCount = list.length
return ( return (
<ReactSortable <ReactSortable
className='space-y-2' className='space-y-2'
list={payloadWithIds} list={listWithIds}
setList={(list) => { onChange(list.map(item => item.v)) }} setList={(list) => { onChange(list.map(item => item.variable)) }}
handle='.handle' handle='.handle'
ghostClass='opacity-50' ghostClass='opacity-50'
animation={150} animation={150}
> >
{list.map((item, index) => { {listWithIds.map((item, index) => {
const canDrag = (() => { const canDrag = (() => {
if (readonly) if (readonly)
return false return false
return varCount > 1 return varCount > 1
})() })()
return ( const variable = item.variable
<div className={cn('flex items-center space-x-1', 'group relative')} key={index}> return (
<Input <div className={cn('flex items-center space-x-1', 'group relative')} key={index}>
wrapperClassName='w-[120px]' <Input
disabled={readonly} wrapperClassName='w-[120px]'
value={list[index].variable} disabled={readonly}
onChange={handleVarNameChange(index)} value={variable.variable}
placeholder={t('workflow.common.variableNamePlaceholder')!} onChange={handleVarNameChange(index)}
/> placeholder={t('workflow.common.variableNamePlaceholder')!}
<VarReferencePicker />
nodeId={nodeId} <VarReferencePicker
readonly={readonly} nodeId={nodeId}
isShowNodeName readonly={readonly}
className='grow' isShowNodeName
value={item.variable_type === VarKindType.constant ? (item.value || '') : (item.value_selector || [])} className='grow'
isSupportConstantValue={isSupportConstantValue} value={variable.variable_type === VarKindType.constant ? (variable.value || '') : (variable.value_selector || [])}
onChange={handleVarReferenceChange(index)} isSupportConstantValue={isSupportConstantValue}
defaultVarKindType={item.variable_type} onChange={handleVarReferenceChange(index)}
onlyLeafNodeVar={onlyLeafNodeVar} defaultVarKindType={variable.variable_type}
filterVar={filterVar} onlyLeafNodeVar={onlyLeafNodeVar}
isSupportFileVar={isSupportFileVar} filterVar={filterVar}
/> isSupportFileVar={isSupportFileVar}
{!readonly && ( />
<RemoveButton onClick={handleVarRemove(index)}/> {!readonly && (
)} <RemoveButton onClick={handleVarRemove(index)}/>
{canDrag && <RiDraggable className={cn( )}
'handle absolute -left-4 top-3 hidden h-3 w-3 cursor-pointer text-text-quaternary', {canDrag && <RiDraggable className={cn(
'group-hover:block', 'handle absolute -left-4 top-3 hidden h-3 w-3 cursor-pointer text-text-quaternary',
)} />} 'group-hover:block',
</div> )} />}
) </div>
})} )
</ReactSortable> })}
) </ReactSortable>
} )
export default React.memo(VarList) }
export default React.memo(VarList)

Loading…
Cancel
Save