html-overlay-node 0.1.9 โ 0.1.11
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/dist/example.json +9 -9
- package/dist/html-overlay-node.es.js +1000 -321
- package/dist/html-overlay-node.es.js.map +1 -1
- package/dist/html-overlay-node.umd.js +1 -1
- package/dist/html-overlay-node.umd.js.map +1 -1
- package/dist/img/favicon.svg +1 -0
- package/index.css +436 -232
- package/package.json +1 -1
- package/readme.md +143 -440
- package/src/core/Graph.js +34 -5
- package/src/core/Runner.js +188 -54
- package/src/index.js +29 -26
- package/src/interact/Controller.js +35 -6
- package/src/nodes/core.js +55 -77
- package/src/nodes/logic.js +51 -48
- package/src/nodes/math.js +23 -8
- package/src/nodes/util.js +238 -131
- package/src/nodes/value.js +87 -102
- package/src/render/CanvasRenderer.js +465 -285
- package/src/render/HtmlOverlay.js +65 -3
- package/src/render/hitTest.js +5 -2
- package/src/ui/HelpOverlay.js +158 -0
- package/src/ui/PropertyPanel.css +58 -27
- package/src/ui/PropertyPanel.js +441 -268
package/readme.md
CHANGED
|
@@ -1,440 +1,143 @@
|
|
|
1
|
-
# HTML-overlay-Node
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/html-overlay-node)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
|
|
6
|
-
**HTML-overlay-Node
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## โจ Features
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Listen to group events
|
|
145
|
-
hooks.on("group:change", () => {
|
|
146
|
-
console.log("Group structure changed");
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## ๐ HTML Overlays
|
|
153
|
-
|
|
154
|
-
Create interactive HTML UIs inside nodes.
|
|
155
|
-
|
|
156
|
-
### Basic Example
|
|
157
|
-
|
|
158
|
-
```javascript
|
|
159
|
-
registry.register("ui/TextInput", {
|
|
160
|
-
title: "Text Input",
|
|
161
|
-
size: { w: 220, h: 100 },
|
|
162
|
-
outputs: [{ name: "text", datatype: "string" }],
|
|
163
|
-
|
|
164
|
-
html: {
|
|
165
|
-
init(node, el, { header, body }) {
|
|
166
|
-
el.style.backgroundColor = "#1a1a1a";
|
|
167
|
-
el.style.borderRadius = "8px";
|
|
168
|
-
|
|
169
|
-
const input = document.createElement("input");
|
|
170
|
-
input.type = "text";
|
|
171
|
-
input.placeholder = "Enter text...";
|
|
172
|
-
Object.assign(input.style, {
|
|
173
|
-
width: "100%",
|
|
174
|
-
padding: "8px",
|
|
175
|
-
background: "#111",
|
|
176
|
-
border: "1px solid #444",
|
|
177
|
-
color: "#fff",
|
|
178
|
-
pointerEvents: "auto", // IMPORTANT: Enable interaction
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
input.addEventListener("input", (e) => {
|
|
182
|
-
node.state.text = e.target.value;
|
|
183
|
-
hooks.emit("node:updated", node);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
input.addEventListener("mousedown", (e) => e.stopPropagation()); // Prevent drag
|
|
187
|
-
|
|
188
|
-
body.appendChild(input);
|
|
189
|
-
el._input = input;
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
update(node, el, { selected }) {
|
|
193
|
-
el.style.borderColor = selected ? "#3b82f6" : "#333";
|
|
194
|
-
if (el._input.value !== (node.state.text || "")) {
|
|
195
|
-
el._input.value = node.state.text || "";
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
onCreate(node) {
|
|
201
|
-
node.state.text = "";
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
onExecute(node, { setOutput }) {
|
|
205
|
-
setOutput("text", node.state.text || "");
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Best Practices
|
|
211
|
-
|
|
212
|
-
1. **Enable Interaction**: Set `pointerEvents: "auto"` on interactive elements
|
|
213
|
-
2. **Stop Propagation**: Prevent canvas drag with `e.stopPropagation()` on `mousedown`
|
|
214
|
-
3. **Update State**: Emit `"node:updated"` when state changes
|
|
215
|
-
4. **Store References**: Cache DOM elements in `el._refs` for performance
|
|
216
|
-
5. **Port Visibility**: HTML overlays are rendered below ports for proper visibility
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## ๐ Port Types
|
|
221
|
-
|
|
222
|
-
HTML-overlay-Node supports two types of ports for different purposes:
|
|
223
|
-
|
|
224
|
-
### Exec Ports (Flow Control)
|
|
225
|
-
|
|
226
|
-
Exec ports control the execution flow between nodes.
|
|
227
|
-
|
|
228
|
-
```javascript
|
|
229
|
-
registry.register("util/Print", {
|
|
230
|
-
title: "Print",
|
|
231
|
-
inputs: [
|
|
232
|
-
{ name: "exec", portType: "exec" }, // Execution input
|
|
233
|
-
{ name: "value", portType: "data", datatype: "any" },
|
|
234
|
-
],
|
|
235
|
-
outputs: [
|
|
236
|
-
{ name: "exec", portType: "exec" }, // Execution output
|
|
237
|
-
],
|
|
238
|
-
onExecute(node, { getInput }) {
|
|
239
|
-
console.log("[Print]", getInput("value"));
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Data Ports (Values)
|
|
245
|
-
|
|
246
|
-
Data ports transfer values between nodes.
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
registry.register("math/Add", {
|
|
250
|
-
title: "Add",
|
|
251
|
-
inputs: [
|
|
252
|
-
{ name: "exec", portType: "exec" },
|
|
253
|
-
{ name: "a", portType: "data", datatype: "number" },
|
|
254
|
-
{ name: "b", portType: "data", datatype: "number" },
|
|
255
|
-
],
|
|
256
|
-
outputs: [
|
|
257
|
-
{ name: "exec", portType: "exec" },
|
|
258
|
-
{ name: "result", portType: "data", datatype: "number" },
|
|
259
|
-
],
|
|
260
|
-
onExecute(node, { getInput, setOutput }) {
|
|
261
|
-
const result = (getInput("a") ?? 0) + (getInput("b") ?? 0);
|
|
262
|
-
setOutput("result", result);
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
### Visual Style
|
|
268
|
-
|
|
269
|
-
- **Exec ports**: Emerald green rounded squares (8ร8px)
|
|
270
|
-
- **Data ports**: Indigo blue circles (10px diameter)
|
|
271
|
-
- Both have subtle outlines for depth
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## โจ๏ธ Keyboard Shortcuts
|
|
276
|
-
|
|
277
|
-
| Shortcut | Action |
|
|
278
|
-
| ------------------------------- | --------------------------- |
|
|
279
|
-
| **Selection** | |
|
|
280
|
-
| `Click` | Select node |
|
|
281
|
-
| `Shift + Click` | Add to selection |
|
|
282
|
-
| `Ctrl + Drag` | Box select |
|
|
283
|
-
| **Editing** | |
|
|
284
|
-
| `Delete` | Delete selected nodes |
|
|
285
|
-
| `Ctrl + Z` | Undo |
|
|
286
|
-
| `Ctrl + Y` / `Ctrl + Shift + Z` | Redo |
|
|
287
|
-
| **Grouping** | |
|
|
288
|
-
| `Ctrl + G` | Create group from selection |
|
|
289
|
-
| **Alignment** | |
|
|
290
|
-
| `A` | Align nodes horizontally |
|
|
291
|
-
| `Shift + A` | Align nodes vertically |
|
|
292
|
-
| **Tools** | |
|
|
293
|
-
| `G` | Toggle snap-to-grid |
|
|
294
|
-
| `?` | Toggle shortcuts help |
|
|
295
|
-
| **Navigation** | |
|
|
296
|
-
| `Middle Click + Drag` | Pan canvas |
|
|
297
|
-
| `Mouse Wheel` | Zoom in/out |
|
|
298
|
-
| `Right Click` | Context menu |
|
|
299
|
-
|
|
300
|
-
---
|
|
301
|
-
|
|
302
|
-
## ๐ Complete API
|
|
303
|
-
|
|
304
|
-
For full API documentation, see the comments in [src/index.js](src/index.js).
|
|
305
|
-
|
|
306
|
-
### Editor API
|
|
307
|
-
|
|
308
|
-
| Property | Description |
|
|
309
|
-
| ------------------- | --------------------- |
|
|
310
|
-
| `graph` | Graph instance |
|
|
311
|
-
| `registry` | Node type registry |
|
|
312
|
-
| `hooks` | Event system |
|
|
313
|
-
| `render()` | Trigger manual render |
|
|
314
|
-
| `start()` | Start execution loop |
|
|
315
|
-
| `stop()` | Stop execution loop |
|
|
316
|
-
| `addGroup(options)` | Create a group |
|
|
317
|
-
| `destroy()` | Cleanup |
|
|
318
|
-
|
|
319
|
-
### Key Methods
|
|
320
|
-
|
|
321
|
-
- `registry.register(type, definition)` - Register node type
|
|
322
|
-
- `graph.addNode(type, options)` - Create node
|
|
323
|
-
- `graph.addEdge(from, fromPort, to, toPort)` - Connect nodes
|
|
324
|
-
- `graph.toJSON()` / `graph.fromJSON(json)` - Serialize/deserialize
|
|
325
|
-
- `hooks.on(event, callback)` - Subscribe to events
|
|
326
|
-
|
|
327
|
-
### Available Events
|
|
328
|
-
|
|
329
|
-
- `node:create` | `node:move` | `node:resize` | `node:updated`
|
|
330
|
-
- `edge:create` | `edge:delete`
|
|
331
|
-
- `group:change`
|
|
332
|
-
- `runner:start` | `runner:stop` | `runner:tick`
|
|
333
|
-
- `error`
|
|
334
|
-
|
|
335
|
-
---
|
|
336
|
-
|
|
337
|
-
## ๐จ Customization
|
|
338
|
-
|
|
339
|
-
### Theme Colors
|
|
340
|
-
|
|
341
|
-
```javascript
|
|
342
|
-
const editor = createGraphEditor(canvas, {
|
|
343
|
-
theme: {
|
|
344
|
-
bg: "#0d0d0f", // Canvas background
|
|
345
|
-
grid: "#1a1a1d", // Grid lines
|
|
346
|
-
node: "#16161a", // Node background
|
|
347
|
-
nodeBorder: "#2a2a2f", // Node border
|
|
348
|
-
title: "#1f1f24", // Node header
|
|
349
|
-
text: "#e4e4e7", // Primary text
|
|
350
|
-
textMuted: "#a1a1aa", // Secondary text
|
|
351
|
-
port: "#6366f1", // Data port color (indigo)
|
|
352
|
-
portExec: "#10b981", // Exec port color (emerald)
|
|
353
|
-
edge: "#52525b", // Edge color
|
|
354
|
-
edgeActive: "#8b5cf6", // Active edge (purple)
|
|
355
|
-
accent: "#6366f1", // Accent color
|
|
356
|
-
accentBright: "#818cf8", // Bright accent
|
|
357
|
-
},
|
|
358
|
-
});
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### Edge Styles
|
|
362
|
-
|
|
363
|
-
```javascript
|
|
364
|
-
// Set edge style
|
|
365
|
-
editor.renderer.setEdgeStyle("orthogonal"); // or "curved", "line"
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
### Custom Node Drawing
|
|
369
|
-
|
|
370
|
-
```javascript
|
|
371
|
-
registry.register("visual/Circle", {
|
|
372
|
-
title: "Circle",
|
|
373
|
-
size: { w: 120, h: 120 },
|
|
374
|
-
onDraw(node, { ctx, theme }) {
|
|
375
|
-
const { x, y, width, height } = node.computed;
|
|
376
|
-
const centerX = x + width / 2;
|
|
377
|
-
const centerY = y + height / 2;
|
|
378
|
-
const radius = Math.min(width, height) / 3;
|
|
379
|
-
|
|
380
|
-
ctx.beginPath();
|
|
381
|
-
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
382
|
-
ctx.fillStyle = theme.wire;
|
|
383
|
-
ctx.fill();
|
|
384
|
-
},
|
|
385
|
-
});
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
---
|
|
389
|
-
|
|
390
|
-
## ๐พ Serialization
|
|
391
|
-
|
|
392
|
-
```javascript
|
|
393
|
-
// Save
|
|
394
|
-
const json = graph.toJSON();
|
|
395
|
-
localStorage.setItem("myGraph", JSON.stringify(json));
|
|
396
|
-
|
|
397
|
-
// Load
|
|
398
|
-
const saved = JSON.parse(localStorage.getItem("myGraph"));
|
|
399
|
-
graph.fromJSON(saved);
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
## ๐ Troubleshooting
|
|
405
|
-
|
|
406
|
-
| Issue | Solution |
|
|
407
|
-
| ---------------------------- | --------------------------------------------------- |
|
|
408
|
-
| Canvas not rendering | Ensure canvas has explicit width/height |
|
|
409
|
-
| Nodes not executing | Call `start()` or set `autorun: true` |
|
|
410
|
-
| Type errors | Register node types before using them |
|
|
411
|
-
| HTML overlay not interactive | Set `pointerEvents: "auto"` on elements |
|
|
412
|
-
| Performance issues | Limit to <1000 nodes, optimize `onExecute`/`onDraw` |
|
|
413
|
-
|
|
414
|
-
---
|
|
415
|
-
|
|
416
|
-
## ๐ค Contributing
|
|
417
|
-
|
|
418
|
-
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
419
|
-
|
|
420
|
-
```bash
|
|
421
|
-
npm install # Install dependencies
|
|
422
|
-
npm run dev # Start dev server
|
|
423
|
-
npm test # Run tests
|
|
424
|
-
npm run lint # Check code quality
|
|
425
|
-
npm run build # Build library
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
---
|
|
429
|
-
|
|
430
|
-
## ๐ License
|
|
431
|
-
|
|
432
|
-
[MIT](LICENSE) ยฉ cheonghakim
|
|
433
|
-
|
|
434
|
-
---
|
|
435
|
-
|
|
436
|
-
## ๐ Links
|
|
437
|
-
|
|
438
|
-
- [GitHub Repository](https://github.com/cheonghakim/html-overlay-node)
|
|
439
|
-
- [Issue Tracker](https://github.com/cheonghakim/html-overlay-node/issues)
|
|
440
|
-
- [Changelog](CHANGELOG.md)
|
|
1
|
+
# HTML-overlay-Node
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/html-overlay-node)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**HTML-overlay-Node**๋ Canvas์ ๊ณ ์ฑ๋ฅ ๋ ๋๋ง๊ณผ HTML์ ์ ์ฐํ UI ์ธํฐํ์ด์ค๋ฅผ ๊ฒฐํฉํ ์ ๋ฌธ๊ฐ์ฉ ๋
ธ๋ ์๋ํฐ ์์ง์
๋๋ค. ๋จ์ํ ์๊ฐํ๋ฅผ ๋์ด, ๋ณต์กํ ๋ก์ง ์ค๊ณ์ ์ค์๊ฐ ์คํ ํ๊ฒฝ์ ์ต์ ํ๋ ๋ ์นด๋กญ๊ณ ์ธ๋ จ๋ ์์ง๋์ด๋ง ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## โจ ํต์ฌ ๊ธฐ๋ฅ (Key Features)
|
|
11
|
+
|
|
12
|
+
- **ํ์ด๋ธ๋ฆฌ๋ ๋ ๋๋ง**: ๋
ธ๋ ๋ณธ์ฒด์ ๋ณต์กํ ์์ ฏ์ HTML๋ก, ์ฐ๊ฒฐ์ (Edge)๊ณผ ๊ณ ์ฃผํ ์ ๋๋ฉ์ด์
์ Canvas๋ก ์ฒ๋ฆฌํ์ฌ ์ฑ๋ฅ๊ณผ ์ปค์คํฐ๋ง์ด์ง์ ๊ท ํ์ ์ก์์ต๋๋ค.
|
|
13
|
+
- **์ค์๊ฐ ์คํ ํ๋ฆ ์๊ฐํ**: ๋
ธ๋ ์คํ ์ํ์ ๋ฐ์ดํฐ ํ๋ฆ์ 'Marching Ants' ํ
๋๋ฆฌ ๋ฐ ํ๋ฅด๋ ์ ์ ๋๋ฉ์ด์
์ผ๋ก ๊ฐ์ํํ์ฌ ๋ก์ง์ ํ๋ฆ์ ์ง๊ด์ ์ผ๋ก ํ์
ํ ์ ์์ต๋๋ค.
|
|
14
|
+
- **์ ๋ฌธ๊ฐ๊ธ ๋์์ธ ์์คํ
**: 2px์ ์ ๋ฐํ ๋ผ์ด๋ฉ, ๊น์ด๊ฐ ์๋ ๋คํฌ ํ
๋ง, ์นดํ
๊ณ ๋ฆฌ๋ณ ์ปฌ๋ฌ ์ฝ๋ฉ์ผ๋ก ๋๊ท๋ชจ ๊ทธ๋ํ์์๋ ๋์ ๊ฐ๋
์ฑ์ ์ ์งํฉ๋๋ค.
|
|
15
|
+
- **๊ฐ๋ ฅํ ๋์ผ ํฌํธ ์์คํ
**: ์คํ ์ ์ด(Execution Flow)์ ๋ฐ์ดํฐ ์ ์ฉ(Data Flow) ํฌํธ๋ฅผ ๋ถ๋ฆฌ ์ง์ํ์ฌ ํจ์ํ/์ด๋ฒคํธ ๊ธฐ๋ฐ ๋ก์ง ์ค๊ณ๋ฅผ ์๋ฒฝํ ์ง์ํฉ๋๋ค.
|
|
16
|
+
- **์์ฐ์ฑ ๋๊ตฌ**: Undo/Redo, ๊ทธ๋ฃนํ(Group), ์๋ ์ ๋ ฌ(Aligning), ๊ทธ๋ฆฌ๋ ์ค๋
, ๋ค์ค ์ ํ ๋ฑ ์ค์ ์์
ํจ์จ์ ๊ทน๋ํํ๋ ํ์ ์ ์ ๊ธฐ๋ฅ์ ๋ด์ฅํ๊ณ ์์ต๋๋ค.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ๐ ๋น ๋ฅธ ์์ (Quick Start)
|
|
21
|
+
|
|
22
|
+
Vite๋ Webpack ํ๊ฒฝ์์ ๋ค์๊ณผ ๊ฐ์ด ์์ฝ๊ฒ ์๋ํฐ๋ฅผ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import { createGraphEditor } from "html-overlay-node";
|
|
26
|
+
import "html-overlay-node/index.css";
|
|
27
|
+
|
|
28
|
+
const editor = createGraphEditor("#editor-container", {
|
|
29
|
+
theme: {
|
|
30
|
+
accent: "#6366f1", // ์ฃผ์ ๊ฐ์กฐ ์์ (Indigo)
|
|
31
|
+
flowSpeed: 150 // ์ ๋๋ฉ์ด์
ํ๋ฆ ์๋ (px/sec)
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const { graph, registry, start } = editor;
|
|
36
|
+
|
|
37
|
+
// ๋
ธ๋ ๋ฑ๋ก ๋ฐ ์์
|
|
38
|
+
registry.register("math/Add", { ... });
|
|
39
|
+
start();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ๐งฉ ํ๋ฌ๊ทธ์ธ ์์คํ
(Plugins & Node Registration)
|
|
45
|
+
|
|
46
|
+
`registry`๋ฅผ ํตํด ์๋ก์ด ๊ธฐ๋ฅ์ ๋์ ์ผ๋ก ํ์ฅํ ์ ์์ต๋๋ค.
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
registry.register("math/Multiply", {
|
|
50
|
+
title: "Multiply",
|
|
51
|
+
color: "#f43f5e", // ์นดํ
๊ณ ๋ฆฌ ์ปฌ๋ฌ
|
|
52
|
+
inputs: [
|
|
53
|
+
{ name: "a", portType: "data" },
|
|
54
|
+
{ name: "b", portType: "data" }
|
|
55
|
+
],
|
|
56
|
+
outputs: [{ name: "result", portType: "data" }],
|
|
57
|
+
// ์คํ ๋ก์ง ์ ์
|
|
58
|
+
onExecute(node, { getInput, setOutput }) {
|
|
59
|
+
const a = getInput("a") ?? 1;
|
|
60
|
+
const b = getInput("b") ?? 1;
|
|
61
|
+
setOutput("result", a * b);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## ๐ผ๏ธ UI & ์์ ฏ (HTML Overlays & Widgets)
|
|
69
|
+
|
|
70
|
+
HTML์ ๊ฐ์ ์ ์ด๋ ค ๋
ธ๋ ๋ด๋ถ์ ๋ณต์กํ UI๋ฅผ ์ง์ ๊ตฌํํ ์ ์์ต๋๋ค. `renderHtml` ํจ์๋ฅผ ํตํด DOM ์์๋ฅผ ์ง์ ์ ์ดํ๊ฑฐ๋ ๋ด์ฅ ์์ ฏ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
registry.register("ui/Slider", {
|
|
74
|
+
html: true, // HTML ์ค๋ฒ๋ ์ด ํ์ฑํ
|
|
75
|
+
renderHtml(node, container) {
|
|
76
|
+
container.innerHTML = `<input type="range" class="hon-slider" />`;
|
|
77
|
+
const input = container.querySelector("input");
|
|
78
|
+
input.oninput = (e) => {
|
|
79
|
+
node.state.value = e.target.value;
|
|
80
|
+
// ์ํ ๋ณ๊ฒฝ ์ ๊ทธ๋ํ ๊ฐฑ์ ์๋ฆผ
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ๐จ CSS ๋์์ธ ํ ํฐ (Design Tokens)
|
|
89
|
+
|
|
90
|
+
`index.css`์ ์ ์๋ CSS ๋ณ์๋ฅผ ๋ฎ์ด์ฐ๋ ๊ฒ๋ง์ผ๋ก๋ ์ ์ฒด ์๋ํฐ์ ๋ฃฉ์คํ์ ๋ธ๋๋์ ๋ง๊ฒ ์กฐ์ ํ ์ ์์ต๋๋ค.
|
|
91
|
+
|
|
92
|
+
| ๋ณ์๋ช
| ์ค๋ช
| ๊ธฐ๋ณธ๊ฐ |
|
|
93
|
+
| :--- | :--- | :--- |
|
|
94
|
+
| `--hon-bg` | ์บ๋ฒ์ค ๋ฐฐ๊ฒฝ์ | `#0d0d0f` |
|
|
95
|
+
| `--hon-node-bg` | ๋
ธ๋ ๋ด๋ถ ๋ฐฐ๊ฒฝ์ | `#16161a` |
|
|
96
|
+
| `--hon-node-border` | ๋
ธ๋ ํ
๋๋ฆฌ ์์ | `rgba(255,255,255,0.08)` |
|
|
97
|
+
| `--hon-accent` | ๊ฐ์กฐ ํฌ์ธํธ ์ปฌ๋ฌ | `#4f46e5` |
|
|
98
|
+
| `--hon-text` | ๊ธฐ๋ณธ ํ
์คํธ ์์ | `#e2e8f0` |
|
|
99
|
+
| `--hon-grid` | ๊ทธ๋ฆฌ๋ ์ ์์ | `rgba(255,255,255,0.03)` |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## โจ๏ธ ์์ฐ์ฑ ๋จ์ถํค
|
|
104
|
+
|
|
105
|
+
| ๊ธฐ๋ฅ | ๋จ์ถํค |
|
|
106
|
+
| :--- | :--- |
|
|
107
|
+
| **๋
ธ๋ ์ญ์ ** | `Delete` |
|
|
108
|
+
| **์ํ/์์ง ์ ๋ ฌ** | `A` / `Shift + A` |
|
|
109
|
+
| **๊ทธ๋ฃน ์์ฑ** | `Ctrl + G` |
|
|
110
|
+
| **๊ทธ๋ฆฌ๋ ์ค๋
ํ ๊ธ**| `G` |
|
|
111
|
+
| **์คํ ์ทจ์/์ฌ์คํ** | `Ctrl + Z` / `Ctrl + Y` |
|
|
112
|
+
| **๋ฏธ๋๋งต ํ ๊ธ** | `M` |
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## ๐พ ๋ฐ์ดํฐ ์ง๋ ฌํ (Serialization)
|
|
117
|
+
|
|
118
|
+
๊ทธ๋ํ์ ๋ชจ๋ ์ํ๋ JSON ํ์์ผ๋ก ์ ์ฅํ๊ณ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
// ํ์ฌ ์บ๋ฒ์ค ์ํ ์ ์ฅ
|
|
122
|
+
const snapshot = graph.toJSON();
|
|
123
|
+
|
|
124
|
+
// ๋ฐ์ดํฐ ๋ณต์ (ํธ๋์ง์
์ ๋๋ฉ์ด์
ํฌํจ)
|
|
125
|
+
graph.fromJSON(snapshot);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## ๐ ๏ธ ๊ฐ๋ฐ ๊ฐ์ด๋ (Development)
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm install # ์์กด์ฑ ์ค์น
|
|
134
|
+
npm run dev # ๊ฐ๋ฐ ์๋ฒ ์์
|
|
135
|
+
npm test # ํ
์คํธ ์คํ
|
|
136
|
+
npm run build # ํ๋ก๋์
๋น๋
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## ๐ ๋ผ์ด์ ์ค
|
|
142
|
+
|
|
143
|
+
[MIT](LICENSE) ยฉ cheonghakim
|
package/src/core/Graph.js
CHANGED
|
@@ -51,11 +51,13 @@ export class Graph {
|
|
|
51
51
|
const available = Array.from(this.registry.types.keys()).join(", ") || "none";
|
|
52
52
|
throw new Error(`Unknown node type: "${type}". Available types: ${available}`);
|
|
53
53
|
}
|
|
54
|
+
const height = opts.height || def.size?.h || this._calculateDefaultNodeHeight(def);
|
|
55
|
+
|
|
54
56
|
const node = new Node({
|
|
55
57
|
type,
|
|
56
58
|
title: def.title,
|
|
57
|
-
width: def.size?.w,
|
|
58
|
-
height
|
|
59
|
+
width: opts.width || def.size?.w || 140,
|
|
60
|
+
height,
|
|
59
61
|
...opts,
|
|
60
62
|
});
|
|
61
63
|
for (const i of def.inputs || []) node.addInput(i.name, i.datatype, i.portType || "data");
|
|
@@ -212,11 +214,17 @@ export class Graph {
|
|
|
212
214
|
this.hooks?.emit("graph:serialize", json);
|
|
213
215
|
return json;
|
|
214
216
|
}
|
|
217
|
+
|
|
215
218
|
fromJSON(json) {
|
|
216
|
-
this.clear();
|
|
219
|
+
this.nodes.clear();
|
|
220
|
+
this.edges.clear();
|
|
217
221
|
|
|
218
222
|
// Restore nodes first
|
|
219
223
|
for (const nd of json.nodes) {
|
|
224
|
+
const def = this.registry?.types?.get(nd.type);
|
|
225
|
+
const minH = def ? this._calculateDefaultNodeHeight(def) : 60;
|
|
226
|
+
const height = nd.h !== undefined ? nd.h : minH;
|
|
227
|
+
|
|
220
228
|
const node = new Node({
|
|
221
229
|
id: nd.id,
|
|
222
230
|
type: nd.type,
|
|
@@ -224,10 +232,10 @@ export class Graph {
|
|
|
224
232
|
x: nd.x,
|
|
225
233
|
y: nd.y,
|
|
226
234
|
width: nd.w,
|
|
227
|
-
height:
|
|
235
|
+
height: height,
|
|
228
236
|
});
|
|
237
|
+
|
|
229
238
|
// Call onCreate to initialize node with defaults first
|
|
230
|
-
const def = this.registry?.types?.get(nd.type);
|
|
231
239
|
if (def?.onCreate) {
|
|
232
240
|
def.onCreate(node);
|
|
233
241
|
}
|
|
@@ -264,4 +272,25 @@ export class Graph {
|
|
|
264
272
|
|
|
265
273
|
return this;
|
|
266
274
|
}
|
|
275
|
+
|
|
276
|
+
_calculateDefaultNodeHeight(def) {
|
|
277
|
+
const inCount = def.inputs?.length || 0;
|
|
278
|
+
const outCount = def.outputs?.length || 0;
|
|
279
|
+
const maxPorts = Math.max(inCount, outCount);
|
|
280
|
+
const headerHeight = 26;
|
|
281
|
+
const portSpacing = 20;
|
|
282
|
+
|
|
283
|
+
if (def.html) {
|
|
284
|
+
// For HTML overlay nodes: reserve space for content BELOW all port rows.
|
|
285
|
+
// Port idx N bottom: headerHeight + 8 + N*portSpacing + portSpacing/2 + 6
|
|
286
|
+
// = 50 + N*portSpacing (for N=0: 50, N=1: 70, ...)
|
|
287
|
+
// Add ~50px for HTML content + bottom padding.
|
|
288
|
+
const lastPortBottom = maxPorts > 0 ? 50 + (maxPorts - 1) * portSpacing : 26;
|
|
289
|
+
return Math.max(lastPortBottom + 50, 90);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const padding = 8;
|
|
293
|
+
let h = headerHeight + padding + (maxPorts * portSpacing) + padding;
|
|
294
|
+
return Math.max(h, 40);
|
|
295
|
+
}
|
|
267
296
|
}
|