@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.
Files changed (67) hide show
  1. package/README.md +46 -0
  2. package/package copy.json +63 -0
  3. package/package.json +75 -0
  4. package/public/favicon.ico +0 -0
  5. package/public/index.html +43 -0
  6. package/public/logo192.png +0 -0
  7. package/public/logo512.png +0 -0
  8. package/public/manifest.json +25 -0
  9. package/public/robots.txt +3 -0
  10. package/public/worker_manager +1 -0
  11. package/src/App.css +38 -0
  12. package/src/App.test.tsx +9 -0
  13. package/src/App.tsx +22 -0
  14. package/src/frontend/datarenderer/images.tsx +28 -0
  15. package/src/frontend/datarenderer/index.tsx +53 -0
  16. package/src/frontend/datarenderer/plotly.tsx +82 -0
  17. package/src/frontend/dialog.scss +88 -0
  18. package/src/frontend/dialog.tsx +70 -0
  19. package/src/frontend/edge.scss +15 -0
  20. package/src/frontend/edge.tsx +31 -0
  21. package/src/frontend/funcnodesreactflow.scss +63 -0
  22. package/src/frontend/funcnodesreactflow.tsx +283 -0
  23. package/src/frontend/header/header.scss +48 -0
  24. package/src/frontend/header/index.tsx +268 -0
  25. package/src/frontend/index.tsx +4 -0
  26. package/src/frontend/layout/htmlelements.scss +63 -0
  27. package/src/frontend/lib.scss +157 -0
  28. package/src/frontend/lib.tsx +198 -0
  29. package/src/frontend/node/index.tsx +3 -0
  30. package/src/frontend/node/io/default_input_renderer.tsx +327 -0
  31. package/src/frontend/node/io/default_output_render.tsx +26 -0
  32. package/src/frontend/node/io/handle_renderer.tsx +89 -0
  33. package/src/frontend/node/io/index.tsx +4 -0
  34. package/src/frontend/node/io/io.scss +91 -0
  35. package/src/frontend/node/io/io.tsx +114 -0
  36. package/src/frontend/node/io/nodeinput.tsx +125 -0
  37. package/src/frontend/node/io/nodeoutput.tsx +37 -0
  38. package/src/frontend/node/node.scss +265 -0
  39. package/src/frontend/node/node.tsx +208 -0
  40. package/src/frontend/nodecontextmenu.scss +18 -0
  41. package/src/frontend/utils/colorpicker.scss +37 -0
  42. package/src/frontend/utils/colorpicker.tsx +342 -0
  43. package/src/frontend/utils/jsondata.tsx +19 -0
  44. package/src/frontend/utils/table.scss +22 -0
  45. package/src/frontend/utils/table.tsx +159 -0
  46. package/src/funcnodes/funcnodesworker.ts +455 -0
  47. package/src/funcnodes/index.ts +4 -0
  48. package/src/funcnodes/websocketworker.ts +153 -0
  49. package/src/funcnodes/workermanager.ts +229 -0
  50. package/src/index.css +13 -0
  51. package/src/index.tsx +19 -0
  52. package/src/logo.svg +1 -0
  53. package/src/react-app-env.d.ts +1 -0
  54. package/src/reportWebVitals.ts +15 -0
  55. package/src/setupTests.ts +5 -0
  56. package/src/state/edge.ts +35 -0
  57. package/src/state/fnrfzst.ts +440 -0
  58. package/src/state/index.ts +139 -0
  59. package/src/state/lib.ts +26 -0
  60. package/src/state/node.ts +118 -0
  61. package/src/state/nodespace.ts +151 -0
  62. package/src/state/reactflow.ts +65 -0
  63. package/src/types/lib.d.ts +16 -0
  64. package/src/types/node.d.ts +29 -0
  65. package/src/types/nodeio.d.ts +82 -0
  66. package/src/types/worker.d.ts +56 -0
  67. 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;
@@ -0,0 +1,4 @@
1
+ import FuncnodesReactFlow, { FuncNodesContext } from "./funcnodesreactflow";
2
+
3
+ export default FuncnodesReactFlow;
4
+ export { FuncNodesContext };