Skip to content

Commit 27edbae

Browse files
committed
Use a centralized canvas state store to manage flow component
1 parent 0b74fc7 commit 27edbae

File tree

5 files changed

+212
-79
lines changed

5 files changed

+212
-79
lines changed

src/components/molecules/flow/index.js

Lines changed: 63 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@ import FlowNode from 'components/atoms/flow/FlowNode';
2727
import { Popover } from '@headlessui/react';
2828
import { generateFlowData } from './flowtestai';
2929
import { GENAI_MODELS } from 'constants/Common';
30+
import useCanvasStore from 'stores/CanvasStore';
31+
32+
import { shallow } from 'zustand/shallow';
3033

3134
const StartNode = () => (
3235
<FlowNode title='Start' handleLeft={false} handleRight={true} handleRightData={{ type: 'source' }}></FlowNode>
3336
);
3437

35-
const init = (flowData) => {
38+
export const init = (flowData) => {
3639
// Initialization
3740
if (flowData && flowData.nodes && flowData.edges) {
3841
return {
@@ -95,36 +98,49 @@ const init = (flowData) => {
9598
}
9699
};
97100

98-
const Flow = ({ tabId, collectionId, flowData }) => {
99-
useEffect(() => {
100-
// Action to perform on tab change
101-
console.log(`Tab changed to: ${tabId}`);
102-
console.log(flowData);
103-
// perform actions based on the new tabId
104-
const result = init(cloneDeep(flowData));
105-
setNodes(result.nodes);
106-
setEdges(result.edges);
107-
}, [tabId]);
108-
109-
const setCanvasDirty = () => {
110-
console.debug('set canvas dirty');
111-
const tab = useTabStore.getState().tabs.find((t) => t.id === tabId);
112-
if (tab) {
113-
tab.isDirty = true;
114-
tab.flowData = {
115-
nodes: nodes.map((node) => {
116-
const _node = JSON.parse(JSON.stringify(node));
117-
return { ..._node };
118-
}),
119-
edges: edges.map((edge) => {
120-
return {
121-
...edge,
122-
animated: false,
123-
};
124-
}),
125-
};
126-
}
127-
};
101+
const selector = (state) => ({
102+
nodes: state.nodes,
103+
edges: state.edges,
104+
onNodesChange: state.onNodesChange,
105+
onEdgesChange: state.onEdgesChange,
106+
onConnect: state.onConnect,
107+
setNodes: state.setNodes,
108+
setEdges: state.setEdges,
109+
});
110+
111+
const Flow = ({ collectionId }) => {
112+
const { nodes, edges, onNodesChange, onEdgesChange, onConnect, setNodes, setEdges } = useCanvasStore(selector);
113+
//console.log(nodes);
114+
115+
// useEffect(() => {
116+
// // Action to perform on tab change
117+
// console.log(`Tab changed to: ${tabId}`);
118+
// console.log(flowData);
119+
// // perform actions based on the new tabId
120+
// const result = init(cloneDeep(flowData));
121+
// setNodes(result.nodes);
122+
// setEdges(result.edges);
123+
// }, [tabId]);
124+
125+
// const setCanvasDirty = () => {
126+
// console.debug('set canvas dirty');
127+
// const tab = useTabStore.getState().tabs.find((t) => t.id === tabId);
128+
// if (tab) {
129+
// tab.isDirty = true;
130+
// tab.flowData = {
131+
// nodes: nodes.map((node) => {
132+
// const _node = JSON.parse(JSON.stringify(node));
133+
// return { ..._node };
134+
// }),
135+
// edges: edges.map((edge) => {
136+
// return {
137+
// ...edge,
138+
// animated: false,
139+
// };
140+
// }),
141+
// };
142+
// }
143+
// };
128144

129145
// notification
130146
// eslint-disable-next-line no-unused-vars
@@ -151,28 +167,20 @@ const Flow = ({ tabId, collectionId, flowData }) => {
151167
[],
152168
);
153169

154-
const [nodes, setNodes, onNodesChange] = useNodesState([]);
155-
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
156-
157-
useEffect(() => {
158-
// skip inital render
159-
if (flowData === undefined || (isEqual(nodes, []) && isEqual(edges, []))) {
160-
return;
161-
}
162-
if (flowData && isEqual(JSON.parse(JSON.stringify(nodes)), flowData.nodes) && isEqual(edges, flowData.edges)) {
163-
console.debug('canvas is unchanged');
164-
return;
165-
}
166-
setCanvasDirty();
167-
}, [nodes, edges]);
168-
169-
const onConnect = (params) => {
170-
const newEdge = {
171-
...params,
172-
type: 'buttonedge',
173-
};
174-
setEdges((eds) => addEdge(newEdge, eds));
175-
};
170+
// const [nodes, setNodes, onNodesChange] = useNodesState([]);
171+
// const [edges, setEdges, onEdgesChange] = useEdgesState([]);
172+
173+
// useEffect(() => {
174+
// // skip inital render
175+
// if (flowData === undefined || (isEqual(nodes, []) && isEqual(edges, []))) {
176+
// return;
177+
// }
178+
// if (flowData && isEqual(JSON.parse(JSON.stringify(nodes)), flowData.nodes) && isEqual(edges, flowData.edges)) {
179+
// console.debug('canvas is unchanged');
180+
// return;
181+
// }
182+
// setCanvasDirty();
183+
// }, [nodes, edges]);
176184

177185
const runnableEdges = (runnable) => {
178186
const updatedEdges = reactFlowInstance.getEdges().map((edge) => {
@@ -220,7 +228,7 @@ const Flow = ({ tabId, collectionId, flowData }) => {
220228
};
221229
console.debug('Dropped node: ', newNode);
222230

223-
setNodes((nds) => nds.concat(newNode));
231+
setNodes([...useCanvasStore.getState().nodes, newNode]);
224232
},
225233
[reactFlowInstance],
226234
);
@@ -279,7 +287,7 @@ const Flow = ({ tabId, collectionId, flowData }) => {
279287
onInit={setReactFlowInstance}
280288
onDrop={onDrop}
281289
onDragOver={onDragOver}
282-
onNodeDragStop={() => setCanvasDirty()}
290+
//onNodeDragStop={() => setCanvasDirty()}
283291
isValidConnection={isValidConnection}
284292
//fitView
285293
>

src/components/molecules/flow/nodes/AuthNode.js

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,37 @@
11
import * as React from 'react';
22
import { PropTypes } from 'prop-types';
33
import FlowNode from 'components/atoms/flow/FlowNode';
4+
import useCanvasStore from 'stores/CanvasStore';
45

5-
const AuthNode = ({ data }) => {
6+
const AuthNode = ({ id, data }) => {
7+
const setAuthNodeType = useCanvasStore((state) => state.setAuthNodeType);
8+
const setBasicAuthValues = useCanvasStore((state) => state.setBasicAuthValues);
69
/* its better to have no space strings as values since they are less error/bug prone */
7-
const initState = () => {
8-
if (data.auth && data.auth.type) {
9-
return data.auth.type; // should return basic-auth
10-
} else {
11-
data.auth = {};
12-
data.auth.type = 'no-auth';
13-
return 'no-auth';
14-
}
15-
};
10+
// const initState = () => {
11+
// if (data.auth && data.auth.type) {
12+
// return data.auth.type; // should return basic-auth
13+
// } else {
14+
// data.auth = {};
15+
// data.auth.type = 'no-auth';
16+
// return 'no-auth';
17+
// }
18+
// };
1619

17-
// const [anchorEl, setAnchorEl] = React.useState(null);
18-
const [auth, setAuth] = React.useState(initState());
20+
// // const [anchorEl, setAnchorEl] = React.useState(null);
21+
// const [auth, setAuth] = React.useState(initState());
1922

2023
/**
2124
* Not sure whether you need to set the value for data props/param or not.
2225
* Technically you should not set the value for a prop/param in a component
2326
* Check this and other places in this file once
2427
*/
2528
const handleChange = (value, option) => {
26-
data.auth[option] = value;
29+
setBasicAuthValues(id, option, value);
2730
};
2831

2932
const handleSelection = (event) => {
30-
const selectedValue = event.target?.value;
31-
setAuth(selectedValue);
32-
data.auth = {};
33-
data.auth.type = selectedValue;
33+
//const selectedValue = event.target?.value;
34+
setAuthNodeType(id, event.target?.value);
3435
};
3536

3637
return (
@@ -47,25 +48,27 @@ const AuthNode = ({ data }) => {
4748
<select
4849
onChange={handleSelection}
4950
name='auth-type'
50-
value={auth}
51-
className='w-full px-1 py-2 border rounded-lg border-neutral-500 text-neutral-500 outline-0 focus:ring-0'
51+
value={data.type ? data.type : 'no-auth'}
52+
className='w-full rounded-lg border border-neutral-500 px-1 py-2 text-neutral-500 outline-0 focus:ring-0'
5253
>
5354
<option value='no-auth'>No Auth</option>
5455
<option value='basic-auth'>Basic Auth</option>
5556
</select>
5657
</div>
57-
{auth === 'basic-auth' && (
58+
{data.type === 'basic-auth' && (
5859
<div>
5960
<input
6061
type='text'
61-
placeholder={data.auth.username ? data.auth.username : 'Username'}
62+
placeholder='Username'
63+
value={data.username ? data.username : ''}
6264
className='nodrag nowheel mb-2 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 outline-blue-300 focus:border-blue-100 focus:ring-blue-100'
6365
name='username'
6466
onChange={(e) => handleChange(e.target.value, 'username')}
6567
/>
6668
<input
6769
type='text'
68-
placeholder={data.auth.password ? data.auth.password : 'Password'}
70+
placeholder='Password'
71+
value={data.password ? data.password : ''}
6972
className='nodrag nowheel block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 outline-blue-300 focus:border-blue-100 focus:ring-blue-100'
7073
name='password'
7174
onChange={(e) => handleChange(e.target.value, 'password')}

src/components/molecules/workspace/WorkspaceContent.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
11
import React from 'react';
2-
import Flow from 'components/molecules/flow';
2+
import Flow, { init } from 'components/molecules/flow';
33
import WorkspaceContentHeader from 'components/molecules/headers/WorkspaceContentHeader';
44
import { useTabStore } from 'stores/TabStore';
5+
import useCanvasStore from 'stores/CanvasStore';
6+
7+
import { cloneDeep, isEqual } from 'lodash';
58

69
const WorkspaceContent = () => {
10+
const setNodes = useCanvasStore((state) => state.setNodes);
11+
const setEdges = useCanvasStore((state) => state.setEdges);
12+
713
const focusTabId = useTabStore((state) => state.focusTabId);
814
const tabs = useTabStore((state) => state.tabs);
915
const focusTab = tabs.find((t) => t.id === focusTabId);
16+
17+
if (focusTab) {
18+
console.log(`Tab changed to: ${focusTabId}`);
19+
console.log(focusTab);
20+
// perform actions based on the new tabId
21+
const result = init(focusTab.flowDataDraft ? focusTab.flowDataDraft : focusTab.flowData);
22+
setNodes(result.nodes);
23+
setEdges(result.edges);
24+
}
25+
1026
return (
11-
<div className='flex flex-col h-full'>
27+
<div className='flex h-full flex-col'>
1228
<WorkspaceContentHeader />
1329
{/* {console.log(focusTab)} */}
1430
{focusTab && <Flow tabId={focusTab.id} collectionId={focusTab.collectionId} flowData={focusTab.flowData} />}

src/stores/CanvasStore.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { create } from 'zustand';
2+
import {
3+
Connection,
4+
Edge,
5+
EdgeChange,
6+
Node,
7+
NodeChange,
8+
addEdge,
9+
OnNodesChange,
10+
OnEdgesChange,
11+
OnConnect,
12+
applyNodeChanges,
13+
applyEdgeChanges,
14+
} from 'reactflow';
15+
import { useTabStore } from './TabStore';
16+
17+
// this is our useStore hook that we can use in our components to get parts of the store and call actions
18+
const useCanvasStore = create((set, get) => ({
19+
nodes: [],
20+
edges: [],
21+
onNodesChange: (changes) => {
22+
set({
23+
nodes: applyNodeChanges(changes, get().nodes),
24+
});
25+
useTabStore.getState().updateFlowTestNodes(get().nodes);
26+
},
27+
onEdgesChange: (changes) => {
28+
set({
29+
edges: applyEdgeChanges(changes, get().edges),
30+
});
31+
useTabStore.getState().updateFlowTestEdges(get().edges);
32+
},
33+
onConnect: (connection) => {
34+
const newEdge = {
35+
...connection,
36+
type: 'buttonedge',
37+
};
38+
set({
39+
edges: addEdge(newEdge, get().edges),
40+
});
41+
useTabStore.getState().updateFlowTestEdges(get().edges);
42+
},
43+
setNodes: (nodes) => {
44+
set({ nodes });
45+
useTabStore.getState().updateFlowTestNodes(get().nodes);
46+
},
47+
setEdges: (edges) => {
48+
set({ edges });
49+
useTabStore.getState().updateFlowTestEdges(get().edges);
50+
},
51+
setAuthNodeType: (nodeId, authType) => {
52+
set({
53+
nodes: get().nodes.map((node) => {
54+
if (node.id === nodeId) {
55+
// it's important to create a new object here, to inform React Flow about the cahnges
56+
node.data = { type: authType };
57+
}
58+
59+
return node;
60+
}),
61+
});
62+
},
63+
setBasicAuthValues: (nodeId, attribute, value) => {
64+
set({
65+
nodes: get().nodes.map((node) => {
66+
if (node.id === nodeId) {
67+
// it's important to create a new object here, to inform React Flow about the cahnges
68+
node.data = {
69+
...node.data,
70+
[attribute]: value,
71+
};
72+
}
73+
74+
return node;
75+
}),
76+
});
77+
},
78+
}));
79+
80+
export default useCanvasStore;

src/stores/TabStore.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { OBJ_TYPES } from 'constants/Common';
2+
import { cloneDeep } from 'lodash';
23
import { create } from 'zustand';
34

45
export const useTabStore = create((set, get) => ({
@@ -31,6 +32,31 @@ export const useTabStore = create((set, get) => ({
3132
set((state) => ({ tabs: [...state.tabs, newTab] }));
3233
set(() => ({ focusTabId: newTab.id }));
3334
},
35+
// these state changes are meant to be triggered by canvas in focus
36+
updateFlowTestNodes: (nodes) => {
37+
if (get().focusTabId) {
38+
const existingTab = get().tabs.find((t) => t.id === get().focusTabId);
39+
if (existingTab) {
40+
if (!existingTab.flowDataDraft) {
41+
existingTab.flowDataDraft = existingTab.flowData ? cloneDeep(existingTab.flowData) : {};
42+
}
43+
existingTab.flowDataDraft.nodes = nodes;
44+
}
45+
console.log(existingTab);
46+
}
47+
},
48+
updateFlowTestEdges: (edges) => {
49+
if (get().focusTabId) {
50+
const existingTab = get().tabs.find((t) => t.id === get().focusTabId);
51+
if (existingTab) {
52+
if (!existingTab.flowDataDraft) {
53+
existingTab.flowDataDraft = cloneDeep(existingTab.flowData);
54+
}
55+
existingTab.flowDataDraft.edges = edges;
56+
}
57+
console.log(existingTab);
58+
}
59+
},
3460
addEnvTab: (env, collectionId) => {
3561
const newTab = {
3662
id: env.id,

0 commit comments

Comments
 (0)