### What problem does this PR solve? feat: Update Switch form data #1739 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.10.0
| @@ -1,6 +1,6 @@ | |||
| import { Handle, Position } from 'reactflow'; | |||
| // import { v4 as uuid } from 'uuid'; | |||
| import React from 'react'; | |||
| import styles from './index.less'; | |||
| const DEFAULT_HANDLE_STYLE = { | |||
| @@ -10,20 +10,19 @@ const DEFAULT_HANDLE_STYLE = { | |||
| fontSize: 8, | |||
| }; | |||
| interface IProps { | |||
| interface IProps extends React.PropsWithChildren { | |||
| top: number; | |||
| right: number; | |||
| text: string; | |||
| id: string; | |||
| idx?: number; | |||
| } | |||
| const CategorizeHandle = ({ top, right, text, idx }: IProps) => { | |||
| const CategorizeHandle = ({ top, right, id, children }: IProps) => { | |||
| return ( | |||
| <Handle | |||
| type="source" | |||
| position={Position.Right} | |||
| // id={`CategorizeHandle${idx}`} | |||
| id={text} | |||
| id={id} | |||
| isConnectable | |||
| style={{ | |||
| ...DEFAULT_HANDLE_STYLE, | |||
| @@ -33,7 +32,7 @@ const CategorizeHandle = ({ top, right, text, idx }: IProps) => { | |||
| color: 'black', | |||
| }} | |||
| > | |||
| <span className={styles.categorizeAnchorPointText}>{text}</span> | |||
| <span className={styles.categorizeAnchorPointText}>{children || id}</span> | |||
| </Handle> | |||
| ); | |||
| }; | |||
| @@ -3,7 +3,7 @@ import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import lowerFirst from 'lodash/lowerFirst'; | |||
| import { Handle, NodeProps, Position } from 'reactflow'; | |||
| import { Operator, operatorMap } from '../../constant'; | |||
| import { Operator, SwitchElseTo, operatorMap } from '../../constant'; | |||
| import { NodeData } from '../../interface'; | |||
| import OperatorIcon from '../../operator-icon'; | |||
| import CategorizeHandle from './categorize-handle'; | |||
| @@ -16,6 +16,7 @@ export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) { | |||
| const style = operatorMap[data.label as Operator]; | |||
| const { t } = useTranslate('flow'); | |||
| const { positions } = useBuildCategorizeHandlePositions({ data, id }); | |||
| const operatorName = data.label; | |||
| return ( | |||
| <NodePopover nodeId={id}> | |||
| @@ -49,13 +50,18 @@ export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) { | |||
| className={styles.handle} | |||
| id={'c'} | |||
| ></Handle> | |||
| {operatorName === Operator.Switch && ( | |||
| <CategorizeHandle top={50} right={-4} id={SwitchElseTo}> | |||
| To | |||
| </CategorizeHandle> | |||
| )} | |||
| {positions.map((position, idx) => { | |||
| return ( | |||
| <CategorizeHandle | |||
| top={position.top} | |||
| right={position.right} | |||
| key={idx} | |||
| text={position.text} | |||
| id={position.text} | |||
| idx={idx} | |||
| ></CategorizeHandle> | |||
| ); | |||
| @@ -2633,3 +2633,5 @@ export const ExeSQLOptions = ['mysql', 'postgresql', 'mariadb'].map((x) => ({ | |||
| label: upperFirst(x), | |||
| value: x, | |||
| })); | |||
| export const SwitchElseTo = 'end_cpn_id'; | |||
| @@ -30,6 +30,7 @@ import { | |||
| NodeMap, | |||
| Operator, | |||
| RestrictedUpstreamMap, | |||
| SwitchElseTo, | |||
| initialArXivValues, | |||
| initialBaiduFanyiValues, | |||
| initialBaiduValues, | |||
| @@ -519,6 +520,17 @@ export const useWatchNodeFormDataChange = () => { | |||
| return pre; | |||
| }, []); | |||
| // Splice the else condition of the conditional judgment to the edge list | |||
| const elseTo = form[SwitchElseTo]; | |||
| if (elseTo) { | |||
| downstreamEdges.push({ | |||
| id: uuid(), | |||
| source: nodeId, | |||
| target: elseTo, | |||
| sourceHandle: SwitchElseTo, | |||
| }); | |||
| } | |||
| setEdgesByNodeId(nodeId, downstreamEdges); | |||
| }, | |||
| [setEdgesByNodeId], | |||
| @@ -21,7 +21,7 @@ import { | |||
| import { create } from 'zustand'; | |||
| import { devtools } from 'zustand/middleware'; | |||
| import { immer } from 'zustand/middleware/immer'; | |||
| import { Operator } from './constant'; | |||
| import { Operator, SwitchElseTo } from './constant'; | |||
| import { NodeData } from './interface'; | |||
| import { getOperatorIndex, isEdgeEqual } from './utils'; | |||
| @@ -37,13 +37,22 @@ export type RFState = { | |||
| setNodes: (nodes: Node[]) => void; | |||
| setEdges: (edges: Edge[]) => void; | |||
| setEdgesByNodeId: (nodeId: string, edges: Edge[]) => void; | |||
| updateNodeForm: (nodeId: string, values: any, path?: string[]) => void; | |||
| updateNodeForm: ( | |||
| nodeId: string, | |||
| values: any, | |||
| path?: (string | number)[], | |||
| ) => void; | |||
| onSelectionChange: OnSelectionChangeFunc; | |||
| addNode: (nodes: Node) => void; | |||
| getNode: (id?: string | null) => Node<NodeData> | undefined; | |||
| addEdge: (connection: Connection) => void; | |||
| getEdge: (id: string) => Edge | undefined; | |||
| updateFormDataOnConnect: (connection: Connection) => void; | |||
| updateSwitchFormData: ( | |||
| source: string, | |||
| sourceHandle?: string | null, | |||
| target?: string | null, | |||
| ) => void; | |||
| deletePreviousEdgeOfClassificationNode: (connection: Connection) => void; | |||
| duplicateNode: (id: string) => void; | |||
| deleteEdge: () => void; | |||
| @@ -132,8 +141,6 @@ const useGraphStore = create<RFState>()( | |||
| if (isDifferent) { | |||
| // other operator's edges | |||
| const irrelevantEdges = edges.filter((x) => x.source !== nodeId); | |||
| // the abandoned edges | |||
| const selfAbandonedEdges = []; | |||
| // the added downstream edges | |||
| const selfAddedDownstreamEdges = differenceWith( | |||
| currentDownstreamEdges, | |||
| @@ -168,7 +175,8 @@ const useGraphStore = create<RFState>()( | |||
| return get().edges.find((x) => x.id === id); | |||
| }, | |||
| updateFormDataOnConnect: (connection: Connection) => { | |||
| const { getOperatorTypeFromId, updateNodeForm } = get(); | |||
| const { getOperatorTypeFromId, updateNodeForm, updateSwitchFormData } = | |||
| get(); | |||
| const { source, target, sourceHandle } = connection; | |||
| const operatorType = getOperatorTypeFromId(source); | |||
| if (source) { | |||
| @@ -185,16 +193,7 @@ const useGraphStore = create<RFState>()( | |||
| ]); | |||
| break; | |||
| case Operator.Switch: { | |||
| if (sourceHandle) { | |||
| const operatorIndex = getOperatorIndex(sourceHandle); | |||
| if (operatorIndex) { | |||
| updateNodeForm(source, target, [ | |||
| 'conditions', | |||
| operatorIndex, | |||
| 'to', | |||
| ]); | |||
| } | |||
| } | |||
| updateSwitchFormData(source, sourceHandle, target); | |||
| break; | |||
| } | |||
| default: | |||
| @@ -253,7 +252,12 @@ const useGraphStore = create<RFState>()( | |||
| }); | |||
| }, | |||
| deleteEdgeById: (id: string) => { | |||
| const { edges, updateNodeForm, getOperatorTypeFromId } = get(); | |||
| const { | |||
| edges, | |||
| updateNodeForm, | |||
| getOperatorTypeFromId, | |||
| updateSwitchFormData, | |||
| } = get(); | |||
| const currentEdge = edges.find((x) => x.id === id); | |||
| if (currentEdge) { | |||
| @@ -275,16 +279,7 @@ const useGraphStore = create<RFState>()( | |||
| ]); | |||
| break; | |||
| case Operator.Switch: { | |||
| if (sourceHandle) { | |||
| const operatorIndex = getOperatorIndex(sourceHandle); | |||
| if (operatorIndex) { | |||
| updateNodeForm(source, undefined, [ | |||
| 'conditions', | |||
| operatorIndex, | |||
| 'to', | |||
| ]); | |||
| } | |||
| } | |||
| updateSwitchFormData(source, sourceHandle, undefined); | |||
| break; | |||
| } | |||
| default: | |||
| @@ -320,7 +315,11 @@ const useGraphStore = create<RFState>()( | |||
| findNodeByName: (name: Operator) => { | |||
| return get().nodes.find((x) => x.data.label === name); | |||
| }, | |||
| updateNodeForm: (nodeId: string, values: any, path: string[] = []) => { | |||
| updateNodeForm: ( | |||
| nodeId: string, | |||
| values: any, | |||
| path: (string | number)[] = [], | |||
| ) => { | |||
| set({ | |||
| nodes: get().nodes.map((node) => { | |||
| if (node.id === nodeId) { | |||
| @@ -343,6 +342,23 @@ const useGraphStore = create<RFState>()( | |||
| }), | |||
| }); | |||
| }, | |||
| updateSwitchFormData: (source, sourceHandle, target) => { | |||
| const { updateNodeForm } = get(); | |||
| if (sourceHandle) { | |||
| if (sourceHandle === SwitchElseTo) { | |||
| updateNodeForm(source, target, [SwitchElseTo]); | |||
| } else { | |||
| const operatorIndex = getOperatorIndex(sourceHandle); | |||
| if (operatorIndex) { | |||
| updateNodeForm(source, target, [ | |||
| 'conditions', | |||
| Number(operatorIndex) - 1, // The index is the conditions form index | |||
| 'to', | |||
| ]); | |||
| } | |||
| } | |||
| } | |||
| }, | |||
| updateMutableNodeFormItem: (id: string, field: string, value: any) => { | |||
| const { nodes } = get(); | |||
| const idx = nodes.findIndex((x) => x.id === id); | |||
| @@ -1,7 +1,7 @@ | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { Button, Card, Form, Input, Select, Typography } from 'antd'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { Operator } from '../constant'; | |||
| import { Operator, SwitchElseTo } from '../constant'; | |||
| import { useBuildFormSelectOptions } from '../form-hooks'; | |||
| import { IOperatorForm, ISwitchForm } from '../interface'; | |||
| import { getOtherFieldValues } from '../utils'; | |||
| @@ -38,15 +38,12 @@ const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => { | |||
| initialValues={{ conditions: [{}] }} | |||
| onValuesChange={onValuesChange} | |||
| > | |||
| <Form.Item label={t('flow.to')} name={['end_cpn_id']}> | |||
| <Form.Item label={t('flow.to')} name={[SwitchElseTo]}> | |||
| <Select | |||
| allowClear | |||
| options={buildCategorizeToOptions(getSelectedConditionTos())} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item label={t('flow.no')} name={['no']}> | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.List name="conditions"> | |||
| {(fields, { add, remove }) => ( | |||
| <div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}> | |||
| @@ -74,7 +71,7 @@ const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => { | |||
| <Select | |||
| allowClear | |||
| options={buildCategorizeToOptions([ | |||
| form?.getFieldValue('end_cpn_id'), | |||
| form?.getFieldValue(SwitchElseTo), | |||
| ...getOtherFieldValues(form!, 'conditions', field, 'to'), | |||
| ])} | |||
| /> | |||