@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,327 @@
1
+ import { useContext, useEffect, useRef, useState } from "react";
2
+ import { FuncNodesReactFlowZustandInterface } from "../../../state";
3
+
4
+ import { FuncNodesContext } from "../../funcnodesreactflow";
5
+ import CustomColorPicker from "../../utils/colorpicker";
6
+
7
+ const BooleanInput = ({ io, inputconverter }: InputRendererProps) => {
8
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
9
+ useContext(FuncNodesContext);
10
+
11
+ const indeterminate = io.value === undefined;
12
+ const cRef = useRef<HTMLInputElement>(null);
13
+
14
+ useEffect(() => {
15
+ if (!cRef.current) return;
16
+ cRef.current.indeterminate = indeterminate;
17
+ }, [cRef, indeterminate]);
18
+
19
+ const on_change = (e: React.ChangeEvent<HTMLInputElement>) => {
20
+ let new_value: boolean = e.target.checked;
21
+ try {
22
+ new_value = inputconverter(e.target.checked);
23
+ } catch (e) {}
24
+ fnrf_zst.worker?.set_io_value({
25
+ nid: io.node,
26
+ ioid: io.id,
27
+ value: new_value,
28
+ set_default: io.render_options?.set_default || false,
29
+ });
30
+ };
31
+ return (
32
+ <input
33
+ ref={cRef}
34
+ type="checkbox"
35
+ className="styledcheckbox"
36
+ checked={!!io.value}
37
+ onChange={on_change}
38
+ disabled={io.connected}
39
+ />
40
+ );
41
+ };
42
+ const NumberInput = ({
43
+ io,
44
+ inputconverter,
45
+ parser = (n: string) => parseFloat(n),
46
+ }: InputRendererProps & {
47
+ parser: (n: string) => number;
48
+ }) => {
49
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
50
+ useContext(FuncNodesContext);
51
+
52
+ const [tempvalue, setTempValue] = useState(io.value);
53
+
54
+ useEffect(() => {
55
+ setTempValue(io.value);
56
+ }, [io.value]);
57
+
58
+ const set_new_value = (new_value: number | string) => {
59
+ new_value = parser(
60
+ parseFloat(new_value.toString()).toString() // parse float for e notation
61
+ );
62
+
63
+ if (isNaN(new_value)) {
64
+ new_value = "<NoValue>";
65
+ setTempValue("");
66
+ } else {
67
+ setTempValue(new_value.toString());
68
+ }
69
+ try {
70
+ new_value = inputconverter(new_value);
71
+ } catch (e) {}
72
+
73
+ fnrf_zst.worker?.set_io_value({
74
+ nid: io.node,
75
+ ioid: io.id,
76
+ value: new_value,
77
+ set_default: io.render_options?.set_default || false,
78
+ });
79
+ };
80
+
81
+ const on_change = (e: React.ChangeEvent<HTMLInputElement>) => {
82
+ set_new_value(e.target.value);
83
+ };
84
+ const v = io.connected ? io.value : tempvalue;
85
+ return (
86
+ <input
87
+ type="text"
88
+ className="nodedatainput styledinput"
89
+ value={v || ""}
90
+ onChange={(e) => setTempValue(e.target.value)}
91
+ onBlur={on_change}
92
+ onKeyDown={(e) => {
93
+ // on key up add step to value
94
+
95
+ if (e.key === "ArrowUp") {
96
+ let step = io.render_options?.step || 1;
97
+ if (e.shiftKey) step *= 10;
98
+
99
+ let new_value = (parseFloat(v) || 0) + step;
100
+ // setTempValue(new_value.toString());
101
+ set_new_value(new_value);
102
+ return;
103
+ }
104
+
105
+ // on key down subtract step to value
106
+ if (e.key === "ArrowDown") {
107
+ let step = io.render_options?.step || 1;
108
+ if (e.shiftKey) step *= 10;
109
+ let new_value = (parseFloat(v) || 0) - step;
110
+ // setTempValue(new_value.toString());
111
+ set_new_value(new_value);
112
+ return;
113
+ }
114
+
115
+ //accept only numbers
116
+ if (
117
+ !/^[0-9.eE+-]$/.test(e.key) &&
118
+ !["Backspace", "ArrowLeft", "ArrowRight", "Delete", "Tab"].includes(
119
+ e.key
120
+ )
121
+ ) {
122
+ e.preventDefault();
123
+ }
124
+ }}
125
+ disabled={io.connected}
126
+ step={io.render_options?.step}
127
+ min={io.value_options?.min}
128
+ />
129
+ );
130
+ };
131
+
132
+ const FloatInput = ({ io, inputconverter }: InputRendererProps) => {
133
+ return NumberInput({ io, inputconverter, parser: parseFloat });
134
+ };
135
+
136
+ const IntegerInput = ({ io, inputconverter }: InputRendererProps) => {
137
+ return NumberInput({ io, inputconverter, parser: parseInt });
138
+ };
139
+
140
+ const StringInput = ({ io, inputconverter }: InputRendererProps) => {
141
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
142
+ useContext(FuncNodesContext);
143
+
144
+ const [tempvalue, setTempValue] = useState(io.value);
145
+
146
+ const on_change = (e: React.ChangeEvent<HTMLInputElement>) => {
147
+ let new_value: string = e.target.value;
148
+ try {
149
+ new_value = inputconverter(new_value);
150
+ } catch (e) {}
151
+
152
+ if (!new_value) new_value = "<NoValue>";
153
+
154
+ fnrf_zst.worker?.set_io_value({
155
+ nid: io.node,
156
+ ioid: io.id,
157
+ value: new_value,
158
+ set_default: io.render_options?.set_default || false,
159
+ });
160
+ };
161
+
162
+ return (
163
+ <input
164
+ className="nodedatainput styledinput"
165
+ value={io.connected ? io.value : tempvalue}
166
+ onChange={(e) => setTempValue(e.target.value)}
167
+ onBlur={on_change}
168
+ disabled={io.connected}
169
+ />
170
+ );
171
+ };
172
+
173
+ const _parse_string = (s: string) => s;
174
+ const _parse_number = (s: string) => parseFloat(s);
175
+ const _parse_boolean = (s: string) => !!s;
176
+ const _parse_null = (s: string) => (s === "null" ? null : s);
177
+
178
+ const get_parser = (datatype: string | null) => {
179
+ if (datatype === "nuinputconvertermber") {
180
+ return _parse_number;
181
+ }
182
+ if (datatype === "boolean") {
183
+ return _parse_boolean;
184
+ }
185
+ if (datatype === "undefined") {
186
+ return _parse_null;
187
+ }
188
+ return _parse_string;
189
+ };
190
+
191
+ const SelectionInput = ({
192
+ io,
193
+ inputconverter,
194
+ parser,
195
+ }: InputRendererProps & {
196
+ parser?(s: string): any;
197
+ }) => {
198
+ let options: (string | number)[] | EnumOf = io.value_options?.options || [];
199
+ if (Array.isArray(options)) {
200
+ options = {
201
+ type: "enum",
202
+ values: options,
203
+ keys: options.map((x) => x.toString()),
204
+ nullable: false,
205
+ };
206
+ }
207
+ options = options as EnumOf;
208
+ if (
209
+ options.nullable &&
210
+ !options.values.includes(null) &&
211
+ !options.keys.includes("None")
212
+ ) {
213
+ options.values.unshift(null);
214
+ options.keys.unshift("None");
215
+ }
216
+ //make key value pairs
217
+ const optionsmap: [string, string, string][] = [];
218
+ for (let i = 0; i < options.values.length; i++) {
219
+ // set const t to "string", "number","boolean" "null" depenting on the type of options.values[i]
220
+ const t =
221
+ options.values[i] === null || options.values[i] === undefined
222
+ ? "undefined"
223
+ : typeof options.values[i];
224
+ let v = options.values[i];
225
+
226
+ if (v === null) {
227
+ v = "null";
228
+ }
229
+ if (v === undefined) {
230
+ v = "undefined";
231
+ }
232
+ optionsmap.push([options.keys[i], v.toString(), t]);
233
+ }
234
+
235
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
236
+ useContext(FuncNodesContext);
237
+
238
+ const on_change = (e: React.ChangeEvent<HTMLSelectElement>) => {
239
+ // Find the selected option element
240
+ const selectedOption = e.target.options[e.target.selectedIndex];
241
+ // Retrieve the datatype attribute from the selected option
242
+ const datatype = selectedOption.getAttribute("datatype");
243
+
244
+ // Use the existing parser or get a new one based on the datatype
245
+ const p = parser || get_parser(datatype);
246
+
247
+ let new_value: string | number = p(e.target.value);
248
+ try {
249
+ new_value = inputconverter(e.target.value);
250
+ } catch (e) {}
251
+
252
+ fnrf_zst.worker?.set_io_value({
253
+ nid: io.node,
254
+ ioid: io.id,
255
+ value: p(e.target.value),
256
+ set_default: io.render_options?.set_default || false,
257
+ });
258
+ };
259
+ let v = io.value;
260
+ if (v === null) {
261
+ v = "null";
262
+ }
263
+ if (v === undefined) {
264
+ v = "undefined";
265
+ }
266
+ return (
267
+ <select
268
+ value={v}
269
+ onChange={on_change}
270
+ disabled={io.connected}
271
+ className="nodedatainput styleddropdown"
272
+ >
273
+ <option value="<NoValue>" disabled>
274
+ select
275
+ </option>
276
+ {optionsmap.map((option) => (
277
+ <option key={option[0]} value={option[1]} datatype={option[2]}>
278
+ {option[0]}
279
+ </option>
280
+ ))}
281
+ </select>
282
+ );
283
+ };
284
+
285
+ const ColorInput = ({ io, inputconverter }: InputRendererProps) => {
286
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
287
+ useContext(FuncNodesContext);
288
+
289
+ const colorspace = io.value_options?.colorspace || "hex";
290
+
291
+ const on_change = (colorconverter?: {
292
+ [key: string]: () => number[] | string;
293
+ }) => {
294
+ let new_value: string | number[] = "<NoValue>";
295
+ if (colorconverter) {
296
+ if (colorconverter[colorspace]) new_value = colorconverter[colorspace]();
297
+ else new_value = colorconverter.hex();
298
+ }
299
+ try {
300
+ new_value = new_value;
301
+ } catch (e) {}
302
+
303
+ fnrf_zst.worker?.set_io_value({
304
+ nid: io.node,
305
+ ioid: io.id,
306
+ value: new_value,
307
+ set_default: io.render_options?.set_default || false,
308
+ });
309
+ };
310
+
311
+ return (
312
+ <CustomColorPicker
313
+ onChange={on_change}
314
+ inicolordata={io.value}
315
+ inicolorspace={colorspace}
316
+ />
317
+ );
318
+ };
319
+
320
+ export {
321
+ FloatInput,
322
+ IntegerInput,
323
+ BooleanInput,
324
+ StringInput,
325
+ SelectionInput,
326
+ ColorInput,
327
+ };
@@ -0,0 +1,26 @@
1
+ import { useContext } from "react";
2
+ import { FuncNodesReactFlowZustandInterface } from "../../../state";
3
+ import { FuncNodesContext } from "../../funcnodesreactflow";
4
+ import { SortableTable } from "../../utils/table";
5
+ import JSONDataDisplay from "../../utils/jsondata";
6
+ import { Base64ImageRenderer } from "../../datarenderer/images";
7
+
8
+ const InLineOutput = ({ io }: { io: IOType }) => {
9
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
10
+ useContext(FuncNodesContext);
11
+ let value = io.fullvalue;
12
+ if (value == undefined) value = io.value;
13
+ if (value === undefined) {
14
+ value = "";
15
+ } else {
16
+ value = JSON.stringify(io.value).replace(/\\n/g, "\n"); //respect "\n" in strings
17
+ }
18
+ //truncate the string if it is too long
19
+ if (value.length > 63) {
20
+ value = value.slice(0, 60) + "...";
21
+ }
22
+
23
+ return <div>{value}</div>;
24
+ };
25
+
26
+ export { InLineOutput };
@@ -0,0 +1,89 @@
1
+ import { useContext } from "react";
2
+ import { FuncNodesReactFlowZustandInterface } from "../../../state";
3
+ import { FuncNodesContext } from "../../funcnodesreactflow";
4
+ import { pick_best_io_type } from "./io";
5
+ import { SortableTable } from "../../utils/table";
6
+ import JSONDataDisplay from "../../utils/jsondata";
7
+ import { Base64ImageRenderer } from "../../datarenderer/images";
8
+
9
+ const TableOutput = ({ io }: { io: IOType }) => {
10
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
11
+ useContext(FuncNodesContext);
12
+
13
+ let value = io.fullvalue;
14
+ if (value == undefined) value = io.value;
15
+ if (value === undefined) {
16
+ value = [];
17
+ }
18
+
19
+ return <SortableTable tabledata={value} />;
20
+ };
21
+
22
+ const DictOutput = ({ io }: { io: IOType }) => {
23
+ let value = io.fullvalue;
24
+
25
+ if (value === undefined) value = io.value;
26
+ if (value === undefined) {
27
+ value = {};
28
+ }
29
+
30
+ return <JSONDataDisplay data={value} />;
31
+ };
32
+
33
+ const Base64ImageOutput = ({ io }: { io: IOType }) => {
34
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
35
+ useContext(FuncNodesContext);
36
+
37
+ let value = io.fullvalue;
38
+ if (value == undefined) value = io.value;
39
+ if (value === undefined) {
40
+ value = "";
41
+ }
42
+
43
+ return <Base64ImageRenderer value={value} />;
44
+ };
45
+
46
+ const SingleValueOutput = ({ io }: { io: IOType }) => {
47
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
48
+ useContext(FuncNodesContext);
49
+
50
+ let value = io.fullvalue;
51
+ if (value == undefined) value = io.value;
52
+ if (value === undefined) {
53
+ value = "";
54
+ } else {
55
+ value = JSON.stringify(io.value).replace(/\\n/g, "\n"); //respect "\n" in strings
56
+ }
57
+
58
+ return (
59
+ <div>
60
+ <pre>{value}</pre>
61
+ </div>
62
+ );
63
+ };
64
+
65
+ const HandlePreviouGenerators: {
66
+ [key: string]: ({ io }: { io: IOType }) => JSX.Element;
67
+ } = {
68
+ string: SingleValueOutput,
69
+ table: TableOutput,
70
+ image: Base64ImageOutput,
71
+ dict: DictOutput,
72
+ };
73
+
74
+ const PreviewHandleDataRendererForIo = (io: IOType) => {
75
+ const fnrf_zst: FuncNodesReactFlowZustandInterface =
76
+ useContext(FuncNodesContext);
77
+ const render: RenderOptions = fnrf_zst.render_options();
78
+
79
+ const [typestring, otypestring] = pick_best_io_type(
80
+ io.type,
81
+ render.typemap || {}
82
+ );
83
+
84
+ return typestring && HandlePreviouGenerators[typestring]
85
+ ? HandlePreviouGenerators[typestring]
86
+ : DictOutput;
87
+ };
88
+
89
+ export { PreviewHandleDataRendererForIo };
@@ -0,0 +1,4 @@
1
+ import NodeInput from "./nodeinput";
2
+ import NodeOutput from "./nodeoutput";
3
+
4
+ export { NodeInput, NodeOutput };
@@ -0,0 +1,91 @@
1
+ s slideUpAndFade {
2
+ from {
3
+ opacity: 0;
4
+ transform: translateY(2px);
5
+ }
6
+ to {
7
+ opacity: 1;
8
+ transform: translateY(0);
9
+ }
10
+ }
11
+
12
+ @keyframes slideRightAndFade {
13
+ from {
14
+ opacity: 0;
15
+ transform: translateX(-2px);
16
+ }
17
+ to {
18
+ opacity: 1;
19
+ transform: translateX(0);
20
+ }
21
+ }
22
+
23
+ @keyframes slideDownAndFade {
24
+ from {
25
+ opacity: 0;
26
+ transform: translateY(-2px);
27
+ }
28
+ to {
29
+ opacity: 1;
30
+ transform: translateY(0);
31
+ }
32
+ }
33
+
34
+ @keyframes slideLeftAndFade {
35
+ from {
36
+ opacity: 0;
37
+ transform: translateX(2px);
38
+ }
39
+ to {
40
+ opacity: 1;
41
+ transform: translateX(0);
42
+ }
43
+ }
44
+
45
+ .iotooltipcontent {
46
+ background-color: #f9f9f9;
47
+ border: 1px solid #ffffff;
48
+ border-radius: 5px;
49
+ padding: 10px;
50
+ box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
51
+ hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
52
+ font-size: 10px;
53
+ color: #333;
54
+ max-width: 30vw;
55
+ max-height: 30vh;
56
+ cursor: default;
57
+
58
+ &.fullsize {
59
+ max-width: 100vw;
60
+ max-height: 100vh;
61
+ position: fixed;
62
+ top: 0;
63
+ }
64
+
65
+ overflow: auto;
66
+ display: flex;
67
+ flex-direction: column;
68
+ &[data-state="delayed-open"][data-side="top"] {
69
+ animation-name: slideDownAndFade;
70
+ }
71
+ &[data-state="delayed-open"][data-side="right"] {
72
+ animation-name: slideLeftAndFade;
73
+ }
74
+ &[data-state="delayed-open"][data-side="bottom"] {
75
+ animation-name: slideUpAndFade;
76
+ }
77
+ &[data-state="delayed-open"][data-side="left"] {
78
+ animation-name: slideRightAndFade;
79
+ }
80
+ }
81
+
82
+ .iotooltip_container {
83
+ display: flex;
84
+ flex-direction: column;
85
+ max-width: inherit;
86
+ max-height: inherit;
87
+ }
88
+
89
+ .iotooltipcontentarrow {
90
+ fill: white;
91
+ }
@@ -0,0 +1,114 @@
1
+ import * as Tooltip from "@radix-ui/react-tooltip";
2
+ import LockOpenIcon from "@mui/icons-material/LockOpen";
3
+ import LockIcon from "@mui/icons-material/Lock";
4
+ import FullscreenIcon from "@mui/icons-material/Fullscreen";
5
+
6
+ import "./io.scss";
7
+ import { Handle, HandleProps } from "reactflow";
8
+ import { useMemo, useState } from "react";
9
+ import CustomDialog from "../../dialog";
10
+ import { PreviewHandleDataRendererForIo } from "./handle_renderer";
11
+ const pick_best_io_type = (
12
+ iot: SerializedType,
13
+ typemap: { [key: string]: string }
14
+ ): [string | undefined, string | undefined] => {
15
+ // check if iot is string
16
+ if (typeof iot === "string") {
17
+ if (iot in typemap) {
18
+ return [typemap[iot], iot];
19
+ }
20
+ return [iot, iot];
21
+ }
22
+ if ("allOf" in iot && iot.allOf !== undefined) {
23
+ return [undefined, undefined];
24
+ }
25
+ if ("anyOf" in iot && iot.anyOf !== undefined) {
26
+ const picks = iot.anyOf.map((x) => pick_best_io_type(x, typemap));
27
+ for (const pick of picks) {
28
+ switch (pick[0]) {
29
+ case "bool":
30
+ return ["bool", pick[1]];
31
+ case "enum":
32
+ return ["enum", pick[1]];
33
+ case "float":
34
+ return ["float", pick[1]];
35
+ case "int":
36
+ return ["int", pick[1]];
37
+ case "string":
38
+ return ["string", pick[1]];
39
+ case "str":
40
+ return ["string", pick[1]];
41
+ }
42
+ }
43
+
44
+ return [undefined, undefined];
45
+ }
46
+ if (!("type" in iot) || iot.type === undefined) {
47
+ return [undefined, undefined];
48
+ }
49
+
50
+ if (iot.type === "enum") {
51
+ return ["enum", "enum"];
52
+ }
53
+ return [undefined, undefined];
54
+ };
55
+
56
+ type HandleWithPreviewProps = {
57
+ io: IOType;
58
+ typestring: string | undefined;
59
+ preview?: React.FC<{ io: IOType }>;
60
+ } & HandleProps;
61
+
62
+ const HandleWithPreview = ({
63
+ io,
64
+ typestring,
65
+ preview,
66
+ ...props
67
+ }: HandleWithPreviewProps) => {
68
+ const [locked, setLocked] = useState(false);
69
+ const [opened, setOpened] = useState(false);
70
+
71
+ const pvhandle: React.FC<{ io: IOType }> =
72
+ preview || PreviewHandleDataRendererForIo(io);
73
+
74
+ return (
75
+ <Tooltip.Provider>
76
+ <Tooltip.Root open={locked || opened} onOpenChange={setOpened}>
77
+ <Tooltip.Trigger asChild>
78
+ <Handle id={io.id} {...props} />
79
+ </Tooltip.Trigger>
80
+ <Tooltip.Portal>
81
+ <Tooltip.Content className={"iotooltipcontent"} sideOffset={5}>
82
+ <div className="iotooltip_container">
83
+ <div className="iotooltip_header">
84
+ {locked ? (
85
+ <LockIcon onClick={() => setLocked(false)} />
86
+ ) : (
87
+ <LockOpenIcon onClick={() => setLocked(true)} />
88
+ )}
89
+ {pvhandle && (
90
+ <CustomDialog
91
+ trigger={<FullscreenIcon />}
92
+ onOpenChange={(open: boolean) => {
93
+ if (open) {
94
+ io.try_get_full_value();
95
+ }
96
+ setLocked(open);
97
+ }}
98
+ >
99
+ {pvhandle({ io })}
100
+ </CustomDialog>
101
+ )}
102
+ </div>
103
+ {pvhandle
104
+ ? pvhandle({ io })
105
+ : `no preview available for "${typestring}"`}
106
+ </div>
107
+ <Tooltip.Arrow className="iotooltipcontentarrow" />
108
+ </Tooltip.Content>
109
+ </Tooltip.Portal>
110
+ </Tooltip.Root>
111
+ </Tooltip.Provider>
112
+ );
113
+ };
114
+ export { pick_best_io_type, HandleWithPreview };