Skip to content

Commit 1aba5fd

Browse files
committed
render each tab correctly, render updates to canvas and save them
1 parent 214c3fd commit 1aba5fd

File tree

8 files changed

+167
-131
lines changed

8 files changed

+167
-131
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"eslint-import-resolver-alias": "^1.1.2",
1919
"eslint-plugin-import": "^2.29.1",
2020
"immer": "^10.0.4",
21+
"lodash": "^4.17.21",
2122
"notistack": "^3.0.1",
2223
"postcss": "^8.4.35",
2324
"react": "^18.2.0",

packages/flowtest-electron/src/ipc/collection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
181181
ipcMain.handle('renderer:read-flowtest', async (event, pathname, collectionId) => {
182182
try {
183183
const content = readFile(pathname);
184-
const flowData = content === '{}' ? undefined : readableDataToFlowData(content);
184+
const flowData = content === '{}' ? undefined : readableDataToFlowData(JSON.parse(content));
185185
mainWindow.webContents.send('main:read-flowtest', pathname, collectionId, flowData);
186186
} catch (error) {
187187
return Promise.reject(error);

src/components/molecules/flow/index.js

Lines changed: 99 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useCallback, useMemo, useState, useEffect } from 'react';
22
import ReactFlow, { useNodesState, useEdgesState, addEdge, Controls, Background, ControlButton } from 'reactflow';
33
import 'reactflow/dist/style.css';
4+
import { cloneDeep, isEqual } from 'lodash';
45

56
// css
67
import './index.css';
@@ -27,12 +28,96 @@ const StartNode = () => (
2728
<FlowNode title='Start' handleLeft={false} handleRight={true} handleRightData={{ type: 'source' }}></FlowNode>
2829
);
2930

31+
const init = (flowData) => {
32+
// Initialization
33+
if (flowData && flowData.nodes && flowData.edges) {
34+
return {
35+
nodes: flowData.nodes,
36+
edges: flowData.edges,
37+
};
38+
} else if (flowData && flowData.nodes && !flowData.edges) {
39+
// AI prompt generated
40+
const nodes = [
41+
{ id: '0', type: 'startNode', position: { x: 150, y: 150 }, deletable: false },
42+
{ id: '1', type: 'authNode', position: { x: 400, y: 150 }, data: {}, deletable: false },
43+
];
44+
const edges = [
45+
{
46+
id: `reactflow__edge-0-1`,
47+
source: `0`,
48+
sourceHandle: null,
49+
target: `1`,
50+
targetHandle: null,
51+
type: 'buttonedge',
52+
},
53+
];
54+
for (let i = 2; i <= flowData.nodes.length; i++) {
55+
nodes.push({
56+
id: `${i}`,
57+
type: flowData.nodes[i - 1].type,
58+
position: { x: 150 + i * 500, y: 50 },
59+
data: flowData.nodes[i - 1],
60+
});
61+
edges.push({
62+
id: `reactflow__edge-${i - 1}-${i}`,
63+
source: `${i - 1}`,
64+
sourceHandle: null,
65+
target: `${i}`,
66+
targetHandle: null,
67+
type: 'buttonedge',
68+
});
69+
}
70+
return {
71+
nodes: flowData.nodes,
72+
edges: flowData.edges,
73+
};
74+
} else {
75+
return {
76+
nodes: [
77+
{ id: '0', type: 'startNode', position: { x: 150, y: 150 }, deletable: false },
78+
{ id: '1', type: 'authNode', position: { x: 400, y: 150 }, data: {}, deletable: false },
79+
],
80+
edges: [
81+
{
82+
id: `reactflow__edge-0-1`,
83+
source: `0`,
84+
sourceHandle: null,
85+
target: `1`,
86+
targetHandle: null,
87+
type: 'buttonedge',
88+
},
89+
],
90+
};
91+
}
92+
};
93+
3094
const Flow = ({ tabId, collectionId, flowData }) => {
95+
useEffect(() => {
96+
// Action to perform on tab change
97+
console.log(`Tab changed to: ${tabId}`);
98+
console.log(flowData);
99+
// perform actions based on the new tabId
100+
const result = init(cloneDeep(flowData));
101+
setNodes(result.nodes);
102+
setEdges(result.edges);
103+
}, [tabId]);
104+
31105
const setCanvasDirty = () => {
106+
console.debug('set canvas dirty');
32107
const tab = useTabStore.getState().tabs.find((t) => t.id === tabId);
33108
if (tab) {
34109
tab.isDirty = true;
35-
tab.flowData = getFlowData();
110+
tab.flowData = {
111+
nodes: nodes.map((node) => {
112+
return { ...node };
113+
}),
114+
edges: edges.map((edge) => {
115+
return {
116+
...edge,
117+
animated: false,
118+
};
119+
}),
120+
};
36121
}
37122
};
38123

@@ -41,21 +126,6 @@ const Flow = ({ tabId, collectionId, flowData }) => {
41126

42127
const [reactFlowInstance, setReactFlowInstance] = useState(null);
43128

44-
const getFlowData = () => {
45-
if (reactFlowInstance) {
46-
// save might get triggered when the flow is running, don't store animated edges
47-
const updatedEdges = reactFlowInstance.getEdges().map((edge) => {
48-
return {
49-
...edge,
50-
animated: false,
51-
};
52-
});
53-
const rfInstanceObject = reactFlowInstance.toObject();
54-
rfInstanceObject.edges = updatedEdges;
55-
return rfInstanceObject;
56-
}
57-
};
58-
59129
const nodeTypes = useMemo(
60130
() => ({
61131
startNode: StartNode,
@@ -75,9 +145,21 @@ const Flow = ({ tabId, collectionId, flowData }) => {
75145
[],
76146
);
77147

78-
const [nodes, setNodes, onNodesChange] = useNodesState();
148+
const [nodes, setNodes, onNodesChange] = useNodesState([]);
79149
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
80150

151+
useEffect(() => {
152+
// skip inital render
153+
if (isEqual(nodes, []) && isEqual(edges, [])) {
154+
return;
155+
}
156+
if (flowData && isEqual(nodes, flowData.nodes) && isEqual(edges, flowData.edges)) {
157+
console.debug('canvas is unchanged');
158+
return;
159+
}
160+
setCanvasDirty();
161+
}, [nodes, edges]);
162+
81163
const onConnect = (params) => {
82164
const newEdge = {
83165
...params,
@@ -133,72 +215,10 @@ const Flow = ({ tabId, collectionId, flowData }) => {
133215
console.debug('Dropped node: ', newNode);
134216

135217
setNodes((nds) => nds.concat(newNode));
136-
setCanvasDirty();
137218
},
138219
[reactFlowInstance],
139220
);
140221

141-
// Initialization
142-
useEffect(() => {
143-
if (flowData && flowData.nodes && flowData.edges) {
144-
setNodes(flowData.nodes);
145-
setEdges(flowData.edges);
146-
} else if (flowData && flowData.nodes) {
147-
// AI prompt generated
148-
const nodes = [
149-
{ id: '0', type: 'startNode', position: { x: 150, y: 150 }, deletable: false },
150-
{ id: '1', type: 'authNode', position: { x: 400, y: 150 }, data: {}, deletable: false },
151-
];
152-
const edges = [
153-
{
154-
id: `reactflow__edge-0-1`,
155-
source: `0`,
156-
sourceHandle: null,
157-
target: `1`,
158-
targetHandle: null,
159-
type: 'buttonedge',
160-
},
161-
];
162-
163-
for (let i = 2; i <= flowData.nodes.length; i++) {
164-
nodes.push({
165-
id: `${i}`,
166-
type: flowData.nodes[i - 1].type,
167-
position: { x: 150 + i * 500, y: 50 },
168-
data: flowData.nodes[i - 1],
169-
});
170-
171-
edges.push({
172-
id: `reactflow__edge-${i - 1}-${i}`,
173-
source: `${i - 1}`,
174-
sourceHandle: null,
175-
target: `${i}`,
176-
targetHandle: null,
177-
type: 'buttonedge',
178-
});
179-
}
180-
setNodes(nodes);
181-
setEdges(edges);
182-
setCanvasDirty();
183-
} else {
184-
setNodes([
185-
{ id: '0', type: 'startNode', position: { x: 150, y: 150 }, deletable: false },
186-
{ id: '1', type: 'authNode', position: { x: 400, y: 150 }, data: {}, deletable: false },
187-
]);
188-
setEdges([
189-
{
190-
id: `reactflow__edge-0-1`,
191-
source: `0`,
192-
sourceHandle: null,
193-
target: `1`,
194-
targetHandle: null,
195-
type: 'buttonedge',
196-
},
197-
]);
198-
setCanvasDirty();
199-
}
200-
}, []);
201-
202222
const isValidConnection = (connection) => {
203223
let canConnect = true;
204224
// Only 1 outgoing edge from each (source, sourceHandle) is allowed

src/components/molecules/headers/WorkspaceContentHeader.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,34 @@ import SelectAuthKeys from '../../atoms/SelectAuthKeys';
55
import SaveFlowModal from '../modals/SaveFlowModal';
66
import Tippy from '@tippyjs/react';
77
import 'tippy.js/dist/tippy.css';
8+
import { useTabStore } from 'stores/TabStore';
89

910
const WorkspaceContentHeader = () => {
11+
const focusTabId = useTabStore.getState().focusTabId;
12+
const tabs = useTabStore.getState().tabs;
13+
const focusTab = tabs.find((t) => t.id === focusTabId);
14+
1015
return (
1116
<div className='flex items-center justify-between gap-4 px-6 py-2 border-b border-neutral-300'>
12-
<EditableTextItem initialText='Untitled Flow' />
13-
<div className='flex items-center justify-between gap-x-4'>
14-
<SelectAuthKeys />
15-
<SaveFlowModal />
16-
<button>
17-
<Tippy content='Coming Soon!' placement='top'>
18-
<DocumentArrowDownIcon className='w-5 h-5' />
19-
</Tippy>
20-
</button>
21-
<button>
22-
<Tippy content='Coming Soon!' placement='top'>
23-
<DocumentArrowUpIcon className='w-5 h-5' />
24-
</Tippy>
25-
</button>
26-
</div>
17+
{focusTab && (
18+
<>
19+
<div className='text-xl'>{focusTab.name}</div>
20+
<div className='flex items-center justify-between gap-x-4'>
21+
{/* <SelectAuthKeys /> */}
22+
<SaveFlowModal tab={focusTab} />
23+
<button>
24+
<Tippy content='Coming Soon!' placement='top'>
25+
<DocumentArrowDownIcon className='w-5 h-5' />
26+
</Tippy>
27+
</button>
28+
<button>
29+
<Tippy content='Coming Soon!' placement='top'>
30+
<DocumentArrowUpIcon className='w-5 h-5' />
31+
</Tippy>
32+
</button>
33+
</div>
34+
</>
35+
)}
2736
</div>
2837
);
2938
};

src/components/molecules/modals/SaveFlowModal.js

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,26 @@ import { Dialog, Transition } from '@headlessui/react';
33
import { InboxArrowDownIcon } from '@heroicons/react/20/solid';
44
import Tippy from '@tippyjs/react';
55
import 'tippy.js/dist/tippy.css';
6-
import { useTabStore } from 'stores/TabStore';
76
import { updateFlowTest } from 'service/collection';
87

9-
const SaveFlowModal = () => {
8+
const SaveFlowModal = ({ tab }) => {
109
let [isOpen, setIsOpen] = useState(false);
11-
const focusTabId = useTabStore((state) => state.focusTabId);
12-
const tabs = useTabStore((state) => state.tabs);
1310

1411
function closeModal() {
1512
setIsOpen(false);
1613
}
1714

1815
function saveHandle() {
19-
console.log(`saveHandle :: ${focusTabId}`);
20-
console.log(`saveHandle :: ${JSON.stringify(tabs)}`);
21-
// if un named file ==> open the modal
22-
// setIsOpen(true);
23-
// else save the data
24-
const tab = tabs.find((tab) => tab.id === focusTabId);
25-
2616
console.log(`saveHandle 3 :: ${tab}`);
2717
console.log(`saveHandle 3 :: ${JSON.stringify(tab)}`);
28-
updateFlowTest(tab.pathname, tab.flowData, tab.collectionId);
18+
updateFlowTest(tab.pathname, tab.flowData, tab.collectionId)
19+
.then((result) => {
20+
console.log(`Updated flowtest: path = ${tab.pathname}, collectionId = ${tab.collectionId}`);
21+
})
22+
.catch((error) => {
23+
// TODO: show error in UI
24+
console.log(`Error updating flowtest = ${tab.pathname}: ${error}`);
25+
});
2926
}
3027

3128
// ToDo: Save the file with the given file name

src/components/molecules/workspace/WorkspaceContent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const WorkspaceContent = () => {
1010
return (
1111
<div className='flex flex-col h-full'>
1212
<WorkspaceContentHeader />
13-
{console.log(focusTab)}
13+
{/* {console.log(focusTab)} */}
1414
{focusTab && <Flow tabId={focusTab.id} collectionId={focusTab.collectionId} flowData={focusTab.flowData} />}
1515
{/* <div className='rachit-test'>{focusTabId}</div> */}
1616
</div>

src/service/collection.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -210,20 +210,25 @@ export const readFlowTest = (pathname, collectionId) => {
210210
// rename flowtest
211211
// tab id is flowtest id, so when rename event happens
212212
export const updateFlowTest = (pathname, flowData, collectionId) => {
213-
const { ipcRenderer } = window;
213+
try {
214+
const { ipcRenderer } = window;
214215

215-
const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);
216-
if (collection) {
217-
const flowtest = findItemInCollectionByPathname(collection, pathname);
218-
if (flowtest) {
219-
return new Promise((resolve, reject) => {
220-
ipcRenderer.invoke('renderer:update-flowtest', pathname, flowData).then(resolve).catch(reject);
221-
});
216+
const collection = useCollectionStore.getState().collections.find((c) => c.id === collectionId);
217+
if (collection) {
218+
const flowtest = findItemInCollectionByPathname(collection, pathname);
219+
if (flowtest) {
220+
return new Promise((resolve, reject) => {
221+
ipcRenderer.invoke('renderer:update-flowtest', pathname, flowData).then(resolve).catch(reject);
222+
});
223+
} else {
224+
return Promise.reject(new Error('A flowtest with this path does not exist'));
225+
}
222226
} else {
223-
return Promise.reject(new Error('A flowtest with this path does not exist'));
227+
return Promise.reject(new Error('Collection not found'));
224228
}
225-
} else {
226-
return Promise.reject(new Error('Collection not found'));
229+
} catch (error) {
230+
console.log(`Error updating flowtest: ${error}`);
231+
// TODO: show error in UI
227232
}
228233
};
229234

0 commit comments

Comments
 (0)