diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index bb646fe6..28527faf 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -596,6 +596,8 @@ The above is the content you need to summarize.`, blank: 'Blank', createFromNothing: 'Create from nothing', addItem: 'Add Item', + nameRequiredMsg: 'Name is required', + nameRepeatedMsg: 'The name cannot be repeated', }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts index 29e96250..e8b56ba7 100644 --- a/web/src/locales/zh-traditional.ts +++ b/web/src/locales/zh-traditional.ts @@ -557,6 +557,8 @@ export default { blank: '空', createFromNothing: '從無到有', addItem: '新增', + nameRequiredMsg: '名稱不能為空', + nameRepeatedMsg: '名稱不能重複', }, footer: { profile: '“保留所有權利 @ react”', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index c61bbcd8..287c0c0c 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -575,6 +575,8 @@ export default { blank: '空', createFromNothing: '从无到有', addItem: '新增', + nameRequiredMsg: '名称不能为空', + nameRepeatedMsg: '名称不能重复', }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/pages/flow/canvas/node/categorize-node.tsx b/web/src/pages/flow/canvas/node/categorize-node.tsx index 324d215e..228aa97b 100644 --- a/web/src/pages/flow/canvas/node/categorize-node.tsx +++ b/web/src/pages/flow/canvas/node/categorize-node.tsx @@ -46,7 +46,6 @@ export function CategorizeNode({ id, data, selected }: NodeProps) { ), indexesInUse, ); - console.info('newPositionMap:', newPositionMap); const nextPostionMap = { ...pick(state, intersectionKeys), diff --git a/web/src/pages/flow/categorize-form/dynamic-categorize.tsx b/web/src/pages/flow/categorize-form/dynamic-categorize.tsx index 6b7a41fa..2995f191 100644 --- a/web/src/pages/flow/categorize-form/dynamic-categorize.tsx +++ b/web/src/pages/flow/categorize-form/dynamic-categorize.tsx @@ -1,16 +1,91 @@ import { useTranslate } from '@/hooks/commonHooks'; import { CloseOutlined } from '@ant-design/icons'; -import { Button, Card, Form, Input, Select } from 'antd'; +import { Button, Card, Form, FormListFieldData, Input, Select } from 'antd'; +import { FormInstance } from 'antd/lib'; import { humanId } from 'human-id'; +import trim from 'lodash/trim'; +import { + ChangeEventHandler, + FocusEventHandler, + useCallback, + useEffect, + useState, +} from 'react'; import { useUpdateNodeInternals } from 'reactflow'; import { Operator } from '../constant'; import { useBuildFormSelectOptions } from '../form-hooks'; -import { ICategorizeItem } from '../interface'; interface IProps { nodeId?: string; } +interface INameInputProps { + value?: string; + onChange?: (value: string) => void; + otherNames?: string[]; + validate(errors: string[]): void; +} + +const getOtherFieldValues = ( + form: FormInstance, + field: FormListFieldData, + latestField: string, +) => + (form.getFieldValue(['items']) ?? []) + .map((x: any) => x[latestField]) + .filter( + (x: string) => + x !== form.getFieldValue(['items', field.name, latestField]), + ); + +const NameInput = ({ + value, + onChange, + otherNames, + validate, +}: INameInputProps) => { + const [name, setName] = useState(); + const { t } = useTranslate('flow'); + + const handleNameChange: ChangeEventHandler = useCallback( + (e) => { + const val = e.target.value; + // trigger validation + if (otherNames?.some((x) => x === val)) { + validate([t('nameRepeatedMsg')]); + } else if (trim(val) === '') { + validate([t('nameRequiredMsg')]); + } else { + validate([]); + } + setName(val); + }, + [otherNames, validate, t], + ); + + const handleNameBlur: FocusEventHandler = useCallback( + (e) => { + const val = e.target.value; + if (otherNames?.every((x) => x !== val) && trim(val) !== '') { + onChange?.(val); + } + }, + [onChange, otherNames], + ); + + useEffect(() => { + setName(value); + }, [value]); + + return ( + + ); +}; + const DynamicCategorize = ({ nodeId }: IProps) => { const updateNodeInternals = useUpdateNodeInternals(); const form = Form.useFormInstance(); @@ -45,11 +120,28 @@ const DynamicCategorize = ({ nodeId }: IProps) => { } > - + + form.setFields([ + { + name: ['items', field.name, 'name'], + errors, + }, + ]) + } + > {