feat: set the edge as the data source to achieve two-way linkage betw… (#1299)

### What problem does this PR solve?

feat: set the edge as the data source to achieve two-way linkage between
the edge and the to field. #918

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2024-06-27 18:09:02 +08:00 committed by GitHub
parent 5a1e01d96f
commit 840e921e96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 49 additions and 41 deletions

View File

@ -22,7 +22,8 @@ const CategorizeHandle = ({ top, right, text, idx }: IProps) => {
<Handle <Handle
type="source" type="source"
position={Position.Right} position={Position.Right}
id={`CategorizeHandle${idx}`} // id={`CategorizeHandle${idx}`}
id={text}
isConnectable isConnectable
style={{ style={{
...DEFAULT_HANDLE_STYLE, ...DEFAULT_HANDLE_STYLE,

View File

@ -11,10 +11,7 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const options = useBuildCategorizeToOptions(); const options = useBuildCategorizeToOptions();
const { handleSelectChange } = useHandleToSelectChange( const { handleSelectChange } = useHandleToSelectChange(nodeId);
options.map((x) => x.value),
nodeId,
);
return ( return (
<> <>
@ -64,7 +61,9 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
<Select <Select
allowClear allowClear
options={options} options={options}
onChange={handleSelectChange} onChange={handleSelectChange(
form.getFieldValue(['items', field.name, 'name']),
)}
/> />
</Form.Item> </Form.Item>
</Card> </Card>

View File

@ -1,11 +1,13 @@
import get from 'lodash/get'; import get from 'lodash/get';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect } from 'react';
import { Edge, Node } from 'reactflow';
import { Operator } from '../constant'; import { Operator } from '../constant';
import { import {
ICategorizeItem, ICategorizeItem,
ICategorizeItemResult, ICategorizeItemResult,
IOperatorForm, IOperatorForm,
NodeData,
} from '../interface'; } from '../interface';
import useGraphStore from '../store'; import useGraphStore from '../store';
@ -33,10 +35,18 @@ export const useBuildCategorizeToOptions = () => {
*/ */
const buildCategorizeListFromObject = ( const buildCategorizeListFromObject = (
categorizeItem: ICategorizeItemResult, categorizeItem: ICategorizeItemResult,
edges: Edge[],
node?: Node<NodeData>,
) => { ) => {
// Categorize's to field has two data sources, with edges as the data source.
// Changes in the edge or to field need to be synchronized to the form field.
return Object.keys(categorizeItem).reduce<Array<ICategorizeItem>>( return Object.keys(categorizeItem).reduce<Array<ICategorizeItem>>(
(pre, cur) => { (pre, cur) => {
pre.push({ name: cur, ...categorizeItem[cur] }); // synchronize edge data to the to field
const edge = edges.find(
(x) => x.source === node?.id && x.sourceHandle === cur,
);
pre.push({ name: cur, ...categorizeItem[cur], to: edge?.target });
return pre; return pre;
}, },
[], [],
@ -70,6 +80,8 @@ export const useHandleFormValuesChange = ({
form, form,
node, node,
}: IOperatorForm) => { }: IOperatorForm) => {
const edges = useGraphStore((state) => state.edges);
const handleValuesChange = useCallback( const handleValuesChange = useCallback(
(changedValues: any, values: any) => { (changedValues: any, values: any) => {
console.info(changedValues, values); console.info(changedValues, values);
@ -85,43 +97,29 @@ export const useHandleFormValuesChange = ({
form?.setFieldsValue({ form?.setFieldsValue({
items: buildCategorizeListFromObject( items: buildCategorizeListFromObject(
get(node, 'data.form.category_description', {}), get(node, 'data.form.category_description', {}),
edges,
node,
), ),
}); });
}, [form, node]); }, [form, node, edges]);
return { handleValuesChange }; return { handleValuesChange };
}; };
export const useHandleToSelectChange = ( export const useHandleToSelectChange = (nodeId?: string) => {
opstionIds: string[], const { addEdge } = useGraphStore((state) => state);
nodeId?: string,
) => {
// const [previousTarget, setPreviousTarget] = useState('');
const previousTarget = useRef('');
const { addEdge, deleteEdgeBySourceAndTarget } = useGraphStore(
(state) => state,
);
const handleSelectChange = useCallback( const handleSelectChange = useCallback(
(value?: string) => { (name?: string) => (value?: string) => {
if (nodeId) { if (nodeId && value && name) {
if (previousTarget.current) {
// delete previous edge
deleteEdgeBySourceAndTarget(nodeId, previousTarget.current);
}
if (value) {
addEdge({ addEdge({
source: nodeId, source: nodeId,
target: value, target: value,
sourceHandle: 'b', sourceHandle: name,
targetHandle: 'd', targetHandle: null,
}); });
} else {
// if the value is empty, delete the edges between the current node and all nodes in the drop-down box.
}
previousTarget.current = value;
} }
}, },
[addEdge, nodeId, deleteEdgeBySourceAndTarget], [addEdge, nodeId],
); );
return { handleSelectChange }; return { handleSelectChange };

View File

@ -169,7 +169,7 @@ export const useWatchGraphChange = () => {
const edges = useGraphStore((state) => state.edges); const edges = useGraphStore((state) => state.edges);
useDebounceEffect( useDebounceEffect(
() => { () => {
console.info('useDebounceEffect'); // console.info('useDebounceEffect');
}, },
[nodes, edges], [nodes, edges],
{ {

View File

@ -1,5 +1,6 @@
import type {} from '@redux-devtools/extension'; import type {} from '@redux-devtools/extension';
import { humanId } from 'human-id'; import { humanId } from 'human-id';
import lodashSet from 'lodash/set';
import { import {
Connection, Connection,
Edge, Edge,
@ -36,6 +37,7 @@ export type RFState = {
addNode: (nodes: Node) => void; addNode: (nodes: Node) => void;
getNode: (id: string) => Node | undefined; getNode: (id: string) => Node | undefined;
addEdge: (connection: Connection) => void; addEdge: (connection: Connection) => void;
getEdge: (id: string) => Edge | undefined;
deletePreviousEdgeOfClassificationNode: (connection: Connection) => void; deletePreviousEdgeOfClassificationNode: (connection: Connection) => void;
duplicateNode: (id: string) => void; duplicateNode: (id: string) => void;
deleteEdge: () => void; deleteEdge: () => void;
@ -43,7 +45,7 @@ export type RFState = {
deleteNodeById: (id: string) => void; deleteNodeById: (id: string) => void;
deleteEdgeBySourceAndTarget: (source: string, target: string) => void; deleteEdgeBySourceAndTarget: (source: string, target: string) => void;
findNodeByName: (operatorName: Operator) => Node | undefined; findNodeByName: (operatorName: Operator) => Node | undefined;
findNodeById: (id: string) => Node | undefined; updateMutableNodeFormItem: (id: string, field: string, value: any) => void;
}; };
// this is our useStore hook that we can use in our components to get parts of the store and call actions // this is our useStore hook that we can use in our components to get parts of the store and call actions
@ -92,6 +94,10 @@ const useGraphStore = create<RFState>()(
set({ set({
edges: addEdge(connection, get().edges), edges: addEdge(connection, get().edges),
}); });
get().deletePreviousEdgeOfClassificationNode(connection);
},
getEdge: (id: string) => {
return get().edges.find((x) => x.id === id);
}, },
deletePreviousEdgeOfClassificationNode: (connection: Connection) => { deletePreviousEdgeOfClassificationNode: (connection: Connection) => {
// Delete the edge on the classification node anchor when the anchor is connected to other nodes // Delete the edge on the classification node anchor when the anchor is connected to other nodes
@ -164,9 +170,6 @@ const useGraphStore = create<RFState>()(
findNodeByName: (name: Operator) => { findNodeByName: (name: Operator) => {
return get().nodes.find((x) => x.data.label === name); return get().nodes.find((x) => x.data.label === name);
}, },
findNodeById: (id: string) => {
return get().nodes.find((x) => x.id === id);
},
updateNodeForm: (nodeId: string, values: any) => { updateNodeForm: (nodeId: string, values: any) => {
set({ set({
nodes: get().nodes.map((node) => { nodes: get().nodes.map((node) => {
@ -178,6 +181,13 @@ const useGraphStore = create<RFState>()(
}), }),
}); });
}, },
updateMutableNodeFormItem: (id: string, field: string, value: any) => {
const { nodes } = get();
const idx = nodes.findIndex((x) => x.id === id);
if (idx) {
lodashSet(nodes, [idx, 'data', 'form', field], value);
}
},
}), }),
{ name: 'graph' }, { name: 'graph' },
), ),