html-overlay-node 0.1.6 → 0.1.10
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 +3 -3
- package/dist/html-overlay-node.es.js +997 -1014
- 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/index.css +391 -232
- package/package.json +9 -8
- package/readme.md +58 -364
- package/src/core/Edge.js +4 -2
- package/src/core/Graph.js +29 -5
- package/src/core/Node.js +27 -11
- package/src/core/Runner.js +201 -211
- package/src/defaults/contextMenu.js +102 -0
- package/src/defaults/index.js +6 -0
- package/src/index.js +85 -793
- package/src/interact/ContextMenu.js +5 -1
- package/src/interact/Controller.js +73 -46
- package/src/nodes/core.js +266 -0
- package/src/nodes/index.js +42 -0
- package/src/nodes/logic.js +60 -0
- package/src/nodes/math.js +99 -0
- package/src/nodes/util.js +176 -0
- package/src/nodes/value.js +100 -0
- package/src/render/CanvasRenderer.js +784 -604
- package/src/render/HtmlOverlay.js +15 -5
- package/src/render/hitTest.js +18 -9
- package/src/ui/HelpOverlay.js +158 -0
- package/src/ui/PropertyPanel.css +58 -27
- package/src/ui/PropertyPanel.js +441 -268
- package/src/utils/utils.js +4 -4
package/src/index.js
CHANGED
|
@@ -10,6 +10,8 @@ import { HtmlOverlay } from "./render/HtmlOverlay.js";
|
|
|
10
10
|
import { RemoveNodeCmd, ChangeGroupColorCmd } from "./core/commands.js";
|
|
11
11
|
import { Minimap } from "./minimap/Minimap.js";
|
|
12
12
|
import { PropertyPanel } from "./ui/PropertyPanel.js";
|
|
13
|
+
import { HelpOverlay } from "./ui/HelpOverlay.js";
|
|
14
|
+
import { setupDefaultContextMenu as defaultContextMenuSetup } from "./defaults/contextMenu.js";
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
|
|
@@ -22,6 +24,11 @@ export function createGraphEditor(
|
|
|
22
24
|
showMinimap = true,
|
|
23
25
|
enablePropertyPanel = true,
|
|
24
26
|
propertyPanelContainer = null,
|
|
27
|
+
enableHelp = true,
|
|
28
|
+
helpShortcuts = null,
|
|
29
|
+
setupDefaultContextMenu = true,
|
|
30
|
+
setupContextMenu = null,
|
|
31
|
+
plugins = [],
|
|
25
32
|
} = {}
|
|
26
33
|
) {
|
|
27
34
|
let canvas;
|
|
@@ -80,6 +87,39 @@ export function createGraphEditor(
|
|
|
80
87
|
// HTML Overlay
|
|
81
88
|
const htmlOverlay = new HtmlOverlay(canvas.parentElement, renderer, registry);
|
|
82
89
|
|
|
90
|
+
// Register callback to sync HTML overlay transform when renderer zoom/pan changes
|
|
91
|
+
renderer.setTransformChangeCallback(() => {
|
|
92
|
+
htmlOverlay.syncTransform();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Edge Canvas (above HTML overlay, for edge animations)
|
|
96
|
+
const edgeCanvas = document.createElement("canvas");
|
|
97
|
+
edgeCanvas.id = "edge-canvas";
|
|
98
|
+
Object.assign(edgeCanvas.style, {
|
|
99
|
+
position: "absolute",
|
|
100
|
+
top: "0",
|
|
101
|
+
left: "0",
|
|
102
|
+
pointerEvents: "none", // Pass through clicks
|
|
103
|
+
zIndex: "15", // Above HTML overlay (10), below port canvas (20)
|
|
104
|
+
});
|
|
105
|
+
canvas.parentElement.appendChild(edgeCanvas);
|
|
106
|
+
|
|
107
|
+
// Create edge renderer (shares transform with main renderer)
|
|
108
|
+
const edgeRenderer = new CanvasRenderer(edgeCanvas, { theme, registry });
|
|
109
|
+
// Sync transform properties with main renderer
|
|
110
|
+
Object.defineProperty(edgeRenderer, 'scale', {
|
|
111
|
+
get() { return renderer.scale; },
|
|
112
|
+
set(v) { renderer.scale = v; }
|
|
113
|
+
});
|
|
114
|
+
Object.defineProperty(edgeRenderer, 'offsetX', {
|
|
115
|
+
get() { return renderer.offsetX; },
|
|
116
|
+
set(v) { renderer.offsetX = v; }
|
|
117
|
+
});
|
|
118
|
+
Object.defineProperty(edgeRenderer, 'offsetY', {
|
|
119
|
+
get() { return renderer.offsetY; },
|
|
120
|
+
set(v) { renderer.offsetY = v; }
|
|
121
|
+
});
|
|
122
|
+
|
|
83
123
|
// Port Canvas (above HTML overlay)
|
|
84
124
|
const portCanvas = document.createElement("canvas");
|
|
85
125
|
portCanvas.id = "port-canvas";
|
|
@@ -88,7 +128,7 @@ export function createGraphEditor(
|
|
|
88
128
|
top: "0",
|
|
89
129
|
left: "0",
|
|
90
130
|
pointerEvents: "none", // Pass through clicks
|
|
91
|
-
zIndex: "20", // Above
|
|
131
|
+
zIndex: "20", // Above edge canvas (15)
|
|
92
132
|
});
|
|
93
133
|
canvas.parentElement.appendChild(portCanvas);
|
|
94
134
|
|
|
@@ -99,7 +139,7 @@ export function createGraphEditor(
|
|
|
99
139
|
portRenderer.offsetX = renderer.offsetX;
|
|
100
140
|
portRenderer.offsetY = renderer.offsetY;
|
|
101
141
|
|
|
102
|
-
const controller = new Controller({ graph, renderer, hooks, htmlOverlay, portRenderer });
|
|
142
|
+
const controller = new Controller({ graph, renderer, hooks, htmlOverlay, edgeRenderer, portRenderer });
|
|
103
143
|
|
|
104
144
|
// Create context menu after controller (needs commandStack)
|
|
105
145
|
const contextMenu = new ContextMenu({
|
|
@@ -134,8 +174,21 @@ export function createGraphEditor(
|
|
|
134
174
|
});
|
|
135
175
|
}
|
|
136
176
|
|
|
177
|
+
// Initialize Help Overlay if enabled
|
|
178
|
+
let helpOverlay = null;
|
|
179
|
+
if (enableHelp) {
|
|
180
|
+
helpOverlay = new HelpOverlay(container, {
|
|
181
|
+
shortcuts: helpShortcuts,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
137
185
|
const runner = new Runner({ graph, registry, hooks });
|
|
138
186
|
|
|
187
|
+
// Attach runner and controller to graph for node access
|
|
188
|
+
// This allows any node (like Trigger) to execute flows without tight coupling
|
|
189
|
+
graph.runner = runner;
|
|
190
|
+
graph.controller = controller;
|
|
191
|
+
|
|
139
192
|
hooks.on("runner:tick", ({ time, dt }) => {
|
|
140
193
|
renderer.draw(graph, {
|
|
141
194
|
selection: controller.selection,
|
|
@@ -173,815 +226,53 @@ export function createGraphEditor(
|
|
|
173
226
|
controller.render();
|
|
174
227
|
});
|
|
175
228
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
size: { w: 180, h: 80 },
|
|
180
|
-
inputs: [{ name: "in", datatype: "any" }],
|
|
181
|
-
outputs: [{ name: "out", datatype: "any" }],
|
|
182
|
-
onCreate(node) {
|
|
183
|
-
node.state.text = "hello";
|
|
184
|
-
},
|
|
185
|
-
onExecute(node, { dt, getInput, setOutput }) {
|
|
186
|
-
// Simple passthrough with uppercase and a heartbeat value
|
|
187
|
-
const incoming = getInput("in");
|
|
188
|
-
const out = (incoming ?? node.state.text ?? "").toString().toUpperCase();
|
|
189
|
-
setOutput(
|
|
190
|
-
"out",
|
|
191
|
-
out + ` · ${Math.floor((performance.now() / 1000) % 100)}`
|
|
192
|
-
);
|
|
193
|
-
},
|
|
194
|
-
onDraw(node, { ctx, theme }) {
|
|
195
|
-
const pr = 8;
|
|
196
|
-
const { x, y } = node.pos;
|
|
197
|
-
const { width: w } = node.size;
|
|
198
|
-
const lx = x + pr; // 월드 x
|
|
199
|
-
const ly = y + 24 + 6; // 타이틀 바(24) 아래 여백 6
|
|
200
|
-
// renderer._drawScreenText(node.state.text ?? "hello", lx, ly, {
|
|
201
|
-
// fontPx: 11,
|
|
202
|
-
// color: theme.text,
|
|
203
|
-
// baseline: "top",
|
|
204
|
-
// align: "left",
|
|
205
|
-
// });
|
|
206
|
-
},
|
|
229
|
+
hooks.on("graph:deserialize", () => {
|
|
230
|
+
renderer.setTransform({ scale: 1, offsetX: 0, offsetY: 0 });
|
|
231
|
+
controller.render();
|
|
207
232
|
});
|
|
208
233
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
inputs: [{ name: "in", datatype: "any" }],
|
|
214
|
-
outputs: [{ name: "out", datatype: "any" }],
|
|
215
|
-
|
|
216
|
-
// HTML Overlay Configuration
|
|
217
|
-
html: {
|
|
218
|
-
// 초기화: 헤더/바디 구성
|
|
219
|
-
init(node, el, { header, body }) {
|
|
220
|
-
el.style.backgroundColor = "#222";
|
|
221
|
-
el.style.borderRadius = "8px";
|
|
222
|
-
el.style.border = "1px solid #444";
|
|
223
|
-
el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.3)";
|
|
234
|
+
// Note: Example nodes have been moved to src/nodes/
|
|
235
|
+
// Users can import and register them selectively:
|
|
236
|
+
// import { registerAllNodes } from "html-overlay-node/nodes";
|
|
237
|
+
// registerAllNodes(registry, hooks);
|
|
224
238
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
header.style.fontWeight = "bold";
|
|
231
|
-
header.textContent = "My HTML Node";
|
|
232
|
-
|
|
233
|
-
// Body
|
|
234
|
-
body.style.padding = "8px";
|
|
235
|
-
body.style.color = "#ccc";
|
|
236
|
-
body.style.fontSize = "12px";
|
|
237
|
-
|
|
238
|
-
const contentDiv = document.createElement("div");
|
|
239
|
-
contentDiv.textContent = "Event Name";
|
|
240
|
-
body.appendChild(contentDiv);
|
|
241
|
-
|
|
242
|
-
// Add some interactive content
|
|
243
|
-
const input = document.createElement("input");
|
|
244
|
-
Object.assign(input.style, {
|
|
245
|
-
marginTop: "4px",
|
|
246
|
-
padding: "4px",
|
|
247
|
-
background: "#111",
|
|
248
|
-
border: "1px solid #555",
|
|
249
|
-
color: "#fff",
|
|
250
|
-
borderRadius: "4px",
|
|
251
|
-
pointerEvents: "auto",
|
|
252
|
-
});
|
|
253
|
-
input.placeholder = "Type here...";
|
|
254
|
-
input.addEventListener("input", (e) => {
|
|
255
|
-
node.state.text = e.target.value;
|
|
256
|
-
});
|
|
257
|
-
input.addEventListener("mousedown", (e) => e.stopPropagation()); // 캔버스 드래그 방지
|
|
258
|
-
|
|
259
|
-
body.appendChild(input);
|
|
260
|
-
|
|
261
|
-
// Store input ref for updates
|
|
262
|
-
el._input = input;
|
|
263
|
-
},
|
|
239
|
+
// Setup context menu
|
|
240
|
+
if (setupDefaultContextMenu) {
|
|
241
|
+
// Use default context menu setup
|
|
242
|
+
defaultContextMenuSetup(contextMenu, { controller, graph, hooks });
|
|
243
|
+
}
|
|
264
244
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
245
|
+
// Allow custom context menu setup
|
|
246
|
+
if (setupContextMenu) {
|
|
247
|
+
setupContextMenu(contextMenu, { controller, graph, hooks });
|
|
248
|
+
}
|
|
269
249
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
250
|
+
// Install plugins
|
|
251
|
+
if (plugins && plugins.length > 0) {
|
|
252
|
+
for (const plugin of plugins) {
|
|
253
|
+
if (typeof plugin.install === "function") {
|
|
254
|
+
try {
|
|
255
|
+
plugin.install({ graph, registry, hooks, runner, controller, contextMenu }, plugin.options || {});
|
|
256
|
+
} catch (err) {
|
|
257
|
+
console.error(`[createGraphEditor] Failed to install plugin "${plugin.name || 'unknown'}":`, err);
|
|
258
|
+
hooks?.emit?.("error", err);
|
|
273
259
|
}
|
|
260
|
+
} else {
|
|
261
|
+
console.warn(`[createGraphEditor] Plugin "${plugin.name || 'unknown'}" does not have an install() method`);
|
|
274
262
|
}
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
onCreate(node) {
|
|
278
|
-
node.state.text = "";
|
|
279
|
-
},
|
|
280
|
-
onExecute(node, { getInput, setOutput }) {
|
|
281
|
-
const incoming = getInput("in");
|
|
282
|
-
setOutput("out", incoming);
|
|
283
|
-
},
|
|
284
|
-
// onDraw는 생략 가능 (HTML이 덮으니까)
|
|
285
|
-
// 하지만 포트 등은 그려야 할 수도 있음.
|
|
286
|
-
// 현재 구조상 CanvasRenderer가 기본 노드를 그리므로,
|
|
287
|
-
// 투명하게 하거나 겹쳐서 그릴 수 있음.
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Todo List Node Example (HTML Overlay)
|
|
291
|
-
registry.register("core/TodoNode", {
|
|
292
|
-
title: "Todo List",
|
|
293
|
-
size: { w: 240, h: 300 },
|
|
294
|
-
inputs: [{ name: "in", datatype: "any" }],
|
|
295
|
-
outputs: [{ name: "out", datatype: "any" }],
|
|
296
|
-
html: {
|
|
297
|
-
init(node, el, { header, body }) {
|
|
298
|
-
el.style.backgroundColor = "#1e1e24";
|
|
299
|
-
el.style.borderRadius = "8px";
|
|
300
|
-
el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.5)";
|
|
301
|
-
el.style.border = "1px solid #333";
|
|
302
|
-
|
|
303
|
-
header.style.backgroundColor = "#2a2a31";
|
|
304
|
-
header.style.padding = "8px";
|
|
305
|
-
header.style.fontWeight = "bold";
|
|
306
|
-
header.style.color = "#e9e9ef";
|
|
307
|
-
header.textContent = node.title;
|
|
308
|
-
|
|
309
|
-
body.style.display = "flex";
|
|
310
|
-
body.style.flexDirection = "column";
|
|
311
|
-
body.style.padding = "8px";
|
|
312
|
-
body.style.color = "#e9e9ef";
|
|
313
|
-
|
|
314
|
-
// Input Area
|
|
315
|
-
const inputRow = document.createElement("div");
|
|
316
|
-
Object.assign(inputRow.style, { display: "flex", gap: "4px", marginBottom: "8px" });
|
|
317
|
-
|
|
318
|
-
const input = document.createElement("input");
|
|
319
|
-
Object.assign(input.style, {
|
|
320
|
-
flex: "1", padding: "6px", borderRadius: "4px",
|
|
321
|
-
border: "1px solid #444", background: "#141417", color: "#fff",
|
|
322
|
-
pointerEvents: "auto",
|
|
323
|
-
});
|
|
324
|
-
input.placeholder = "Add task...";
|
|
325
|
-
|
|
326
|
-
const addBtn = document.createElement("button");
|
|
327
|
-
addBtn.textContent = "+";
|
|
328
|
-
Object.assign(addBtn.style, {
|
|
329
|
-
padding: "0 12px", cursor: "pointer", background: "#4f5b66",
|
|
330
|
-
color: "#fff", border: "none", borderRadius: "4px",
|
|
331
|
-
pointerEvents: "auto",
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
inputRow.append(input, addBtn);
|
|
335
|
-
|
|
336
|
-
// List Area
|
|
337
|
-
const list = document.createElement("ul");
|
|
338
|
-
Object.assign(list.style, {
|
|
339
|
-
listStyle: "none", padding: "0", margin: "0",
|
|
340
|
-
overflow: "hidden", flex: "1"
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
body.append(inputRow, list);
|
|
344
|
-
|
|
345
|
-
// Logic
|
|
346
|
-
const addTodo = () => {
|
|
347
|
-
const text = input.value.trim();
|
|
348
|
-
if (!text) return;
|
|
349
|
-
const todos = node.state.todos || [];
|
|
350
|
-
node.state.todos = [...todos, { id: Date.now(), text, done: false }];
|
|
351
|
-
input.value = "";
|
|
352
|
-
hooks.emit("node:updated", node);
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
addBtn.onclick = addTodo;
|
|
356
|
-
input.onkeydown = (e) => {
|
|
357
|
-
if (e.key === "Enter") addTodo();
|
|
358
|
-
e.stopPropagation();
|
|
359
|
-
};
|
|
360
|
-
input.onmousedown = (e) => e.stopPropagation(); // prevent drag
|
|
361
|
-
|
|
362
|
-
el._refs = { list };
|
|
363
|
-
},
|
|
364
|
-
update(node, el, { selected }) {
|
|
365
|
-
el.style.borderColor = selected ? "#6cf" : "#333";
|
|
366
|
-
|
|
367
|
-
const { list } = el._refs;
|
|
368
|
-
const todos = node.state.todos || [];
|
|
369
|
-
|
|
370
|
-
// Re-render list (simple approach)
|
|
371
|
-
list.innerHTML = "";
|
|
372
|
-
todos.forEach((todo) => {
|
|
373
|
-
const li = document.createElement("li");
|
|
374
|
-
Object.assign(li.style, {
|
|
375
|
-
display: "flex", alignItems: "center", padding: "6px 0",
|
|
376
|
-
borderBottom: "1px solid #2a2a31"
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
const chk = document.createElement("input");
|
|
380
|
-
chk.type = "checkbox";
|
|
381
|
-
chk.checked = todo.done;
|
|
382
|
-
chk.style.marginRight = "8px";
|
|
383
|
-
chk.style.pointerEvents = "auto";
|
|
384
|
-
chk.onchange = () => {
|
|
385
|
-
todo.done = chk.checked;
|
|
386
|
-
hooks.emit("node:updated", node);
|
|
387
|
-
};
|
|
388
|
-
chk.onmousedown = (e) => e.stopPropagation();
|
|
389
|
-
|
|
390
|
-
const span = document.createElement("span");
|
|
391
|
-
span.textContent = todo.text;
|
|
392
|
-
span.style.flex = "1";
|
|
393
|
-
span.style.textDecoration = todo.done ? "line-through" : "none";
|
|
394
|
-
span.style.color = todo.done ? "#777" : "#eee";
|
|
395
|
-
|
|
396
|
-
const del = document.createElement("button");
|
|
397
|
-
del.textContent = "×";
|
|
398
|
-
Object.assign(del.style, {
|
|
399
|
-
background: "none", border: "none", color: "#f44",
|
|
400
|
-
cursor: "pointer", fontSize: "16px",
|
|
401
|
-
pointerEvents: "auto",
|
|
402
|
-
});
|
|
403
|
-
del.onclick = () => {
|
|
404
|
-
node.state.todos = node.state.todos.filter((t) => t.id !== todo.id);
|
|
405
|
-
hooks.emit("node:updated", node);
|
|
406
|
-
};
|
|
407
|
-
del.onmousedown = (e) => e.stopPropagation();
|
|
408
|
-
|
|
409
|
-
li.append(chk, span, del);
|
|
410
|
-
list.appendChild(li);
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
},
|
|
414
|
-
onCreate(node) {
|
|
415
|
-
node.state.todos = [
|
|
416
|
-
{ id: 1, text: "Welcome to Free Node", done: false },
|
|
417
|
-
{ id: 2, text: "Try adding a task", done: true },
|
|
418
|
-
];
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// ===== MATH NODES =====
|
|
423
|
-
registry.register("math/Add", {
|
|
424
|
-
title: "Add",
|
|
425
|
-
size: { w: 140, h: 100 },
|
|
426
|
-
inputs: [
|
|
427
|
-
{ name: "exec", portType: "exec" },
|
|
428
|
-
{ name: "a", portType: "data", datatype: "number" },
|
|
429
|
-
{ name: "b", portType: "data", datatype: "number" },
|
|
430
|
-
],
|
|
431
|
-
outputs: [
|
|
432
|
-
{ name: "exec", portType: "exec" },
|
|
433
|
-
{ name: "result", portType: "data", datatype: "number" },
|
|
434
|
-
],
|
|
435
|
-
onCreate(node) {
|
|
436
|
-
node.state.a = 0;
|
|
437
|
-
node.state.b = 0;
|
|
438
|
-
},
|
|
439
|
-
onExecute(node, { getInput, setOutput }) {
|
|
440
|
-
const a = getInput("a") ?? 0;
|
|
441
|
-
const b = getInput("b") ?? 0;
|
|
442
|
-
const result = a + b;
|
|
443
|
-
console.log("[Add] a:", a, "b:", b, "result:", result);
|
|
444
|
-
setOutput("result", result);
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
registry.register("math/Subtract", {
|
|
449
|
-
title: "Subtract",
|
|
450
|
-
size: { w: 140, h: 80 },
|
|
451
|
-
inputs: [
|
|
452
|
-
{ name: "a", datatype: "number" },
|
|
453
|
-
{ name: "b", datatype: "number" },
|
|
454
|
-
],
|
|
455
|
-
outputs: [{ name: "result", datatype: "number" }],
|
|
456
|
-
onExecute(node, { getInput, setOutput }) {
|
|
457
|
-
const a = getInput("a") ?? 0;
|
|
458
|
-
const b = getInput("b") ?? 0;
|
|
459
|
-
setOutput("result", a - b);
|
|
460
|
-
},
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
registry.register("math/Multiply", {
|
|
464
|
-
title: "Multiply",
|
|
465
|
-
size: { w: 140, h: 100 },
|
|
466
|
-
inputs: [
|
|
467
|
-
{ name: "exec", portType: "exec" },
|
|
468
|
-
{ name: "a", portType: "data", datatype: "number" },
|
|
469
|
-
{ name: "b", portType: "data", datatype: "number" },
|
|
470
|
-
],
|
|
471
|
-
outputs: [
|
|
472
|
-
{ name: "exec", portType: "exec" },
|
|
473
|
-
{ name: "result", portType: "data", datatype: "number" },
|
|
474
|
-
],
|
|
475
|
-
onExecute(node, { getInput, setOutput }) {
|
|
476
|
-
const a = getInput("a") ?? 0;
|
|
477
|
-
const b = getInput("b") ?? 0;
|
|
478
|
-
const result = a * b;
|
|
479
|
-
console.log("[Multiply] a:", a, "b:", b, "result:", result);
|
|
480
|
-
setOutput("result", result);
|
|
481
|
-
},
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
registry.register("math/Divide", {
|
|
485
|
-
title: "Divide",
|
|
486
|
-
size: { w: 140, h: 80 },
|
|
487
|
-
inputs: [
|
|
488
|
-
{ name: "a", datatype: "number" },
|
|
489
|
-
{ name: "b", datatype: "number" },
|
|
490
|
-
],
|
|
491
|
-
outputs: [{ name: "result", datatype: "number" }],
|
|
492
|
-
onExecute(node, { getInput, setOutput }) {
|
|
493
|
-
const a = getInput("a") ?? 0;
|
|
494
|
-
const b = getInput("b") ?? 1;
|
|
495
|
-
setOutput("result", b !== 0 ? a / b : 0);
|
|
496
|
-
},
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
// ===== LOGIC NODES =====
|
|
500
|
-
registry.register("logic/AND", {
|
|
501
|
-
title: "AND",
|
|
502
|
-
size: { w: 120, h: 100 },
|
|
503
|
-
inputs: [
|
|
504
|
-
{ name: "exec", portType: "exec" },
|
|
505
|
-
{ name: "a", portType: "data", datatype: "boolean" },
|
|
506
|
-
{ name: "b", portType: "data", datatype: "boolean" },
|
|
507
|
-
],
|
|
508
|
-
outputs: [
|
|
509
|
-
{ name: "exec", portType: "exec" },
|
|
510
|
-
{ name: "result", portType: "data", datatype: "boolean" },
|
|
511
|
-
],
|
|
512
|
-
onExecute(node, { getInput, setOutput }) {
|
|
513
|
-
const a = getInput("a") ?? false;
|
|
514
|
-
const b = getInput("b") ?? false;
|
|
515
|
-
console.log("[AND] Inputs - a:", a, "b:", b);
|
|
516
|
-
const result = a && b;
|
|
517
|
-
console.log("[AND] Result:", result);
|
|
518
|
-
setOutput("result", result);
|
|
519
|
-
},
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
registry.register("logic/OR", {
|
|
523
|
-
title: "OR",
|
|
524
|
-
size: { w: 120, h: 80 },
|
|
525
|
-
inputs: [
|
|
526
|
-
{ name: "a", datatype: "boolean" },
|
|
527
|
-
{ name: "b", datatype: "boolean" },
|
|
528
|
-
],
|
|
529
|
-
outputs: [{ name: "result", datatype: "boolean" }],
|
|
530
|
-
onExecute(node, { getInput, setOutput }) {
|
|
531
|
-
const a = getInput("a") ?? false;
|
|
532
|
-
const b = getInput("b") ?? false;
|
|
533
|
-
setOutput("result", a || b);
|
|
534
|
-
},
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
registry.register("logic/NOT", {
|
|
538
|
-
title: "NOT",
|
|
539
|
-
size: { w: 120, h: 70 },
|
|
540
|
-
inputs: [{ name: "in", datatype: "boolean" }],
|
|
541
|
-
outputs: [{ name: "out", datatype: "boolean" }],
|
|
542
|
-
onExecute(node, { getInput, setOutput }) {
|
|
543
|
-
const val = getInput("in") ?? false;
|
|
544
|
-
setOutput("out", !val);
|
|
545
|
-
},
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
// ===== VALUE NODES =====
|
|
549
|
-
registry.register("value/Number", {
|
|
550
|
-
title: "Number",
|
|
551
|
-
size: { w: 140, h: 60 },
|
|
552
|
-
outputs: [{ name: "value", portType: "data", datatype: "number" }],
|
|
553
|
-
onCreate(node) {
|
|
554
|
-
node.state.value = 0;
|
|
555
|
-
},
|
|
556
|
-
onExecute(node, { setOutput }) {
|
|
557
|
-
console.log("[Number] Outputting value:", node.state.value ?? 0);
|
|
558
|
-
setOutput("value", node.state.value ?? 0);
|
|
559
|
-
},
|
|
560
|
-
html: {
|
|
561
|
-
init(node, el, { header, body }) {
|
|
562
|
-
el.style.backgroundColor = "#1e1e24";
|
|
563
|
-
el.style.border = "1px solid #444";
|
|
564
|
-
el.style.borderRadius = "8px";
|
|
565
|
-
|
|
566
|
-
header.style.backgroundColor = "#2a2a31";
|
|
567
|
-
header.style.borderBottom = "1px solid #444";
|
|
568
|
-
header.style.color = "#eee";
|
|
569
|
-
header.style.fontSize = "12px";
|
|
570
|
-
header.textContent = "Number";
|
|
571
|
-
|
|
572
|
-
body.style.padding = "12px";
|
|
573
|
-
body.style.display = "flex";
|
|
574
|
-
body.style.alignItems = "center";
|
|
575
|
-
body.style.justifyContent = "center";
|
|
576
|
-
|
|
577
|
-
const input = document.createElement("input");
|
|
578
|
-
input.type = "number";
|
|
579
|
-
input.value = node.state.value ?? 0;
|
|
580
|
-
Object.assign(input.style, {
|
|
581
|
-
width: "100%",
|
|
582
|
-
padding: "6px",
|
|
583
|
-
background: "#141417",
|
|
584
|
-
border: "1px solid #444",
|
|
585
|
-
borderRadius: "4px",
|
|
586
|
-
color: "#fff",
|
|
587
|
-
fontSize: "14px",
|
|
588
|
-
textAlign: "center",
|
|
589
|
-
pointerEvents: "auto",
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
input.addEventListener("change", (e) => {
|
|
593
|
-
node.state.value = parseFloat(e.target.value) || 0;
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
input.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
597
|
-
input.addEventListener("keydown", (e) => e.stopPropagation());
|
|
598
|
-
|
|
599
|
-
body.appendChild(input);
|
|
600
|
-
},
|
|
601
|
-
update(node, el, { header, body, selected }) {
|
|
602
|
-
el.style.borderColor = selected ? "#6cf" : "#444";
|
|
603
|
-
header.style.backgroundColor = selected ? "#3a4a5a" : "#2a2a31";
|
|
604
|
-
},
|
|
605
|
-
},
|
|
606
|
-
onDraw(node, { ctx, theme }) {
|
|
607
|
-
const { x, y } = node.computed;
|
|
608
|
-
ctx.fillStyle = "#8f8";
|
|
609
|
-
ctx.font = "14px sans-serif";
|
|
610
|
-
ctx.textAlign = "center";
|
|
611
|
-
ctx.fillText(String(node.state.value ?? 0), x + 70, y + 42);
|
|
612
|
-
},
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
registry.register("value/String", {
|
|
616
|
-
title: "String",
|
|
617
|
-
size: { w: 160, h: 60 },
|
|
618
|
-
outputs: [{ name: "value", datatype: "string" }],
|
|
619
|
-
onCreate(node) {
|
|
620
|
-
node.state.value = "Hello";
|
|
621
|
-
},
|
|
622
|
-
onExecute(node, { setOutput }) {
|
|
623
|
-
setOutput("value", node.state.value ?? "");
|
|
624
|
-
},
|
|
625
|
-
onDraw(node, { ctx, theme }) {
|
|
626
|
-
const { x, y } = node.computed;
|
|
627
|
-
ctx.fillStyle = "#8f8";
|
|
628
|
-
ctx.font = "12px sans-serif";
|
|
629
|
-
ctx.textAlign = "center";
|
|
630
|
-
const text = String(node.state.value ?? "");
|
|
631
|
-
const displayText = text.length > 15 ? text.substring(0, 15) + "..." : text;
|
|
632
|
-
ctx.fillText(displayText, x + 80, y + 42);
|
|
633
|
-
},
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
registry.register("value/Boolean", {
|
|
637
|
-
title: "Boolean",
|
|
638
|
-
size: { w: 140, h: 60 },
|
|
639
|
-
outputs: [{ name: "value", portType: "data", datatype: "boolean" }],
|
|
640
|
-
onCreate(node) {
|
|
641
|
-
node.state.value = true;
|
|
642
|
-
},
|
|
643
|
-
onExecute(node, { setOutput }) {
|
|
644
|
-
console.log("[Boolean] Outputting value:", node.state.value ?? false);
|
|
645
|
-
setOutput("value", node.state.value ?? false);
|
|
646
|
-
},
|
|
647
|
-
onDraw(node, { ctx, theme }) {
|
|
648
|
-
const { x, y } = node.computed;
|
|
649
|
-
ctx.fillStyle = node.state.value ? "#8f8" : "#f88";
|
|
650
|
-
ctx.font = "14px sans-serif";
|
|
651
|
-
ctx.textAlign = "center";
|
|
652
|
-
ctx.fillText(String(node.state.value), x + 70, y + 42);
|
|
653
|
-
},
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
// ===== UTILITY NODES =====
|
|
657
|
-
registry.register("util/Print", {
|
|
658
|
-
title: "Print",
|
|
659
|
-
size: { w: 140, h: 80 },
|
|
660
|
-
inputs: [
|
|
661
|
-
{ name: "exec", portType: "exec" },
|
|
662
|
-
{ name: "value", portType: "data", datatype: "any" },
|
|
663
|
-
],
|
|
664
|
-
onCreate(node) {
|
|
665
|
-
node.state.lastValue = null;
|
|
666
|
-
},
|
|
667
|
-
onExecute(node, { getInput }) {
|
|
668
|
-
const val = getInput("value");
|
|
669
|
-
if (val !== node.state.lastValue) {
|
|
670
|
-
console.log("[Print]", val);
|
|
671
|
-
node.state.lastValue = val;
|
|
672
|
-
}
|
|
673
|
-
},
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
registry.register("util/Watch", {
|
|
677
|
-
title: "Watch",
|
|
678
|
-
size: { w: 180, h: 110 },
|
|
679
|
-
inputs: [
|
|
680
|
-
{ name: "exec", portType: "exec" },
|
|
681
|
-
{ name: "value", portType: "data", datatype: "any" },
|
|
682
|
-
],
|
|
683
|
-
outputs: [
|
|
684
|
-
{ name: "exec", portType: "exec" },
|
|
685
|
-
{ name: "value", portType: "data", datatype: "any" },
|
|
686
|
-
],
|
|
687
|
-
onCreate(node) {
|
|
688
|
-
node.state.displayValue = "---";
|
|
689
|
-
},
|
|
690
|
-
onExecute(node, { getInput, setOutput }) {
|
|
691
|
-
const val = getInput("value");
|
|
692
|
-
console.log("[Watch] onExecute called, value:", val);
|
|
693
|
-
node.state.displayValue = String(val ?? "---");
|
|
694
|
-
setOutput("value", val);
|
|
695
|
-
},
|
|
696
|
-
onDraw(node, { ctx, theme }) {
|
|
697
|
-
const { x, y } = node.computed;
|
|
698
|
-
ctx.fillStyle = "#fa3";
|
|
699
|
-
ctx.font = "11px monospace";
|
|
700
|
-
ctx.textAlign = "left";
|
|
701
|
-
const text = String(node.state.displayValue ?? "---");
|
|
702
|
-
const displayText = text.length > 20 ? text.substring(0, 20) + "..." : text;
|
|
703
|
-
ctx.fillText(displayText, x + 8, y + 50);
|
|
704
|
-
},
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
registry.register("util/Timer", {
|
|
708
|
-
title: "Timer",
|
|
709
|
-
size: { w: 140, h: 60 },
|
|
710
|
-
outputs: [{ name: "time", datatype: "number" }],
|
|
711
|
-
onCreate(node) {
|
|
712
|
-
node.state.startTime = performance.now();
|
|
713
|
-
},
|
|
714
|
-
onExecute(node, { setOutput }) {
|
|
715
|
-
const elapsed = (performance.now() - (node.state.startTime ?? 0)) / 1000;
|
|
716
|
-
setOutput("time", elapsed.toFixed(2));
|
|
717
|
-
},
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
// Trigger Node with Button (HTML Overlay)
|
|
721
|
-
registry.register("util/Trigger", {
|
|
722
|
-
title: "Trigger",
|
|
723
|
-
size: { w: 140, h: 80 },
|
|
724
|
-
outputs: [{ name: "exec", portType: "exec" }], // Changed to exec port
|
|
725
|
-
|
|
726
|
-
html: {
|
|
727
|
-
init(node, el, { header, body }) {
|
|
728
|
-
el.style.backgroundColor = "#1e1e24";
|
|
729
|
-
el.style.border = "1px solid #444";
|
|
730
|
-
el.style.borderRadius = "8px";
|
|
731
|
-
|
|
732
|
-
header.style.backgroundColor = "#2a2a31";
|
|
733
|
-
header.style.borderBottom = "1px solid #444";
|
|
734
|
-
header.style.color = "#eee";
|
|
735
|
-
header.style.fontSize = "12px";
|
|
736
|
-
header.textContent = "Trigger";
|
|
737
|
-
|
|
738
|
-
body.style.padding = "12px";
|
|
739
|
-
body.style.display = "flex";
|
|
740
|
-
body.style.alignItems = "center";
|
|
741
|
-
body.style.justifyContent = "center";
|
|
742
|
-
|
|
743
|
-
const button = document.createElement("button");
|
|
744
|
-
button.textContent = "Fire!";
|
|
745
|
-
Object.assign(button.style, {
|
|
746
|
-
padding: "8px 16px",
|
|
747
|
-
background: "#4a9eff",
|
|
748
|
-
border: "none",
|
|
749
|
-
borderRadius: "4px",
|
|
750
|
-
color: "#fff",
|
|
751
|
-
fontWeight: "bold",
|
|
752
|
-
cursor: "pointer",
|
|
753
|
-
pointerEvents: "auto",
|
|
754
|
-
transition: "background 0.2s",
|
|
755
|
-
});
|
|
756
|
-
|
|
757
|
-
button.addEventListener("mousedown", (e) => {
|
|
758
|
-
e.stopPropagation();
|
|
759
|
-
button.style.background = "#2a7ede";
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
button.addEventListener("mouseup", () => {
|
|
763
|
-
button.style.background = "#4a9eff";
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
button.addEventListener("click", (e) => {
|
|
767
|
-
e.stopPropagation();
|
|
768
|
-
node.state.triggered = true;
|
|
769
|
-
console.log("[Trigger] Button clicked!");
|
|
770
|
-
|
|
771
|
-
// Use runner.runOnce for connected node execution
|
|
772
|
-
if (node.__runnerRef && node.__controllerRef) {
|
|
773
|
-
console.log("[Trigger] Runner and controller found");
|
|
774
|
-
const runner = node.__runnerRef;
|
|
775
|
-
const controller = node.__controllerRef;
|
|
776
|
-
const graph = controller.graph;
|
|
777
|
-
console.log("[Trigger] Calling runner.runOnce with node.id:", node.id);
|
|
778
|
-
|
|
779
|
-
// Execute connected nodes using runner
|
|
780
|
-
const result = runner.runOnce(node.id, 0);
|
|
781
|
-
const connectedEdges = result.connectedEdges;
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
// Show animation with manual rendering
|
|
786
|
-
const startTime = performance.now();
|
|
787
|
-
const animationDuration = 500;
|
|
788
|
-
|
|
789
|
-
const animate = () => {
|
|
790
|
-
const elapsed = performance.now() - startTime;
|
|
791
|
-
if (elapsed < animationDuration) {
|
|
792
|
-
controller.renderer.draw(graph, {
|
|
793
|
-
selection: controller.selection,
|
|
794
|
-
tempEdge: null,
|
|
795
|
-
running: true,
|
|
796
|
-
time: performance.now(),
|
|
797
|
-
dt: 0,
|
|
798
|
-
activeEdges: connectedEdges, // Only animate connected edges
|
|
799
|
-
});
|
|
800
|
-
controller.htmlOverlay?.draw(graph, controller.selection);
|
|
801
|
-
requestAnimationFrame(animate);
|
|
802
|
-
} else {
|
|
803
|
-
controller.render();
|
|
804
|
-
node.state.triggered = false;
|
|
805
|
-
}
|
|
806
|
-
};
|
|
807
|
-
|
|
808
|
-
animate();
|
|
809
|
-
}
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
body.appendChild(button);
|
|
813
|
-
},
|
|
814
|
-
|
|
815
|
-
update(node, el, { header, body, selected }) {
|
|
816
|
-
el.style.borderColor = selected ? "#6cf" : "#444";
|
|
817
|
-
header.style.backgroundColor = selected ? "#3a4a5a" : "#2a2a31";
|
|
818
|
-
},
|
|
819
|
-
},
|
|
820
|
-
|
|
821
|
-
onCreate(node) {
|
|
822
|
-
node.state.triggered = false;
|
|
823
|
-
},
|
|
824
|
-
|
|
825
|
-
onExecute(node, { setOutput }) {
|
|
826
|
-
console.log("[Trigger] Outputting triggered:", node.state.triggered);
|
|
827
|
-
setOutput("triggered", node.state.triggered);
|
|
828
|
-
},
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
// Group Node
|
|
832
|
-
registry.register("core/Group", {
|
|
833
|
-
title: "Group",
|
|
834
|
-
size: { w: 240, h: 160 },
|
|
835
|
-
onDraw(node, { ctx, theme }) {
|
|
836
|
-
const { x, y, w, h } = node.computed;
|
|
837
|
-
const headerH = 24;
|
|
838
|
-
const color = node.state.color || "#39424e";
|
|
839
|
-
const bgAlpha = 0.5;
|
|
840
|
-
const textColor = theme.text || "#e9e9ef";
|
|
841
|
-
|
|
842
|
-
// Helper for rgba
|
|
843
|
-
const rgba = (hex, a) => {
|
|
844
|
-
const c = hex.replace("#", "");
|
|
845
|
-
const n = parseInt(
|
|
846
|
-
c.length === 3
|
|
847
|
-
? c
|
|
848
|
-
.split("")
|
|
849
|
-
.map((x) => x + x)
|
|
850
|
-
.join("")
|
|
851
|
-
: c,
|
|
852
|
-
16
|
|
853
|
-
);
|
|
854
|
-
const r = (n >> 16) & 255,
|
|
855
|
-
g = (n >> 8) & 255,
|
|
856
|
-
b = n & 255;
|
|
857
|
-
return `rgba(${r},${g},${b},${a})`;
|
|
858
|
-
};
|
|
859
|
-
|
|
860
|
-
// Helper for roundRect
|
|
861
|
-
const roundRect = (ctx, x, y, w, h, r) => {
|
|
862
|
-
if (w < 2 * r) r = w / 2;
|
|
863
|
-
if (h < 2 * r) r = h / 2;
|
|
864
|
-
ctx.beginPath();
|
|
865
|
-
ctx.moveTo(x + r, y);
|
|
866
|
-
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
867
|
-
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
868
|
-
ctx.arcTo(x, y + h, x, y, r);
|
|
869
|
-
ctx.arcTo(x, y, x + w, y, r);
|
|
870
|
-
ctx.closePath();
|
|
871
|
-
};
|
|
872
|
-
|
|
873
|
-
// Body
|
|
874
|
-
ctx.fillStyle = rgba(color, bgAlpha);
|
|
875
|
-
roundRect(ctx, x, y, w, h, 10);
|
|
876
|
-
ctx.fill();
|
|
877
|
-
|
|
878
|
-
// Header bar (subtle)
|
|
879
|
-
ctx.fillStyle = rgba(color, 0.3);
|
|
880
|
-
ctx.beginPath();
|
|
881
|
-
ctx.roundRect(x, y, w, headerH, [10, 10, 0, 0]);
|
|
882
|
-
ctx.fill();
|
|
883
|
-
|
|
884
|
-
// Title - top left with better styling
|
|
885
|
-
ctx.fillStyle = textColor;
|
|
886
|
-
ctx.font = "600 13px system-ui";
|
|
887
|
-
ctx.textBaseline = "top";
|
|
888
|
-
ctx.fillText(node.title, x + 12, y + 6);
|
|
889
|
-
},
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
/**
|
|
893
|
-
* Setup default context menu items
|
|
894
|
-
* This function can be customized or replaced by users
|
|
895
|
-
*/
|
|
896
|
-
function setupDefaultContextMenu(contextMenu, { controller, graph, hooks }) {
|
|
897
|
-
// Add Node submenu (canvas background only)
|
|
898
|
-
const nodeTypes = [];
|
|
899
|
-
for (const [key, typeDef] of graph.registry.types.entries()) {
|
|
900
|
-
nodeTypes.push({
|
|
901
|
-
id: `add-${key}`,
|
|
902
|
-
label: typeDef.title || key,
|
|
903
|
-
action: () => {
|
|
904
|
-
// Get world position from context menu
|
|
905
|
-
const worldPos = contextMenu.worldPosition || { x: 100, y: 100 };
|
|
906
|
-
|
|
907
|
-
// Add node at click position
|
|
908
|
-
const node = graph.addNode(key, {
|
|
909
|
-
x: worldPos.x,
|
|
910
|
-
y: worldPos.y,
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
hooks?.emit("node:updated", node);
|
|
914
|
-
controller.render(); // Update minimap and canvas
|
|
915
|
-
},
|
|
916
|
-
});
|
|
917
263
|
}
|
|
918
|
-
|
|
919
|
-
contextMenu.addItem("add-node", "Add Node", {
|
|
920
|
-
condition: (target) => !target,
|
|
921
|
-
submenu: nodeTypes,
|
|
922
|
-
order: 5,
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
// Delete Node (for all nodes except groups)
|
|
926
|
-
contextMenu.addItem("delete-node", "Delete Node", {
|
|
927
|
-
condition: (target) => target && target.type !== "core/Group",
|
|
928
|
-
action: (target) => {
|
|
929
|
-
const cmd = RemoveNodeCmd(graph, target);
|
|
930
|
-
controller.stack.exec(cmd);
|
|
931
|
-
hooks?.emit("node:updated", target);
|
|
932
|
-
},
|
|
933
|
-
order: 10,
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
// Change Group Color (for groups only) - with submenu
|
|
937
|
-
const colors = [
|
|
938
|
-
{ name: "Default", color: "#39424e" },
|
|
939
|
-
{ name: "Slate", color: "#4a5568" },
|
|
940
|
-
{ name: "Gray", color: "#2d3748" },
|
|
941
|
-
{ name: "Blue", color: "#1a365d" },
|
|
942
|
-
{ name: "Green", color: "#22543d" },
|
|
943
|
-
{ name: "Red", color: "#742a2a" },
|
|
944
|
-
{ name: "Purple", color: "#44337a" },
|
|
945
|
-
];
|
|
946
|
-
|
|
947
|
-
contextMenu.addItem("change-group-color", "Change Color", {
|
|
948
|
-
condition: (target) => target && target.type === "core/Group",
|
|
949
|
-
submenu: colors.map((colorInfo) => ({
|
|
950
|
-
id: `color-${colorInfo.color}`,
|
|
951
|
-
label: colorInfo.name,
|
|
952
|
-
color: colorInfo.color,
|
|
953
|
-
action: (target) => {
|
|
954
|
-
const currentColor = target.state.color || "#39424e";
|
|
955
|
-
const cmd = ChangeGroupColorCmd(target, currentColor, colorInfo.color);
|
|
956
|
-
controller.stack.exec(cmd);
|
|
957
|
-
hooks?.emit("node:updated", target);
|
|
958
|
-
},
|
|
959
|
-
})),
|
|
960
|
-
order: 20,
|
|
961
|
-
});
|
|
962
|
-
contextMenu.addItem("delete-group", "Delete Group", {
|
|
963
|
-
condition: (target) => target && target.type === "core/Group",
|
|
964
|
-
action: (target) => {
|
|
965
|
-
const cmd = RemoveNodeCmd(graph, target);
|
|
966
|
-
controller.stack.exec(cmd);
|
|
967
|
-
hooks?.emit("node:updated", target);
|
|
968
|
-
},
|
|
969
|
-
order: 20,
|
|
970
|
-
});
|
|
971
264
|
}
|
|
972
265
|
|
|
973
266
|
|
|
974
|
-
// Setup default context menu items
|
|
975
|
-
// Users can easily override, remove, or add items here
|
|
976
|
-
setupDefaultContextMenu(contextMenu, { controller, graph, hooks });
|
|
977
|
-
|
|
978
267
|
// initial render & resize
|
|
979
268
|
renderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
269
|
+
edgeRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
980
270
|
portRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
981
271
|
controller.render();
|
|
982
272
|
|
|
983
273
|
const ro = new ResizeObserver(() => {
|
|
984
274
|
renderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
275
|
+
edgeRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
985
276
|
portRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
986
277
|
controller.render();
|
|
987
278
|
});
|
|
@@ -1022,6 +313,7 @@ export function createGraphEditor(
|
|
|
1022
313
|
contextMenu.destroy();
|
|
1023
314
|
if (propertyPanel) propertyPanel.destroy();
|
|
1024
315
|
if (minimap) minimap.destroy();
|
|
316
|
+
if (helpOverlay) helpOverlay.destroy();
|
|
1025
317
|
},
|
|
1026
318
|
};
|
|
1027
319
|
|