### What problem does this PR solve? feat: Build the edges of Switch by form data #1739 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.10.0
| @@ -4,7 +4,11 @@ import { useEffect, useMemo, useState } from 'react'; | |||
| import { useUpdateNodeInternals } from 'reactflow'; | |||
| import { Operator } from '../../constant'; | |||
| import { IPosition, NodeData } from '../../interface'; | |||
| import { buildNewPositionMap, isKeysEqual } from '../../utils'; | |||
| import { | |||
| buildNewPositionMap, | |||
| generateSwitchHandleText, | |||
| isKeysEqual, | |||
| } from '../../utils'; | |||
| export const useBuildCategorizeHandlePositions = ({ | |||
| data, | |||
| @@ -32,7 +36,7 @@ export const useBuildCategorizeHandlePositions = ({ | |||
| const position = positionMap[x]; | |||
| let text = x; | |||
| if (operatorName === Operator.Switch) { | |||
| text = `Item ${idx + 1}`; | |||
| text = generateSwitchHandleText(idx); | |||
| } | |||
| return { text, ...position }; | |||
| }) | |||
| @@ -28,14 +28,15 @@ interface INameInputProps { | |||
| const getOtherFieldValues = ( | |||
| form: FormInstance, | |||
| formListName: string = 'items', | |||
| field: FormListFieldData, | |||
| latestField: string, | |||
| ) => | |||
| (form.getFieldValue(['items']) ?? []) | |||
| (form.getFieldValue([formListName]) ?? []) | |||
| .map((x: any) => x[latestField]) | |||
| .filter( | |||
| (x: string) => | |||
| x !== form.getFieldValue(['items', field.name, latestField]), | |||
| x !== form.getFieldValue([formListName, field.name, latestField]), | |||
| ); | |||
| const NameInput = ({ | |||
| @@ -132,7 +133,12 @@ const DynamicCategorize = ({ nodeId }: IProps) => { | |||
| ]} | |||
| > | |||
| <NameInput | |||
| otherNames={getOtherFieldValues(form, field, 'name')} | |||
| otherNames={getOtherFieldValues( | |||
| form, | |||
| 'items', | |||
| field, | |||
| 'name', | |||
| )} | |||
| validate={(errors: string[]) => | |||
| form.setFields([ | |||
| { | |||
| @@ -159,7 +165,7 @@ const DynamicCategorize = ({ nodeId }: IProps) => { | |||
| <Select | |||
| allowClear | |||
| options={buildCategorizeToOptions( | |||
| getOtherFieldValues(form, field, 'to'), | |||
| getOtherFieldValues(form, 'items', field, 'to'), | |||
| )} | |||
| /> | |||
| </Form.Item> | |||
| @@ -173,14 +179,6 @@ const DynamicCategorize = ({ nodeId }: IProps) => { | |||
| ); | |||
| }} | |||
| </Form.List> | |||
| {/* <Form.Item noStyle shouldUpdate> | |||
| {() => ( | |||
| <Typography> | |||
| <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre> | |||
| </Typography> | |||
| )} | |||
| </Form.Item> */} | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -53,10 +53,11 @@ import { | |||
| initialSwitchValues, | |||
| initialWikipediaValues, | |||
| } from './constant'; | |||
| import { ICategorizeForm, IRelevantForm } from './interface'; | |||
| import { ICategorizeForm, IRelevantForm, ISwitchForm } from './interface'; | |||
| import useGraphStore, { RFState } from './store'; | |||
| import { | |||
| buildDslComponentsByGraph, | |||
| generateSwitchHandleText, | |||
| receiveMessageError, | |||
| replaceIdWithText, | |||
| } from './utils'; | |||
| @@ -498,6 +499,31 @@ export const useWatchNodeFormDataChange = () => { | |||
| [setEdgesByNodeId], | |||
| ); | |||
| const buildSwitchEdgesByFormData = useCallback( | |||
| (nodeId: string, form: ISwitchForm) => { | |||
| // add | |||
| // delete | |||
| // edit | |||
| const conditions = form.conditions; | |||
| const downstreamEdges = conditions.reduce<Edge[]>((pre, _, idx) => { | |||
| const target = conditions[idx]?.to; | |||
| if (target) { | |||
| pre.push({ | |||
| id: uuid(), | |||
| source: nodeId, | |||
| target, | |||
| sourceHandle: generateSwitchHandleText(idx), | |||
| }); | |||
| } | |||
| return pre; | |||
| }, []); | |||
| setEdgesByNodeId(nodeId, downstreamEdges); | |||
| }, | |||
| [setEdgesByNodeId], | |||
| ); | |||
| useEffect(() => { | |||
| nodes.forEach((node) => { | |||
| const currentNode = getNode(node.id); | |||
| @@ -510,6 +536,9 @@ export const useWatchNodeFormDataChange = () => { | |||
| case Operator.Categorize: | |||
| buildCategorizeEdgesByFormData(node.id, form as ICategorizeForm); | |||
| break; | |||
| case Operator.Switch: | |||
| buildSwitchEdgesByFormData(node.id, form as ISwitchForm); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| @@ -519,5 +548,6 @@ export const useWatchNodeFormDataChange = () => { | |||
| buildCategorizeEdgesByFormData, | |||
| getNode, | |||
| buildRelevantEdgesByFormData, | |||
| buildSwitchEdgesByFormData, | |||
| ]); | |||
| }; | |||
| @@ -23,7 +23,7 @@ import { devtools } from 'zustand/middleware'; | |||
| import { immer } from 'zustand/middleware/immer'; | |||
| import { Operator } from './constant'; | |||
| import { NodeData } from './interface'; | |||
| import { isEdgeEqual } from './utils'; | |||
| import { getOperatorIndex, isEdgeEqual } from './utils'; | |||
| export type RFState = { | |||
| nodes: Node<NodeData>[]; | |||
| @@ -184,14 +184,19 @@ const useGraphStore = create<RFState>()( | |||
| 'to', | |||
| ]); | |||
| break; | |||
| // case Operator.Switch: | |||
| // if (sourceHandle) | |||
| // updateNodeForm(source, target, [ | |||
| // 'conditions', | |||
| // sourceHandle, | |||
| // 'to', | |||
| // ]); | |||
| // break; | |||
| case Operator.Switch: { | |||
| if (sourceHandle) { | |||
| const operatorIndex = getOperatorIndex(sourceHandle); | |||
| if (operatorIndex) { | |||
| updateNodeForm(source, target, [ | |||
| 'conditions', | |||
| operatorIndex, | |||
| 'to', | |||
| ]); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| default: | |||
| break; | |||
| } | |||
| @@ -201,7 +206,11 @@ const useGraphStore = create<RFState>()( | |||
| // Delete the edge on the classification node or relevant node anchor when the anchor is connected to other nodes | |||
| const { edges, getOperatorTypeFromId, deleteEdgeById } = get(); | |||
| // the node containing the anchor | |||
| const anchoredNodes = [Operator.Categorize, Operator.Relevant]; | |||
| const anchoredNodes = [ | |||
| Operator.Categorize, | |||
| Operator.Relevant, | |||
| Operator.Switch, | |||
| ]; | |||
| if ( | |||
| anchoredNodes.some( | |||
| (x) => x === getOperatorTypeFromId(connection.source), | |||
| @@ -265,6 +274,19 @@ const useGraphStore = create<RFState>()( | |||
| 'to', | |||
| ]); | |||
| break; | |||
| case Operator.Switch: { | |||
| if (sourceHandle) { | |||
| const operatorIndex = getOperatorIndex(sourceHandle); | |||
| if (operatorIndex) { | |||
| updateNodeForm(source, undefined, [ | |||
| 'conditions', | |||
| operatorIndex, | |||
| 'to', | |||
| ]); | |||
| } | |||
| } | |||
| break; | |||
| } | |||
| default: | |||
| break; | |||
| } | |||
| @@ -1,10 +1,10 @@ | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { Button, Card, Form, Input, Select, Typography } from 'antd'; | |||
| import React from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { Operator } from '../constant'; | |||
| import { useBuildFormSelectOptions } from '../form-hooks'; | |||
| import { IOperatorForm } from '../interface'; | |||
| import { IOperatorForm, ISwitchForm } from '../interface'; | |||
| import { getOtherFieldValues } from '../utils'; | |||
| const subLabelCol = { | |||
| span: 7, | |||
| @@ -14,17 +14,20 @@ const subWrapperCol = { | |||
| span: 17, | |||
| }; | |||
| const SwitchForm: React.FC = ({ | |||
| form, | |||
| onValuesChange, | |||
| nodeId, | |||
| }: IOperatorForm) => { | |||
| const SwitchForm = ({ onValuesChange, node, form }: IOperatorForm) => { | |||
| const { t } = useTranslation(); | |||
| const buildCategorizeToOptions = useBuildFormSelectOptions( | |||
| Operator.Categorize, | |||
| nodeId, | |||
| Operator.Switch, | |||
| node?.id, | |||
| ); | |||
| const getSelectedConditionTos = () => { | |||
| const conditions: ISwitchForm['conditions'] = | |||
| form?.getFieldValue('conditions'); | |||
| return conditions?.filter((x) => !!x).map((x) => x?.to) ?? []; | |||
| }; | |||
| return ( | |||
| <Form | |||
| labelCol={{ span: 8 }} | |||
| @@ -36,7 +39,10 @@ const SwitchForm: React.FC = ({ | |||
| onValuesChange={onValuesChange} | |||
| > | |||
| <Form.Item label={t('flow.to')} name={['end_cpn_id']}> | |||
| <Select options={buildCategorizeToOptions([])} /> | |||
| <Select | |||
| allowClear | |||
| options={buildCategorizeToOptions(getSelectedConditionTos())} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item label={t('flow.no')} name={['no']}> | |||
| <Input /> | |||
| @@ -65,7 +71,13 @@ const SwitchForm: React.FC = ({ | |||
| </Form.Item> | |||
| <Form.Item label={t('flow.to')} name={[field.name, 'to']}> | |||
| <Select options={buildCategorizeToOptions([])} /> | |||
| <Select | |||
| allowClear | |||
| options={buildCategorizeToOptions([ | |||
| form?.getFieldValue('end_cpn_id'), | |||
| ...getOtherFieldValues(form!, 'conditions', field, 'to'), | |||
| ])} | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item label="Items"> | |||
| <Form.List name={[field.name, 'items']}> | |||
| @@ -1,7 +1,8 @@ | |||
| import { DSLComponents } from '@/interfaces/database/flow'; | |||
| import { removeUselessFieldsFromValues } from '@/utils/form'; | |||
| import { FormInstance, FormListFieldData } from 'antd'; | |||
| import { humanId } from 'human-id'; | |||
| import { curry, intersectionWith, isEqual, sample } from 'lodash'; | |||
| import { curry, get, intersectionWith, isEqual, sample } from 'lodash'; | |||
| import pipe from 'lodash/fp/pipe'; | |||
| import isObject from 'lodash/isObject'; | |||
| import { Edge, Node, Position } from 'reactflow'; | |||
| @@ -209,3 +210,27 @@ export const buildNewPositionMap = ( | |||
| export const isKeysEqual = (currentKeys: string[], previousKeys: string[]) => { | |||
| return isEqual(currentKeys.sort(), previousKeys.sort()); | |||
| }; | |||
| export const getOperatorIndex = (handleTitle: string) => { | |||
| return handleTitle.split(' ').at(-1); | |||
| }; | |||
| // Get the value of other forms except itself | |||
| export const getOtherFieldValues = ( | |||
| form: FormInstance, | |||
| formListName: string = 'items', | |||
| field: FormListFieldData, | |||
| latestField: string, | |||
| ) => | |||
| (form.getFieldValue([formListName]) ?? []) | |||
| .map((x: any) => { | |||
| return get(x, latestField); | |||
| }) | |||
| .filter( | |||
| (x: string) => | |||
| x !== form.getFieldValue([formListName, field.name, latestField]), | |||
| ); | |||
| export const generateSwitchHandleText = (idx: number) => { | |||
| return `Item ${idx + 1}`; | |||
| }; | |||