### What problem does this PR solve? fix: The name of the copy operator is displayed the same as before ##3265 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.14.0
| import { Flex, MenuProps } from 'antd'; | import { Flex, MenuProps } from 'antd'; | ||||
| import { useCallback } from 'react'; | import { useCallback } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useGetNodeName } from '../../hooks'; | |||||
| import useGraphStore from '../../store'; | import useGraphStore from '../../store'; | ||||
| interface IProps { | interface IProps { | ||||
| id: string; | id: string; | ||||
| iconFontColor?: string; | iconFontColor?: string; | ||||
| label: string; | |||||
| } | } | ||||
| const NodeDropdown = ({ id, iconFontColor }: IProps) => { | |||||
| const NodeDropdown = ({ id, iconFontColor, label }: IProps) => { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const deleteNodeById = useGraphStore((store) => store.deleteNodeById); | const deleteNodeById = useGraphStore((store) => store.deleteNodeById); | ||||
| const duplicateNodeById = useGraphStore((store) => store.duplicateNode); | const duplicateNodeById = useGraphStore((store) => store.duplicateNode); | ||||
| const getNodeName = useGetNodeName(); | |||||
| const deleteNode = useCallback(() => { | const deleteNode = useCallback(() => { | ||||
| deleteNodeById(id); | deleteNodeById(id); | ||||
| }, [id, deleteNodeById]); | }, [id, deleteNodeById]); | ||||
| const duplicateNode = useCallback(() => { | const duplicateNode = useCallback(() => { | ||||
| duplicateNodeById(id); | |||||
| }, [id, duplicateNodeById]); | |||||
| duplicateNodeById(id, getNodeName(label)); | |||||
| }, [duplicateNodeById, id, getNodeName, label]); | |||||
| const items: MenuProps['items'] = [ | const items: MenuProps['items'] = [ | ||||
| { | { |
| interface IProps { | interface IProps { | ||||
| id: string; | id: string; | ||||
| label?: string; | |||||
| name?: string; | |||||
| label: string; | |||||
| name: string; | |||||
| gap?: number; | gap?: number; | ||||
| className?: string; | className?: string; | ||||
| } | } | ||||
| export function RunStatus({ id, name }: IProps) { | |||||
| export function RunStatus({ id, name }: Omit<IProps, 'label'>) { | |||||
| const { t } = useTranslate('flow'); | const { t } = useTranslate('flow'); | ||||
| return ( | return ( | ||||
| <section className="flex justify-end items-center pb-1 "> | <section className="flex justify-end items-center pb-1 "> | ||||
| color={operatorMap[label as Operator].color} | color={operatorMap[label as Operator].color} | ||||
| ></OperatorIcon> | ></OperatorIcon> | ||||
| <span className={styles.nodeTitle}>{name}</span> | <span className={styles.nodeTitle}>{name}</span> | ||||
| <NodeDropdown id={id}></NodeDropdown> | |||||
| <NodeDropdown id={id} label={label}></NodeDropdown> | |||||
| </Flex> | </Flex> | ||||
| </section> | </section> | ||||
| ); | ); |
| onChange={handleNameChange} | onChange={handleNameChange} | ||||
| className={styles.noteName} | className={styles.noteName} | ||||
| ></Input> | ></Input> | ||||
| <NodeDropdown id={id}></NodeDropdown> | |||||
| <NodeDropdown id={id} label={data.label}></NodeDropdown> | |||||
| </Flex> | </Flex> | ||||
| <Form | <Form | ||||
| onValuesChange={handleValuesChange} | onValuesChange={handleValuesChange} |
| import useGraphStore, { RFState } from './store'; | import useGraphStore, { RFState } from './store'; | ||||
| import { | import { | ||||
| buildDslComponentsByGraph, | buildDslComponentsByGraph, | ||||
| generateNodeNamesWithIncreasingIndex, | |||||
| generateSwitchHandleText, | generateSwitchHandleText, | ||||
| getNodeDragHandle, | getNodeDragHandle, | ||||
| receiveMessageError, | receiveMessageError, | ||||
| return { handleDragStart }; | return { handleDragStart }; | ||||
| }; | }; | ||||
| const splitName = (name: string) => { | |||||
| const names = name.split('_'); | |||||
| const type = names.at(0); | |||||
| const index = Number(names.at(-1)); | |||||
| export const useGetNodeName = () => { | |||||
| const { t } = useTranslation(); | |||||
| return { type, index }; | |||||
| return (type: string) => { | |||||
| const name = t(`flow.${lowerFirst(type)}`); | |||||
| return name; | |||||
| }; | |||||
| }; | }; | ||||
| export const useHandleDrop = () => { | export const useHandleDrop = () => { | ||||
| const [reactFlowInstance, setReactFlowInstance] = | const [reactFlowInstance, setReactFlowInstance] = | ||||
| useState<ReactFlowInstance<any, any>>(); | useState<ReactFlowInstance<any, any>>(); | ||||
| const initializeOperatorParams = useInitializeOperatorParams(); | const initializeOperatorParams = useInitializeOperatorParams(); | ||||
| const { t } = useTranslation(); | |||||
| const getNodeName = useGetNodeName(); | |||||
| const onDragOver = useCallback((event: React.DragEvent<HTMLDivElement>) => { | const onDragOver = useCallback((event: React.DragEvent<HTMLDivElement>) => { | ||||
| event.preventDefault(); | event.preventDefault(); | ||||
| event.dataTransfer.dropEffect = 'move'; | event.dataTransfer.dropEffect = 'move'; | ||||
| }, []); | }, []); | ||||
| const generateNodeName = useCallback( | |||||
| (type: string) => { | |||||
| const name = t(`flow.${lowerFirst(type)}`); | |||||
| const templateNameList = nodes | |||||
| .filter((x) => { | |||||
| const temporaryName = x.data.name; | |||||
| const { type, index } = splitName(temporaryName); | |||||
| return ( | |||||
| temporaryName.match(/_/g)?.length === 1 && | |||||
| type === name && | |||||
| !isNaN(index) | |||||
| ); | |||||
| }) | |||||
| .map((x) => { | |||||
| const temporaryName = x.data.name; | |||||
| const { index } = splitName(temporaryName); | |||||
| return { | |||||
| idx: index, | |||||
| name: temporaryName, | |||||
| }; | |||||
| }) | |||||
| .sort((a, b) => a.idx - b.idx); | |||||
| let index: number = 0; | |||||
| for (let i = 0; i < templateNameList.length; i++) { | |||||
| const idx = templateNameList[i]?.idx; | |||||
| const nextIdx = templateNameList[i + 1]?.idx; | |||||
| if (idx + 1 !== nextIdx) { | |||||
| index = idx + 1; | |||||
| break; | |||||
| } | |||||
| } | |||||
| return `${name}_${index}`; | |||||
| }, | |||||
| [t, nodes], | |||||
| ); | |||||
| const onDrop = useCallback( | const onDrop = useCallback( | ||||
| (event: React.DragEvent<HTMLDivElement>) => { | (event: React.DragEvent<HTMLDivElement>) => { | ||||
| event.preventDefault(); | event.preventDefault(); | ||||
| }, | }, | ||||
| data: { | data: { | ||||
| label: `${type}`, | label: `${type}`, | ||||
| name: generateNodeName(type), | |||||
| name: generateNodeNamesWithIncreasingIndex(getNodeName(type), nodes), | |||||
| form: initializeOperatorParams(type as Operator), | form: initializeOperatorParams(type as Operator), | ||||
| }, | }, | ||||
| sourcePosition: Position.Right, | sourcePosition: Position.Right, | ||||
| addNode(newNode); | addNode(newNode); | ||||
| }, | }, | ||||
| [reactFlowInstance, addNode, initializeOperatorParams, generateNodeName], | |||||
| [reactFlowInstance, getNodeName, nodes, initializeOperatorParams, addNode], | |||||
| ); | ); | ||||
| return { onDrop, onDragOver, setReactFlowInstance }; | return { onDrop, onDragOver, setReactFlowInstance }; |
| import { immer } from 'zustand/middleware/immer'; | import { immer } from 'zustand/middleware/immer'; | ||||
| import { Operator, SwitchElseTo } from './constant'; | import { Operator, SwitchElseTo } from './constant'; | ||||
| import { NodeData } from './interface'; | import { NodeData } from './interface'; | ||||
| import { getNodeDragHandle, getOperatorIndex, isEdgeEqual } from './utils'; | |||||
| import { | |||||
| generateNodeNamesWithIncreasingIndex, | |||||
| getNodeDragHandle, | |||||
| getOperatorIndex, | |||||
| isEdgeEqual, | |||||
| } from './utils'; | |||||
| export type RFState = { | export type RFState = { | ||||
| nodes: Node<NodeData>[]; | nodes: Node<NodeData>[]; | ||||
| target?: string | null, | target?: string | null, | ||||
| ) => void; | ) => void; | ||||
| deletePreviousEdgeOfClassificationNode: (connection: Connection) => void; | deletePreviousEdgeOfClassificationNode: (connection: Connection) => void; | ||||
| duplicateNode: (id: string) => void; | |||||
| duplicateNode: (id: string, name: string) => void; | |||||
| deleteEdge: () => void; | deleteEdge: () => void; | ||||
| deleteEdgeById: (id: string) => void; | deleteEdgeById: (id: string) => void; | ||||
| deleteNodeById: (id: string) => void; | deleteNodeById: (id: string) => void; | ||||
| updateMutableNodeFormItem: (id: string, field: string, value: any) => void; | updateMutableNodeFormItem: (id: string, field: string, value: any) => void; | ||||
| getOperatorTypeFromId: (id?: string | null) => string | undefined; | getOperatorTypeFromId: (id?: string | null) => string | undefined; | ||||
| updateNodeName: (id: string, name: string) => void; | updateNodeName: (id: string, name: string) => void; | ||||
| generateNodeName: (name: string) => string; | |||||
| setClickedNodeId: (id?: string) => void; | setClickedNodeId: (id?: string) => void; | ||||
| }; | }; | ||||
| } | } | ||||
| } | } | ||||
| }, | }, | ||||
| duplicateNode: (id: string) => { | |||||
| const { getNode, addNode } = get(); | |||||
| duplicateNode: (id: string, name: string) => { | |||||
| const { getNode, addNode, generateNodeName } = get(); | |||||
| const node = getNode(id); | const node = getNode(id); | ||||
| const position = { | const position = { | ||||
| x: (node?.position?.x || 0) + 30, | x: (node?.position?.x || 0) + 30, | ||||
| addNode({ | addNode({ | ||||
| ...(node || {}), | ...(node || {}), | ||||
| data: node?.data, | |||||
| data: { ...(node?.data ?? {}), name: generateNodeName(name) }, | |||||
| selected: false, | selected: false, | ||||
| dragging: false, | dragging: false, | ||||
| id: `${node?.data?.label}:${humanId()}`, | id: `${node?.data?.label}:${humanId()}`, | ||||
| setClickedNodeId: (id?: string) => { | setClickedNodeId: (id?: string) => { | ||||
| set({ clickedNodeId: id }); | set({ clickedNodeId: id }); | ||||
| }, | }, | ||||
| generateNodeName: (name: string) => { | |||||
| const { nodes } = get(); | |||||
| return generateNodeNamesWithIncreasingIndex(name, nodes); | |||||
| }, | |||||
| })), | })), | ||||
| { name: 'graph' }, | { name: 'graph' }, | ||||
| ), | ), |
| const intersectionKeys = intersectionWith( | const intersectionKeys = intersectionWith( | ||||
| previousKeys, | previousKeys, | ||||
| currentKeys, | currentKeys, | ||||
| (categoryDataKey, positionMapKey) => categoryDataKey === positionMapKey, | |||||
| (categoryDataKey: string, positionMapKey: string) => | |||||
| categoryDataKey === positionMapKey, | |||||
| ); | ); | ||||
| // difference set | // difference set | ||||
| const currentDifferenceKeys = currentKeys.filter( | const currentDifferenceKeys = currentKeys.filter( | ||||
| (x) => !intersectionKeys.some((y) => y === x), | |||||
| (x) => !intersectionKeys.some((y: string) => y === x), | |||||
| ); | ); | ||||
| const newPositionMap = currentDifferenceKeys.reduce< | const newPositionMap = currentDifferenceKeys.reduce< | ||||
| Record<string, IPosition> | Record<string, IPosition> | ||||
| export const getNodeDragHandle = (nodeType?: string) => { | export const getNodeDragHandle = (nodeType?: string) => { | ||||
| return nodeType === Operator.Note ? '.note-drag-handle' : undefined; | return nodeType === Operator.Note ? '.note-drag-handle' : undefined; | ||||
| }; | }; | ||||
| const splitName = (name: string) => { | |||||
| const names = name.split('_'); | |||||
| const type = names.at(0); | |||||
| const index = Number(names.at(-1)); | |||||
| return { type, index }; | |||||
| }; | |||||
| export const generateNodeNamesWithIncreasingIndex = ( | |||||
| name: string, | |||||
| nodes: Node[], | |||||
| ) => { | |||||
| const templateNameList = nodes | |||||
| .filter((x) => { | |||||
| const temporaryName = x.data.name; | |||||
| const { type, index } = splitName(temporaryName); | |||||
| return ( | |||||
| temporaryName.match(/_/g)?.length === 1 && | |||||
| type === name && | |||||
| !isNaN(index) | |||||
| ); | |||||
| }) | |||||
| .map((x) => { | |||||
| const temporaryName = x.data.name; | |||||
| const { index } = splitName(temporaryName); | |||||
| return { | |||||
| idx: index, | |||||
| name: temporaryName, | |||||
| }; | |||||
| }) | |||||
| .sort((a, b) => a.idx - b.idx); | |||||
| let index: number = 0; | |||||
| for (let i = 0; i < templateNameList.length; i++) { | |||||
| const idx = templateNameList[i]?.idx; | |||||
| const nextIdx = templateNameList[i + 1]?.idx; | |||||
| if (idx + 1 !== nextIdx) { | |||||
| index = idx + 1; | |||||
| break; | |||||
| } | |||||
| } | |||||
| return `${name}_${index}`; | |||||
| }; |