@linkdlab/funcnodes_react_flow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/package copy.json +63 -0
- package/package.json +75 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/public/worker_manager +1 -0
- package/src/App.css +38 -0
- package/src/App.test.tsx +9 -0
- package/src/App.tsx +22 -0
- package/src/frontend/datarenderer/images.tsx +28 -0
- package/src/frontend/datarenderer/index.tsx +53 -0
- package/src/frontend/datarenderer/plotly.tsx +82 -0
- package/src/frontend/dialog.scss +88 -0
- package/src/frontend/dialog.tsx +70 -0
- package/src/frontend/edge.scss +15 -0
- package/src/frontend/edge.tsx +31 -0
- package/src/frontend/funcnodesreactflow.scss +63 -0
- package/src/frontend/funcnodesreactflow.tsx +283 -0
- package/src/frontend/header/header.scss +48 -0
- package/src/frontend/header/index.tsx +268 -0
- package/src/frontend/index.tsx +4 -0
- package/src/frontend/layout/htmlelements.scss +63 -0
- package/src/frontend/lib.scss +157 -0
- package/src/frontend/lib.tsx +198 -0
- package/src/frontend/node/index.tsx +3 -0
- package/src/frontend/node/io/default_input_renderer.tsx +327 -0
- package/src/frontend/node/io/default_output_render.tsx +26 -0
- package/src/frontend/node/io/handle_renderer.tsx +89 -0
- package/src/frontend/node/io/index.tsx +4 -0
- package/src/frontend/node/io/io.scss +91 -0
- package/src/frontend/node/io/io.tsx +114 -0
- package/src/frontend/node/io/nodeinput.tsx +125 -0
- package/src/frontend/node/io/nodeoutput.tsx +37 -0
- package/src/frontend/node/node.scss +265 -0
- package/src/frontend/node/node.tsx +208 -0
- package/src/frontend/nodecontextmenu.scss +18 -0
- package/src/frontend/utils/colorpicker.scss +37 -0
- package/src/frontend/utils/colorpicker.tsx +342 -0
- package/src/frontend/utils/jsondata.tsx +19 -0
- package/src/frontend/utils/table.scss +22 -0
- package/src/frontend/utils/table.tsx +159 -0
- package/src/funcnodes/funcnodesworker.ts +455 -0
- package/src/funcnodes/index.ts +4 -0
- package/src/funcnodes/websocketworker.ts +153 -0
- package/src/funcnodes/workermanager.ts +229 -0
- package/src/index.css +13 -0
- package/src/index.tsx +19 -0
- package/src/logo.svg +1 -0
- package/src/react-app-env.d.ts +1 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/setupTests.ts +5 -0
- package/src/state/edge.ts +35 -0
- package/src/state/fnrfzst.ts +440 -0
- package/src/state/index.ts +139 -0
- package/src/state/lib.ts +26 -0
- package/src/state/node.ts +118 -0
- package/src/state/nodespace.ts +151 -0
- package/src/state/reactflow.ts +65 -0
- package/src/types/lib.d.ts +16 -0
- package/src/types/node.d.ts +29 -0
- package/src/types/nodeio.d.ts +82 -0
- package/src/types/worker.d.ts +56 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { EdgeProps, getBezierPath, BaseEdge } from "reactflow";
|
|
2
|
+
import "./edge.scss";
|
|
3
|
+
|
|
4
|
+
const DefaultEdge = ({
|
|
5
|
+
id,
|
|
6
|
+
sourceX,
|
|
7
|
+
sourceY,
|
|
8
|
+
targetX,
|
|
9
|
+
targetY,
|
|
10
|
+
sourcePosition,
|
|
11
|
+
targetPosition,
|
|
12
|
+
data,
|
|
13
|
+
...props
|
|
14
|
+
}: EdgeProps) => {
|
|
15
|
+
const [edgePath, labelX, labelY] = getBezierPath({
|
|
16
|
+
sourceX,
|
|
17
|
+
sourceY,
|
|
18
|
+
sourcePosition,
|
|
19
|
+
targetX,
|
|
20
|
+
targetY,
|
|
21
|
+
targetPosition,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<BaseEdge id={id} path={edgePath} {...props} />
|
|
27
|
+
</>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default DefaultEdge;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@import "./layout/htmlelements.scss";
|
|
2
|
+
:root {
|
|
3
|
+
--funheadercolor: #00d9ff; // Dark background color
|
|
4
|
+
--funcnodesbackground1: hsl(243, 26%, 13%); // Dark background color
|
|
5
|
+
--funcnodesbackground2: hsl(245, 22%, 22%); // Dark background color
|
|
6
|
+
--funcnodesbackground_light: hsl(240, 22%, 38%);
|
|
7
|
+
--containerboarderradius: 1rem;
|
|
8
|
+
--funcnodestextcolor1: #ffffff; // Dark background color
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.funcnodesreactflowcontainer {
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 100%;
|
|
14
|
+
background-color: var(--funcnodesbackground1); // Dark background color
|
|
15
|
+
position: relative;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
color: var(--funcnodestextcolor1);
|
|
19
|
+
|
|
20
|
+
& * {
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.funcnodesreactflowbody {
|
|
26
|
+
flex-grow: 1;
|
|
27
|
+
position: relative;
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: row;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.reactflowlayer {
|
|
34
|
+
flex-grow: 1;
|
|
35
|
+
position: relative;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
background-color: var(--funcnodesbackground2); // Dark background color
|
|
38
|
+
margin: 0.5rem;
|
|
39
|
+
border-radius: var(--containerboarderradius);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.vscrollcontainer {
|
|
43
|
+
overflow-y: auto;
|
|
44
|
+
overflow-x: hidden;
|
|
45
|
+
flex-grow: 1;
|
|
46
|
+
padding: 0.5rem;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.workerselect {
|
|
51
|
+
max-width: 140px;
|
|
52
|
+
}
|
|
53
|
+
.workerselectoption {
|
|
54
|
+
&.selected {
|
|
55
|
+
color: var(--funcnodestextcolor1);
|
|
56
|
+
}
|
|
57
|
+
&.active {
|
|
58
|
+
color: green;
|
|
59
|
+
}
|
|
60
|
+
&.inactive {
|
|
61
|
+
color: red;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
MouseEvent as ReactMouseEvent,
|
|
10
|
+
} from "react";
|
|
11
|
+
import { FuncNodesWorker, WebSocketWorker, WorkerManager } from "../funcnodes";
|
|
12
|
+
import "./funcnodesreactflow.scss";
|
|
13
|
+
import useStore, { RFState } from "../state/reactflow";
|
|
14
|
+
import { shallow } from "zustand/shallow";
|
|
15
|
+
import { useShallow } from "zustand/react/shallow";
|
|
16
|
+
import ReactFlow, {
|
|
17
|
+
Edge,
|
|
18
|
+
Node,
|
|
19
|
+
Background,
|
|
20
|
+
ReactFlowProvider,
|
|
21
|
+
MiniMap,
|
|
22
|
+
useReactFlow,
|
|
23
|
+
FitView,
|
|
24
|
+
NodeTypes,
|
|
25
|
+
EdgeTypes,
|
|
26
|
+
useKeyPress,
|
|
27
|
+
useEdges,
|
|
28
|
+
useNodes,
|
|
29
|
+
} from "reactflow";
|
|
30
|
+
import Library from "./lib";
|
|
31
|
+
import FuncNodesReactFlowZustand, {
|
|
32
|
+
FuncNodesReactFlowZustandInterface,
|
|
33
|
+
} from "../state";
|
|
34
|
+
|
|
35
|
+
import DefaultNode from "./node";
|
|
36
|
+
import ConnectionLine from "./edge";
|
|
37
|
+
|
|
38
|
+
import "./nodecontextmenu.scss";
|
|
39
|
+
import DefaultEdge from "./edge";
|
|
40
|
+
import { Key } from "@mui/icons-material";
|
|
41
|
+
import FuncnodesHeader from "./header";
|
|
42
|
+
|
|
43
|
+
const FuncNodesContext = createContext<FuncNodesReactFlowZustandInterface>(
|
|
44
|
+
FuncNodesReactFlowZustand()
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
type ContextMenuProps = {
|
|
48
|
+
id: string;
|
|
49
|
+
top?: number;
|
|
50
|
+
left?: number;
|
|
51
|
+
right?: number;
|
|
52
|
+
bottom?: number;
|
|
53
|
+
onClick?: () => void;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const ContextMenu = ({
|
|
57
|
+
id,
|
|
58
|
+
top,
|
|
59
|
+
left,
|
|
60
|
+
right,
|
|
61
|
+
bottom,
|
|
62
|
+
...props
|
|
63
|
+
}: ContextMenuProps) => {
|
|
64
|
+
const { getNode, setNodes, addNodes, setEdges } = useReactFlow();
|
|
65
|
+
|
|
66
|
+
const fnrf_zst = useContext(FuncNodesContext);
|
|
67
|
+
|
|
68
|
+
const duplicateNode = useCallback(() => {
|
|
69
|
+
const rfnode = getNode(id);
|
|
70
|
+
if (!rfnode) return;
|
|
71
|
+
const position = {
|
|
72
|
+
x: rfnode.position.x + 50,
|
|
73
|
+
y: rfnode.position.y + 50,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
addNodes({ ...rfnode, id: `${rfnode.id}-copy`, position });
|
|
77
|
+
}, [id, getNode, addNodes]);
|
|
78
|
+
|
|
79
|
+
const deleteNode = useCallback(() => {
|
|
80
|
+
fnrf_zst.on_node_action({ type: "delete", id, from_remote: false });
|
|
81
|
+
}, [id, setNodes, setEdges]);
|
|
82
|
+
|
|
83
|
+
const nodestore = fnrf_zst.nodespace.get_node(id, false);
|
|
84
|
+
if (!nodestore) return <> </>;
|
|
85
|
+
const node: NodeType = nodestore();
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
style={{ top, left, right, bottom }}
|
|
90
|
+
className="context-menu"
|
|
91
|
+
{...props}
|
|
92
|
+
>
|
|
93
|
+
<p style={{ fontWeight: "bold" }}>
|
|
94
|
+
<small>{node.name}</small>
|
|
95
|
+
</p>
|
|
96
|
+
<button onClick={duplicateNode}>duplicate</button>
|
|
97
|
+
<button onClick={deleteNode}>delete</button>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const ReactFlowManager = () => {
|
|
103
|
+
const rfinstance = useReactFlow();
|
|
104
|
+
const fnrf_zst = useContext(FuncNodesContext);
|
|
105
|
+
fnrf_zst.rf_instance = rfinstance;
|
|
106
|
+
return <></>;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const selector = (state: RFState) => ({
|
|
110
|
+
nodes: state.nodes,
|
|
111
|
+
edges: state.edges,
|
|
112
|
+
onNodesChange: state.onNodesChange,
|
|
113
|
+
onEdgesChange: state.onEdgesChange,
|
|
114
|
+
onConnect: state.onConnect,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const nodeTypes: NodeTypes = { default: DefaultNode };
|
|
118
|
+
|
|
119
|
+
const edgeTypes: EdgeTypes = {
|
|
120
|
+
default: DefaultEdge,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const ReactFlowLayer = () => {
|
|
124
|
+
const fnrf_zst: FuncNodesReactFlowZustandInterface =
|
|
125
|
+
useContext(FuncNodesContext);
|
|
126
|
+
|
|
127
|
+
const reactflowRef = useRef<HTMLDivElement>(null);
|
|
128
|
+
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
fnrf_zst.reactflowRef = reactflowRef.current;
|
|
131
|
+
}, [reactflowRef]);
|
|
132
|
+
|
|
133
|
+
const [menu, setMenu] = useState<ContextMenuProps | null>(null);
|
|
134
|
+
|
|
135
|
+
const onNodeContextMenu = useCallback(
|
|
136
|
+
(event: ReactMouseEvent, node: Node) => {
|
|
137
|
+
if (!reactflowRef.current) return;
|
|
138
|
+
// Prevent native context menu from showing
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
|
|
141
|
+
// Calculate position of the context menu. We want to make sure it
|
|
142
|
+
// doesn't get positioned off-screen.
|
|
143
|
+
const pane = reactflowRef.current.getBoundingClientRect();
|
|
144
|
+
const clientX = event.clientX as number;
|
|
145
|
+
const clientY = event.clientY as number;
|
|
146
|
+
setMenu({
|
|
147
|
+
id: node.id,
|
|
148
|
+
top: clientY < pane.height - 200 ? clientY : undefined,
|
|
149
|
+
left: clientX < pane.width - 200 ? clientX : undefined,
|
|
150
|
+
right: clientX >= pane.width - 200 ? pane.width - clientX : undefined,
|
|
151
|
+
bottom:
|
|
152
|
+
clientY >= pane.height - 200 ? pane.height - clientY : undefined,
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
[setMenu]
|
|
156
|
+
);
|
|
157
|
+
const onPaneClick = useCallback(() => setMenu(null), [setMenu]);
|
|
158
|
+
|
|
159
|
+
const { nodes, edges, onNodesChange, onEdgesChange, onConnect } =
|
|
160
|
+
fnrf_zst.useReactFlowStore(useShallow(selector));
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className="reactflowlayer">
|
|
164
|
+
<ReactFlow
|
|
165
|
+
nodes={nodes}
|
|
166
|
+
edges={edges}
|
|
167
|
+
onNodesChange={onNodesChange}
|
|
168
|
+
onEdgesChange={onEdgesChange}
|
|
169
|
+
onConnect={onConnect}
|
|
170
|
+
nodeTypes={nodeTypes}
|
|
171
|
+
edgeTypes={edgeTypes}
|
|
172
|
+
minZoom={0.1}
|
|
173
|
+
maxZoom={2}
|
|
174
|
+
fitView
|
|
175
|
+
ref={reactflowRef}
|
|
176
|
+
// onNodeContextMenu={onNodeContextMenu}
|
|
177
|
+
onPaneClick={onPaneClick}
|
|
178
|
+
//multiSelectionKeyCode="Control"
|
|
179
|
+
>
|
|
180
|
+
<ReactFlowManager />
|
|
181
|
+
<KeyHandler />
|
|
182
|
+
<Background
|
|
183
|
+
color="#888" // Color of the grid lines
|
|
184
|
+
gap={16} // Distance between grid lines
|
|
185
|
+
size={1} // Thickness of the grid lines
|
|
186
|
+
/>
|
|
187
|
+
<MiniMap
|
|
188
|
+
nodeStrokeWidth={3}
|
|
189
|
+
pannable={true}
|
|
190
|
+
zoomable={true}
|
|
191
|
+
zoomStep={3}
|
|
192
|
+
/>
|
|
193
|
+
{menu && <ContextMenu onClick={onPaneClick} {...menu} />}
|
|
194
|
+
</ReactFlow>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
const KeyHandler = () => {
|
|
202
|
+
const fnrf_zst = useContext(FuncNodesContext);
|
|
203
|
+
const delPressed = useKeyPress("Delete");
|
|
204
|
+
const edges = useEdges();
|
|
205
|
+
const nodes = useNodes();
|
|
206
|
+
if (delPressed) {
|
|
207
|
+
for (const edge of edges) {
|
|
208
|
+
if (edge.selected) {
|
|
209
|
+
if (!fnrf_zst.worker) return <></>;
|
|
210
|
+
if (!edge.source || !edge.target) return <></>;
|
|
211
|
+
if (!edge.sourceHandle || !edge.targetHandle) return <></>;
|
|
212
|
+
fnrf_zst.worker?.remove_edge({
|
|
213
|
+
src_nid: edge.source,
|
|
214
|
+
src_ioid: edge.sourceHandle,
|
|
215
|
+
trg_nid: edge.target,
|
|
216
|
+
trg_ioid: edge.targetHandle,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const node of nodes) {
|
|
221
|
+
if (node.selected) {
|
|
222
|
+
if (!fnrf_zst.worker) return <></>;
|
|
223
|
+
fnrf_zst.worker.remove_node(node.id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return <></>;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const InnerFuncnodesReactFlow = ({
|
|
232
|
+
fnrf_zst,
|
|
233
|
+
}: {
|
|
234
|
+
fnrf_zst: FuncNodesReactFlowZustandInterface;
|
|
235
|
+
}) => {
|
|
236
|
+
const [workermanageruri, setWorkermanageruri] = useState<string>("");
|
|
237
|
+
const [worker, setWorker] = useState<FuncNodesWorker | undefined>(undefined);
|
|
238
|
+
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
async function fetch_worker_manager() {
|
|
241
|
+
let response = await fetch("/worker_manager");
|
|
242
|
+
let workerewsuri = await response.text();
|
|
243
|
+
setWorkermanageruri(workerewsuri);
|
|
244
|
+
}
|
|
245
|
+
fetch_worker_manager();
|
|
246
|
+
}, []);
|
|
247
|
+
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (workermanageruri) {
|
|
250
|
+
const workermanager = new WorkerManager(workermanageruri, fnrf_zst);
|
|
251
|
+
workermanager.on_setWorker = setWorker;
|
|
252
|
+
fnrf_zst.workermanager = workermanager;
|
|
253
|
+
}
|
|
254
|
+
}, [workermanageruri]);
|
|
255
|
+
|
|
256
|
+
fnrf_zst.worker = worker;
|
|
257
|
+
// const worker = new WebSocketWorker("ws://localhost:9382", fnrf_zst);
|
|
258
|
+
// fnrf_zst.worker = worker;
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<FuncNodesContext.Provider value={fnrf_zst}>
|
|
262
|
+
<div className="funcnodesreactflowcontainer">
|
|
263
|
+
<FuncnodesHeader></FuncnodesHeader>
|
|
264
|
+
|
|
265
|
+
<div className="funcnodesreactflowbody">
|
|
266
|
+
<Library></Library>
|
|
267
|
+
<ReactFlowLayer></ReactFlowLayer>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</FuncNodesContext.Provider>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const FuncnodesReactFlow = () => {
|
|
275
|
+
const fnrf_zst = FuncNodesReactFlowZustand();
|
|
276
|
+
|
|
277
|
+
// @ts-ignore
|
|
278
|
+
window.fnrf_zst = fnrf_zst; // For debugging
|
|
279
|
+
return <InnerFuncnodesReactFlow fnrf_zst={fnrf_zst} />;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export default FuncnodesReactFlow;
|
|
283
|
+
export { FuncNodesContext };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.funcnodesreactflowheader {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
height: 50px;
|
|
5
|
+
justify-content: flex-start;
|
|
6
|
+
position: relative;
|
|
7
|
+
top: 0;
|
|
8
|
+
left: 0;
|
|
9
|
+
|
|
10
|
+
.headerelement {
|
|
11
|
+
height: 100%;
|
|
12
|
+
// center items vertically
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
margin: 4px 4px 4px 4px;
|
|
16
|
+
position: relative;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
& .statusbar {
|
|
21
|
+
width: 250px;
|
|
22
|
+
height: 1.5rem;
|
|
23
|
+
background-color: var(--funcnodesbackground2);
|
|
24
|
+
display: inline-block;
|
|
25
|
+
margin: 2px 4px 0px 4px;
|
|
26
|
+
position: relative;
|
|
27
|
+
border-radius: 0.5rem;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
|
|
30
|
+
&-progressbar {
|
|
31
|
+
position: absolute;
|
|
32
|
+
top: 0;
|
|
33
|
+
left: 0;
|
|
34
|
+
width: 0;
|
|
35
|
+
height: 100%;
|
|
36
|
+
background-color: var(--funheadercolor);
|
|
37
|
+
display: inline-block;
|
|
38
|
+
}
|
|
39
|
+
&-message {
|
|
40
|
+
position: relative;
|
|
41
|
+
top: 0;
|
|
42
|
+
left: 0;
|
|
43
|
+
font-size: 0.8rem;
|
|
44
|
+
color: var(--funheadercolor);
|
|
45
|
+
mix-blend-mode: difference;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { useContext, useState } from "react";
|
|
2
|
+
import { FuncNodesReactFlowZustandInterface } from "../../state";
|
|
3
|
+
import { FuncNodesContext } from "../funcnodesreactflow";
|
|
4
|
+
|
|
5
|
+
import "./header.scss";
|
|
6
|
+
import CustomDialog from "../dialog";
|
|
7
|
+
|
|
8
|
+
const NewWorkerDialog = ({ trigger }: { trigger: React.ReactNode }) => {
|
|
9
|
+
const [name, setName] = useState<string>("");
|
|
10
|
+
const [copyLib, setCopyLib] = useState<boolean>(false);
|
|
11
|
+
const [copyNS, setCopyNS] = useState<boolean>(false);
|
|
12
|
+
const fnrf_zst: FuncNodesReactFlowZustandInterface =
|
|
13
|
+
useContext(FuncNodesContext);
|
|
14
|
+
|
|
15
|
+
const workersstate = fnrf_zst.workers();
|
|
16
|
+
|
|
17
|
+
const [reference, setReference] = useState<{ name: string; uuid: string }>({
|
|
18
|
+
name: "None",
|
|
19
|
+
uuid: "",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<CustomDialog
|
|
24
|
+
trigger={trigger}
|
|
25
|
+
title="New Worker"
|
|
26
|
+
description="Please provide a name and select a another worker as interpreter reference"
|
|
27
|
+
>
|
|
28
|
+
<div>
|
|
29
|
+
Name:
|
|
30
|
+
<br />
|
|
31
|
+
<input
|
|
32
|
+
className="styledinput"
|
|
33
|
+
onChange={(e) => {
|
|
34
|
+
setName(e.currentTarget.value);
|
|
35
|
+
}}
|
|
36
|
+
value={name}
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<div>
|
|
40
|
+
Reference Worker:
|
|
41
|
+
<br />
|
|
42
|
+
<select
|
|
43
|
+
className="styleddropdown"
|
|
44
|
+
onChange={(e) => {
|
|
45
|
+
const uuid = e.target.value;
|
|
46
|
+
const name = e.target.selectedOptions[0].innerText;
|
|
47
|
+
setReference({ name, uuid });
|
|
48
|
+
}}
|
|
49
|
+
value={reference.uuid}
|
|
50
|
+
>
|
|
51
|
+
<option value="">None</option>
|
|
52
|
+
{Object.keys(workersstate).map((workerid) => (
|
|
53
|
+
<option className={""} key={workerid} value={workerid}>
|
|
54
|
+
{workersstate[workerid].name || workerid}
|
|
55
|
+
</option>
|
|
56
|
+
))}
|
|
57
|
+
</select>
|
|
58
|
+
{reference.uuid && (
|
|
59
|
+
<div>
|
|
60
|
+
<div>
|
|
61
|
+
Copy Lib:{" "}
|
|
62
|
+
<input
|
|
63
|
+
type="checkbox"
|
|
64
|
+
className="styledcheckbox"
|
|
65
|
+
checked={copyLib}
|
|
66
|
+
onChange={(e) => {
|
|
67
|
+
setCopyLib(e.currentTarget.checked);
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
{copyLib && (
|
|
72
|
+
<div>
|
|
73
|
+
Copy Nodespace{" "}
|
|
74
|
+
<input
|
|
75
|
+
type="checkbox"
|
|
76
|
+
className="styledcheckbox"
|
|
77
|
+
checked={copyNS}
|
|
78
|
+
onChange={(e) => {
|
|
79
|
+
setCopyNS(e.currentTarget.checked);
|
|
80
|
+
if (e.currentTarget.checked) {
|
|
81
|
+
setCopyLib(true);
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
{name && (
|
|
90
|
+
<div>
|
|
91
|
+
<button
|
|
92
|
+
className="styledbtn"
|
|
93
|
+
onClick={() => {
|
|
94
|
+
fnrf_zst.workermanager?.new_worker({
|
|
95
|
+
name,
|
|
96
|
+
reference: reference.uuid,
|
|
97
|
+
copyLib,
|
|
98
|
+
copyNS,
|
|
99
|
+
});
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
Create
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
</CustomDialog>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const Statusbar = () => {
|
|
112
|
+
const fnrf_zst: FuncNodesReactFlowZustandInterface =
|
|
113
|
+
useContext(FuncNodesContext);
|
|
114
|
+
const progress = fnrf_zst.progress_state();
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div className="statusbar">
|
|
118
|
+
<span
|
|
119
|
+
className="statusbar-progressbar"
|
|
120
|
+
style={{ width: Math.min(100, 100 * progress.progress) + "%" }}
|
|
121
|
+
></span>
|
|
122
|
+
<span className="statusbar-message">{progress.message}</span>
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const FuncnodesHeader = () => {
|
|
128
|
+
const fnrf_zst: FuncNodesReactFlowZustandInterface =
|
|
129
|
+
useContext(FuncNodesContext);
|
|
130
|
+
|
|
131
|
+
const workersstate = fnrf_zst.workers();
|
|
132
|
+
const onNew = () => {
|
|
133
|
+
const alert = window.confirm("Are you sure you want to start a new flow?");
|
|
134
|
+
if (alert) {
|
|
135
|
+
fnrf_zst.worker?.clear();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const onSave = async () => {
|
|
140
|
+
const data = await fnrf_zst.worker?.save();
|
|
141
|
+
if (!data) return;
|
|
142
|
+
const blob = new Blob([JSON.stringify(data)], {
|
|
143
|
+
type: "application/json",
|
|
144
|
+
});
|
|
145
|
+
const url = URL.createObjectURL(blob);
|
|
146
|
+
const a = document.createElement("a");
|
|
147
|
+
a.href = url;
|
|
148
|
+
a.download = "flow.json";
|
|
149
|
+
a.click();
|
|
150
|
+
URL.revokeObjectURL(url);
|
|
151
|
+
a.remove();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const onOpen = async () => {
|
|
155
|
+
const input = document.createElement("input");
|
|
156
|
+
input.type = "file";
|
|
157
|
+
input.accept = ".json";
|
|
158
|
+
input.onchange = async (e) => {
|
|
159
|
+
const file = (e.target as HTMLInputElement).files?.[0];
|
|
160
|
+
if (!file) return;
|
|
161
|
+
const reader = new FileReader();
|
|
162
|
+
reader.onload = async (e) => {
|
|
163
|
+
const contents = e.target?.result;
|
|
164
|
+
if (!contents) return;
|
|
165
|
+
const data = JSON.parse(contents as string);
|
|
166
|
+
await fnrf_zst.worker?.load(data);
|
|
167
|
+
};
|
|
168
|
+
reader.readAsText(file);
|
|
169
|
+
};
|
|
170
|
+
input.click();
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const workerselectchange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
174
|
+
const workerid = e.target.value;
|
|
175
|
+
if (workerid === "__select__") return;
|
|
176
|
+
if (!fnrf_zst.workers) return;
|
|
177
|
+
if (!fnrf_zst.workermanager) return;
|
|
178
|
+
if (!workersstate[workerid]) return;
|
|
179
|
+
if (!workersstate[workerid].active) {
|
|
180
|
+
//create popup
|
|
181
|
+
const ans = window.confirm(
|
|
182
|
+
"this is an inactive worker, selecting it will start it, continue?"
|
|
183
|
+
);
|
|
184
|
+
if (!ans) return;
|
|
185
|
+
}
|
|
186
|
+
fnrf_zst.workermanager.set_active(workerid);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div className="funcnodesreactflowheader">
|
|
191
|
+
<div className="headerelement">
|
|
192
|
+
<Statusbar></Statusbar>
|
|
193
|
+
</div>
|
|
194
|
+
<div className="headerelement">
|
|
195
|
+
<select
|
|
196
|
+
className="workerselect styleddropdown"
|
|
197
|
+
value={fnrf_zst.worker ? fnrf_zst.worker.uuid : "__select__"}
|
|
198
|
+
onChange={workerselectchange}
|
|
199
|
+
>
|
|
200
|
+
<option disabled value="__select__">
|
|
201
|
+
Select Worker
|
|
202
|
+
</option>
|
|
203
|
+
{Object.keys(workersstate).map((workerid) => (
|
|
204
|
+
<option
|
|
205
|
+
className={
|
|
206
|
+
"workerselectoption" +
|
|
207
|
+
(workersstate[workerid].active ? " active" : " inactive")
|
|
208
|
+
}
|
|
209
|
+
key={workerid}
|
|
210
|
+
value={workerid}
|
|
211
|
+
>
|
|
212
|
+
{workersstate[workerid].name || workerid}
|
|
213
|
+
</option>
|
|
214
|
+
))}
|
|
215
|
+
</select>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{fnrf_zst.worker && (
|
|
219
|
+
<>
|
|
220
|
+
<div className="headerelement">
|
|
221
|
+
<button
|
|
222
|
+
className="styledbtn"
|
|
223
|
+
onClick={() => {
|
|
224
|
+
if (!fnrf_zst.worker) return;
|
|
225
|
+
fnrf_zst.worker.stop();
|
|
226
|
+
}}
|
|
227
|
+
>
|
|
228
|
+
stop worker
|
|
229
|
+
</button>
|
|
230
|
+
</div>
|
|
231
|
+
<div className="headerelement">
|
|
232
|
+
<button
|
|
233
|
+
className="styledbtn"
|
|
234
|
+
onClick={() => {
|
|
235
|
+
if (!fnrf_zst.worker) return;
|
|
236
|
+
fnrf_zst.workermanager?.restart_worker(fnrf_zst.worker.uuid);
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
restart worker
|
|
240
|
+
</button>
|
|
241
|
+
</div>
|
|
242
|
+
</>
|
|
243
|
+
)}
|
|
244
|
+
<div className="headerelement">
|
|
245
|
+
<NewWorkerDialog
|
|
246
|
+
trigger={<button className="styledbtn">new worker</button>}
|
|
247
|
+
></NewWorkerDialog>
|
|
248
|
+
</div>
|
|
249
|
+
<div className="headerelement">
|
|
250
|
+
<button className="styledbtn" onClick={onNew}>
|
|
251
|
+
new nodespace
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
<div className="headerelement">
|
|
255
|
+
<button className="styledbtn" onClick={onOpen}>
|
|
256
|
+
open
|
|
257
|
+
</button>
|
|
258
|
+
</div>
|
|
259
|
+
<div className="headerelement">
|
|
260
|
+
<button className="styledbtn" onClick={onSave}>
|
|
261
|
+
save
|
|
262
|
+
</button>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export default FuncnodesHeader;
|