import {
|
useCallback,
|
useMemo,
|
useState,
|
} from 'react'
|
import { useTranslation } from 'react-i18next'
|
import { RiDeleteBinLine } from '@remixicon/react'
|
import produce from 'immer'
|
import type { VarType as NumberVarType } from '../../../tool/types'
|
import type {
|
Condition,
|
HandleAddSubVariableCondition,
|
HandleRemoveCondition,
|
HandleToggleSubVariableConditionLogicalOperator,
|
HandleUpdateCondition,
|
HandleUpdateSubVariableCondition,
|
handleRemoveSubVariableCondition,
|
} from '../../types'
|
import {
|
ComparisonOperator,
|
} from '../../types'
|
import ConditionNumberInput from '../condition-number-input'
|
import ConditionWrap from '../condition-wrap'
|
import { comparisonOperatorNotRequireValue, getOperators } from './../../utils'
|
import ConditionOperator from './condition-operator'
|
import ConditionInput from './condition-input'
|
import { FILE_TYPE_OPTIONS, SUB_VARIABLES, TRANSFER_METHOD } from './../../default'
|
import type {
|
Node,
|
NodeOutPutVar,
|
ValueSelector,
|
Var,
|
} from '@/app/components/workflow/types'
|
import { VarType } from '@/app/components/workflow/types'
|
import cn from '@/utils/classnames'
|
import { SimpleSelect as Select } from '@/app/components/base/select'
|
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
import ConditionVarSelector from './condition-var-selector'
|
|
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
|
|
type ConditionItemProps = {
|
className?: string
|
disabled?: boolean
|
conditionId: string // in isSubVariableKey it's the value of the parent condition's id
|
condition: Condition // condition may the condition of case or condition of sub variable
|
file?: { key: string }
|
isSubVariableKey?: boolean
|
isValueFieldShort?: boolean
|
onRemoveCondition?: HandleRemoveCondition
|
onUpdateCondition?: HandleUpdateCondition
|
onAddSubVariableCondition?: HandleAddSubVariableCondition
|
onRemoveSubVariableCondition?: handleRemoveSubVariableCondition
|
onUpdateSubVariableCondition?: HandleUpdateSubVariableCondition
|
onToggleSubVariableConditionLogicalOperator?: HandleToggleSubVariableConditionLogicalOperator
|
nodeId: string
|
availableNodes: Node[]
|
numberVariables: NodeOutPutVar[]
|
availableVars: NodeOutPutVar[]
|
}
|
const ConditionItem = ({
|
className,
|
disabled,
|
conditionId,
|
condition,
|
file,
|
isSubVariableKey,
|
isValueFieldShort,
|
onRemoveCondition,
|
onUpdateCondition,
|
onAddSubVariableCondition,
|
onRemoveSubVariableCondition,
|
onUpdateSubVariableCondition,
|
onToggleSubVariableConditionLogicalOperator,
|
nodeId,
|
availableNodes,
|
numberVariables,
|
availableVars,
|
}: ConditionItemProps) => {
|
const { t } = useTranslation()
|
|
const [isHovered, setIsHovered] = useState(false)
|
const [open, setOpen] = useState(false)
|
|
const doUpdateCondition = useCallback((newCondition: Condition) => {
|
if (isSubVariableKey)
|
onUpdateSubVariableCondition?.(conditionId, condition.id, newCondition)
|
else
|
onUpdateCondition?.(condition.id, newCondition)
|
}, [condition, conditionId, isSubVariableKey, onUpdateCondition, onUpdateSubVariableCondition])
|
|
const canChooseOperator = useMemo(() => {
|
if (disabled)
|
return false
|
|
if (isSubVariableKey)
|
return !!condition.key
|
|
return true
|
}, [condition.key, disabled, isSubVariableKey])
|
const handleUpdateConditionOperator = useCallback((value: ComparisonOperator) => {
|
const newCondition = {
|
...condition,
|
comparison_operator: value,
|
}
|
doUpdateCondition(newCondition)
|
}, [condition, doUpdateCondition])
|
|
const handleUpdateConditionNumberVarType = useCallback((numberVarType: NumberVarType) => {
|
const newCondition = {
|
...condition,
|
numberVarType,
|
value: '',
|
}
|
doUpdateCondition(newCondition)
|
}, [condition, doUpdateCondition])
|
|
const isSubVariable = condition.varType === VarType.arrayFile && [ComparisonOperator.contains, ComparisonOperator.notContains, ComparisonOperator.allOf].includes(condition.comparison_operator!)
|
const fileAttr = useMemo(() => {
|
if (file)
|
return file
|
if (isSubVariableKey) {
|
return {
|
key: condition.key!,
|
}
|
}
|
return undefined
|
}, [condition.key, file, isSubVariableKey])
|
|
const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type'
|
|
const handleUpdateConditionValue = useCallback((value: string) => {
|
if (value === condition.value || (isArrayValue && value === condition.value?.[0]))
|
return
|
const newCondition = {
|
...condition,
|
value: isArrayValue ? [value] : value,
|
}
|
doUpdateCondition(newCondition)
|
}, [condition, doUpdateCondition, isArrayValue])
|
|
const isSelect = condition.comparison_operator && [ComparisonOperator.in, ComparisonOperator.notIn].includes(condition.comparison_operator)
|
const selectOptions = useMemo(() => {
|
if (isSelect) {
|
if (fileAttr?.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) {
|
return FILE_TYPE_OPTIONS.map(item => ({
|
name: t(`${optionNameI18NPrefix}.${item.i18nKey}`),
|
value: item.value,
|
}))
|
}
|
if (fileAttr?.key === 'transfer_method') {
|
return TRANSFER_METHOD.map(item => ({
|
name: t(`${optionNameI18NPrefix}.${item.i18nKey}`),
|
value: item.value,
|
}))
|
}
|
return []
|
}
|
return []
|
}, [condition.comparison_operator, fileAttr?.key, isSelect, t])
|
|
const isNotInput = isSelect || isSubVariable
|
|
const isSubVarSelect = isSubVariableKey
|
const subVarOptions = SUB_VARIABLES.map(item => ({
|
name: item,
|
value: item,
|
}))
|
|
const handleSubVarKeyChange = useCallback((key: string) => {
|
const newCondition = produce(condition, (draft) => {
|
draft.key = key
|
if (key === 'size')
|
draft.varType = VarType.number
|
else
|
draft.varType = VarType.string
|
|
draft.value = ''
|
draft.comparison_operator = getOperators(undefined, { key })[0]
|
})
|
|
onUpdateSubVariableCondition?.(conditionId, condition.id, newCondition)
|
}, [condition, conditionId, onUpdateSubVariableCondition])
|
|
const doRemoveCondition = useCallback(() => {
|
if (isSubVariableKey)
|
onRemoveSubVariableCondition?.(conditionId, condition.id)
|
else
|
onRemoveCondition?.(condition.id)
|
}, [condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition])
|
|
const handleVarChange = useCallback((valueSelector: ValueSelector, varItem: Var) => {
|
const newCondition = produce(condition, (draft) => {
|
draft.variable_selector = valueSelector
|
draft.varType = varItem.type
|
draft.value = ''
|
draft.comparison_operator = getOperators(varItem.type)[0]
|
})
|
doUpdateCondition(newCondition)
|
setOpen(false)
|
}, [condition, doUpdateCondition])
|
|
return (
|
<div className={cn('mb-1 flex last-of-type:mb-0', className)}>
|
<div className={cn(
|
'grow rounded-lg bg-components-input-bg-normal',
|
isHovered && 'bg-state-destructive-hover',
|
)}>
|
<div className='flex items-center p-1'>
|
<div className='w-0 grow'>
|
{isSubVarSelect
|
? (
|
<Select
|
wrapperClassName='h-6'
|
className='pl-0 text-xs'
|
optionWrapClassName='w-[165px] max-h-none'
|
defaultValue={condition.key}
|
items={subVarOptions}
|
onSelect={item => handleSubVarKeyChange(item.value as string)}
|
renderTrigger={item => (
|
item
|
? <div className='flex cursor-pointer justify-start'>
|
<div className='inline-flex h-6 max-w-full items-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-1.5 text-text-accent shadow-xs'>
|
<Variable02 className='h-3.5 w-3.5 shrink-0 text-text-accent' />
|
<div className='system-xs-medium ml-0.5 truncate'>{item?.name}</div>
|
</div>
|
</div>
|
: <div className='system-sm-regular text-left text-components-input-text-placeholder'>{t('common.placeholder.select')}</div>
|
)}
|
hideChecked
|
/>
|
)
|
: (
|
<ConditionVarSelector
|
open={open}
|
onOpenChange={setOpen}
|
valueSelector={condition.variable_selector || []}
|
varType={condition.varType}
|
availableNodes={availableNodes}
|
nodesOutputVars={availableVars}
|
onChange={handleVarChange}
|
/>
|
)}
|
|
</div>
|
<div className='mx-1 h-3 w-[1px] bg-divider-regular'></div>
|
<ConditionOperator
|
disabled={!canChooseOperator}
|
varType={condition.varType}
|
value={condition.comparison_operator}
|
onSelect={handleUpdateConditionOperator}
|
file={fileAttr}
|
/>
|
</div>
|
{
|
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && (
|
<div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'>
|
<ConditionInput
|
disabled={disabled}
|
value={condition.value as string}
|
onChange={handleUpdateConditionValue}
|
availableNodes={availableNodes}
|
/>
|
</div>
|
)
|
}
|
{
|
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && (
|
<div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'>
|
<ConditionNumberInput
|
numberVarType={condition.numberVarType}
|
onNumberVarTypeChange={handleUpdateConditionNumberVarType}
|
value={condition.value as string}
|
onValueChange={handleUpdateConditionValue}
|
variables={numberVariables}
|
isShort={isValueFieldShort}
|
unit={fileAttr?.key === 'size' ? 'Byte' : undefined}
|
/>
|
</div>
|
)
|
}
|
{
|
!comparisonOperatorNotRequireValue(condition.comparison_operator) && isSelect && (
|
<div className='border-t border-t-divider-subtle'>
|
<Select
|
wrapperClassName='h-8'
|
className='rounded-t-none px-2 text-xs'
|
defaultValue={isArrayValue ? (condition.value as string[])?.[0] : (condition.value as string)}
|
items={selectOptions}
|
onSelect={item => handleUpdateConditionValue(item.value as string)}
|
hideChecked
|
notClearable
|
/>
|
</div>
|
)
|
}
|
{
|
!comparisonOperatorNotRequireValue(condition.comparison_operator) && isSubVariable && (
|
<div className='p-1'>
|
<ConditionWrap
|
isSubVariable
|
conditions={condition.sub_variable_condition?.conditions || []}
|
logicalOperator={condition.sub_variable_condition?.logical_operator}
|
conditionId={conditionId}
|
readOnly={!!disabled}
|
handleAddSubVariableCondition={onAddSubVariableCondition}
|
handleRemoveSubVariableCondition={onRemoveSubVariableCondition}
|
handleUpdateSubVariableCondition={onUpdateSubVariableCondition}
|
handleToggleSubVariableConditionLogicalOperator={onToggleSubVariableConditionLogicalOperator}
|
nodeId={nodeId}
|
availableNodes={availableNodes}
|
availableVars={availableVars}
|
/>
|
</div>
|
)
|
}
|
</div>
|
<div
|
className='ml-1 mt-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive'
|
onMouseEnter={() => setIsHovered(true)}
|
onMouseLeave={() => setIsHovered(false)}
|
onClick={doRemoveCondition}
|
>
|
<RiDeleteBinLine className='h-4 w-4' />
|
</div>
|
</div>
|
)
|
}
|
|
export default ConditionItem
|