### What problem does this PR solve? feat: Build the positions of the Switch handle #1739 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.10.0
| @@ -1,11 +1,10 @@ | |||
| import get from 'lodash/get'; | |||
| import intersectionWith from 'lodash/intersectionWith'; | |||
| import isEqual from 'lodash/isEqual'; | |||
| import pick from 'lodash/pick'; | |||
| import { useEffect, useMemo, useState } from 'react'; | |||
| import { useUpdateNodeInternals } from 'reactflow'; | |||
| import { Operator } from '../../constant'; | |||
| import { IPosition, NodeData } from '../../interface'; | |||
| import { buildNewPositionMap } from '../../utils'; | |||
| import { buildNewPositionMap, isKeysEqual } from '../../utils'; | |||
| export const useBuildCategorizeHandlePositions = ({ | |||
| data, | |||
| @@ -14,40 +13,41 @@ export const useBuildCategorizeHandlePositions = ({ | |||
| id: string; | |||
| data: NodeData; | |||
| }) => { | |||
| const operatorName = data.label as Operator; | |||
| const updateNodeInternals = useUpdateNodeInternals(); | |||
| const [positionMap, setPositionMap] = useState<Record<string, IPosition>>({}); | |||
| const categoryData = useMemo( | |||
| () => get(data, 'form.category_description') ?? {}, | |||
| [data], | |||
| ); | |||
| const categoryData = useMemo(() => { | |||
| if (operatorName === Operator.Categorize) { | |||
| return get(data, `form.category_description`, {}); | |||
| } else if (operatorName === Operator.Switch) { | |||
| return get(data, 'form.conditions', []); | |||
| } | |||
| return {}; | |||
| }, [data, operatorName]); | |||
| const positions = useMemo(() => { | |||
| return Object.keys(categoryData) | |||
| .map((x) => { | |||
| .map((x, idx) => { | |||
| const position = positionMap[x]; | |||
| return { text: x, ...position }; | |||
| let text = x; | |||
| if (operatorName === Operator.Switch) { | |||
| text = `Item ${idx + 1}`; | |||
| } | |||
| return { text, ...position }; | |||
| }) | |||
| .filter((x) => typeof x?.right === 'number'); | |||
| }, [categoryData, positionMap]); | |||
| }, [categoryData, positionMap, operatorName]); | |||
| useEffect(() => { | |||
| // Cache used coordinates | |||
| setPositionMap((state) => { | |||
| // index in use | |||
| const indexesInUse = Object.values(state).map((x) => x.idx); | |||
| const categoryDataKeys = Object.keys(categoryData); | |||
| const stateKeys = Object.keys(state); | |||
| if (!isEqual(categoryDataKeys.sort(), stateKeys.sort())) { | |||
| const intersectionKeys = intersectionWith( | |||
| stateKeys, | |||
| if (!isKeysEqual(categoryDataKeys, stateKeys)) { | |||
| const { newPositionMap, intersectionKeys } = buildNewPositionMap( | |||
| categoryDataKeys, | |||
| (categoryDataKey, postionMapKey) => categoryDataKey === postionMapKey, | |||
| ); | |||
| const newPositionMap = buildNewPositionMap( | |||
| categoryDataKeys.filter( | |||
| (x) => !intersectionKeys.some((y) => y === x), | |||
| ), | |||
| indexesInUse, | |||
| state, | |||
| ); | |||
| const nextPositionMap = { | |||
| @@ -68,10 +68,30 @@ export const useBuildCategorizeHandlePositions = ({ | |||
| return { positions }; | |||
| }; | |||
| export const useBuildSwitchHandlePositions = ({ | |||
| data, | |||
| id, | |||
| }: { | |||
| id: string; | |||
| data: NodeData; | |||
| }) => {}; | |||
| // export const useBuildSwitchHandlePositions = ({ | |||
| // data, | |||
| // id, | |||
| // }: { | |||
| // id: string; | |||
| // data: NodeData; | |||
| // }) => { | |||
| // const [positionMap, setPositionMap] = useState<Record<string, IPosition>>({}); | |||
| // const conditions = useMemo(() => get(data, 'form.conditions', []), [data]); | |||
| // const updateNodeInternals = useUpdateNodeInternals(); | |||
| // const positions = useMemo(() => { | |||
| // return conditions | |||
| // .map((x, idx) => { | |||
| // const text = `Item ${idx}`; | |||
| // const position = positionMap[text]; | |||
| // return { text: text, ...position }; | |||
| // }) | |||
| // .filter((x) => typeof x?.right === 'number'); | |||
| // }, [conditions, positionMap]); | |||
| // useEffect(() => { | |||
| // updateNodeInternals(id); | |||
| // }, [id, updateNodeInternals, positionMap]); | |||
| // return { positions }; | |||
| // }; | |||
| @@ -87,7 +87,19 @@ export const operatorIconMap = { | |||
| [Operator.Switch]: SwitchIcon, | |||
| }; | |||
| export const operatorMap = { | |||
| export const operatorMap: Record< | |||
| Operator, | |||
| { | |||
| backgroundColor?: string; | |||
| color?: string; | |||
| width?: number; | |||
| height?: number; | |||
| fontSize?: number; | |||
| iconFontSize?: number; | |||
| iconWidth?: number; | |||
| moreIconColor?: number; | |||
| } | |||
| > = { | |||
| [Operator.Retrieval]: { | |||
| backgroundColor: '#cad6e0', | |||
| color: '#385974', | |||
| @@ -388,7 +400,7 @@ export const initialExeSqlValues = { | |||
| top_n: 30, | |||
| }; | |||
| export const initialSwitchValues = { conditions: [{}] }; | |||
| export const initialSwitchValues = { conditions: [] }; | |||
| export const CategorizeAnchorPointPositions = [ | |||
| { top: 1, right: 34 }, | |||
| @@ -64,11 +64,34 @@ export interface IRelevantForm extends IGenerateForm { | |||
| no: string; | |||
| } | |||
| interface Condition { | |||
| items: Item[]; | |||
| logical_operator: string; | |||
| to: string; | |||
| } | |||
| interface Item { | |||
| cpn_id: string; | |||
| operator: string; | |||
| value: string; | |||
| } | |||
| export interface ISwitchForm { | |||
| conditions: Condition[]; | |||
| end_cpn_id: string; | |||
| no: string; | |||
| } | |||
| export type NodeData = { | |||
| label: string; // operator type | |||
| name: string; // operator name | |||
| color: string; | |||
| form: IBeginForm | IRetrievalForm | IGenerateForm | ICategorizeForm; | |||
| form: | |||
| | IBeginForm | |||
| | IRetrievalForm | |||
| | IGenerateForm | |||
| | ICategorizeForm | |||
| | ISwitchForm; | |||
| }; | |||
| export type IPosition = { top: number; right: number; idx: number }; | |||
| @@ -184,6 +184,14 @@ const useGraphStore = create<RFState>()( | |||
| 'to', | |||
| ]); | |||
| break; | |||
| // case Operator.Switch: | |||
| // if (sourceHandle) | |||
| // updateNodeForm(source, target, [ | |||
| // 'conditions', | |||
| // sourceHandle, | |||
| // 'to', | |||
| // ]); | |||
| // break; | |||
| default: | |||
| break; | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| import { DSLComponents } from '@/interfaces/database/flow'; | |||
| import { removeUselessFieldsFromValues } from '@/utils/form'; | |||
| import { humanId } from 'human-id'; | |||
| import { curry, sample } from 'lodash'; | |||
| import { curry, intersectionWith, isEqual, sample } from 'lodash'; | |||
| import pipe from 'lodash/fp/pipe'; | |||
| import isObject from 'lodash/isObject'; | |||
| import { Edge, Node, Position } from 'reactflow'; | |||
| @@ -172,10 +172,24 @@ export const isEdgeEqual = (previous: Edge, current: Edge) => | |||
| previous.sourceHandle === current.sourceHandle; | |||
| export const buildNewPositionMap = ( | |||
| categoryDataKeys: string[], | |||
| indexesInUse: number[], | |||
| currentKeys: string[], | |||
| previousPositionMap: Record<string, IPosition>, | |||
| ) => { | |||
| return categoryDataKeys.reduce<Record<string, IPosition>>((pre, cur) => { | |||
| // index in use | |||
| const indexesInUse = Object.values(previousPositionMap).map((x) => x.idx); | |||
| const previousKeys = Object.keys(previousPositionMap); | |||
| const intersectionKeys = intersectionWith( | |||
| previousKeys, | |||
| currentKeys, | |||
| (categoryDataKey, positionMapKey) => categoryDataKey === positionMapKey, | |||
| ); | |||
| // difference set | |||
| const currentDifferenceKeys = currentKeys.filter( | |||
| (x) => !intersectionKeys.some((y) => y === x), | |||
| ); | |||
| const newPositionMap = currentDifferenceKeys.reduce< | |||
| Record<string, IPosition> | |||
| >((pre, cur) => { | |||
| // take a coordinate | |||
| const effectiveIdxes = CategorizeAnchorPointPositions.map( | |||
| (x, idx) => idx, | |||
| @@ -188,4 +202,10 @@ export const buildNewPositionMap = ( | |||
| return pre; | |||
| }, {}); | |||
| return { intersectionKeys, newPositionMap }; | |||
| }; | |||
| export const isKeysEqual = (currentKeys: string[], previousKeys: string[]) => { | |||
| return isEqual(currentKeys.sort(), previousKeys.sort()); | |||
| }; | |||