html-overlay-node 0.1.6 → 0.1.9
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/html-overlay-node.es.js +379 -802
- 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/package.json +9 -8
- package/src/core/Edge.js +4 -2
- package/src/core/Node.js +27 -11
- package/src/core/Runner.js +32 -18
- package/src/defaults/contextMenu.js +102 -0
- package/src/defaults/index.js +6 -0
- package/src/index.js +70 -795
- package/src/interact/ContextMenu.js +5 -1
- package/src/interact/Controller.js +66 -46
- package/src/nodes/core.js +288 -0
- package/src/nodes/index.js +42 -0
- package/src/nodes/logic.js +57 -0
- package/src/nodes/math.js +86 -0
- package/src/nodes/util.js +134 -0
- package/src/nodes/value.js +116 -0
- package/src/render/CanvasRenderer.js +180 -80
- package/src/render/HtmlOverlay.js +14 -4
- package/src/render/hitTest.js +14 -8
- package/src/utils/utils.js +4 -4
package/src/index.js
CHANGED
|
@@ -10,6 +10,7 @@ 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 { setupDefaultContextMenu as defaultContextMenuSetup } from "./defaults/contextMenu.js";
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
|
|
@@ -22,6 +23,9 @@ export function createGraphEditor(
|
|
|
22
23
|
showMinimap = true,
|
|
23
24
|
enablePropertyPanel = true,
|
|
24
25
|
propertyPanelContainer = null,
|
|
26
|
+
setupDefaultContextMenu = true,
|
|
27
|
+
setupContextMenu = null,
|
|
28
|
+
plugins = [],
|
|
25
29
|
} = {}
|
|
26
30
|
) {
|
|
27
31
|
let canvas;
|
|
@@ -80,6 +84,39 @@ export function createGraphEditor(
|
|
|
80
84
|
// HTML Overlay
|
|
81
85
|
const htmlOverlay = new HtmlOverlay(canvas.parentElement, renderer, registry);
|
|
82
86
|
|
|
87
|
+
// Register callback to sync HTML overlay transform when renderer zoom/pan changes
|
|
88
|
+
renderer.setTransformChangeCallback(() => {
|
|
89
|
+
htmlOverlay.syncTransform();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Edge Canvas (above HTML overlay, for edge animations)
|
|
93
|
+
const edgeCanvas = document.createElement("canvas");
|
|
94
|
+
edgeCanvas.id = "edge-canvas";
|
|
95
|
+
Object.assign(edgeCanvas.style, {
|
|
96
|
+
position: "absolute",
|
|
97
|
+
top: "0",
|
|
98
|
+
left: "0",
|
|
99
|
+
pointerEvents: "none", // Pass through clicks
|
|
100
|
+
zIndex: "15", // Above HTML overlay (10), below port canvas (20)
|
|
101
|
+
});
|
|
102
|
+
canvas.parentElement.appendChild(edgeCanvas);
|
|
103
|
+
|
|
104
|
+
// Create edge renderer (shares transform with main renderer)
|
|
105
|
+
const edgeRenderer = new CanvasRenderer(edgeCanvas, { theme, registry });
|
|
106
|
+
// Sync transform properties with main renderer
|
|
107
|
+
Object.defineProperty(edgeRenderer, 'scale', {
|
|
108
|
+
get() { return renderer.scale; },
|
|
109
|
+
set(v) { renderer.scale = v; }
|
|
110
|
+
});
|
|
111
|
+
Object.defineProperty(edgeRenderer, 'offsetX', {
|
|
112
|
+
get() { return renderer.offsetX; },
|
|
113
|
+
set(v) { renderer.offsetX = v; }
|
|
114
|
+
});
|
|
115
|
+
Object.defineProperty(edgeRenderer, 'offsetY', {
|
|
116
|
+
get() { return renderer.offsetY; },
|
|
117
|
+
set(v) { renderer.offsetY = v; }
|
|
118
|
+
});
|
|
119
|
+
|
|
83
120
|
// Port Canvas (above HTML overlay)
|
|
84
121
|
const portCanvas = document.createElement("canvas");
|
|
85
122
|
portCanvas.id = "port-canvas";
|
|
@@ -88,7 +125,7 @@ export function createGraphEditor(
|
|
|
88
125
|
top: "0",
|
|
89
126
|
left: "0",
|
|
90
127
|
pointerEvents: "none", // Pass through clicks
|
|
91
|
-
zIndex: "20", // Above
|
|
128
|
+
zIndex: "20", // Above edge canvas (15)
|
|
92
129
|
});
|
|
93
130
|
canvas.parentElement.appendChild(portCanvas);
|
|
94
131
|
|
|
@@ -99,7 +136,7 @@ export function createGraphEditor(
|
|
|
99
136
|
portRenderer.offsetX = renderer.offsetX;
|
|
100
137
|
portRenderer.offsetY = renderer.offsetY;
|
|
101
138
|
|
|
102
|
-
const controller = new Controller({ graph, renderer, hooks, htmlOverlay, portRenderer });
|
|
139
|
+
const controller = new Controller({ graph, renderer, hooks, htmlOverlay, edgeRenderer, portRenderer });
|
|
103
140
|
|
|
104
141
|
// Create context menu after controller (needs commandStack)
|
|
105
142
|
const contextMenu = new ContextMenu({
|
|
@@ -136,6 +173,11 @@ export function createGraphEditor(
|
|
|
136
173
|
|
|
137
174
|
const runner = new Runner({ graph, registry, hooks });
|
|
138
175
|
|
|
176
|
+
// Attach runner and controller to graph for node access
|
|
177
|
+
// This allows any node (like Trigger) to execute flows without tight coupling
|
|
178
|
+
graph.runner = runner;
|
|
179
|
+
graph.controller = controller;
|
|
180
|
+
|
|
139
181
|
hooks.on("runner:tick", ({ time, dt }) => {
|
|
140
182
|
renderer.draw(graph, {
|
|
141
183
|
selection: controller.selection,
|
|
@@ -173,815 +215,48 @@ export function createGraphEditor(
|
|
|
173
215
|
controller.render();
|
|
174
216
|
});
|
|
175
217
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
},
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// HTML Custom Node Example
|
|
210
|
-
registry.register("core/HtmlNote", {
|
|
211
|
-
title: "HTML Note",
|
|
212
|
-
size: { w: 200, h: 150 },
|
|
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)";
|
|
224
|
-
|
|
225
|
-
// Header
|
|
226
|
-
header.style.backgroundColor = "#333";
|
|
227
|
-
header.style.borderBottom = "1px solid #444";
|
|
228
|
-
header.style.color = "#eee";
|
|
229
|
-
header.style.fontSize = "12px";
|
|
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";
|
|
218
|
+
// Note: Example nodes have been moved to src/nodes/
|
|
219
|
+
// Users can import and register them selectively:
|
|
220
|
+
// import { registerAllNodes } from "html-overlay-node/nodes";
|
|
221
|
+
// registerAllNodes(registry, hooks);
|
|
237
222
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
},
|
|
223
|
+
// Setup context menu
|
|
224
|
+
if (setupDefaultContextMenu) {
|
|
225
|
+
// Use default context menu setup
|
|
226
|
+
defaultContextMenuSetup(contextMenu, { controller, graph, hooks });
|
|
227
|
+
}
|
|
264
228
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
229
|
+
// Allow custom context menu setup
|
|
230
|
+
if (setupContextMenu) {
|
|
231
|
+
setupContextMenu(contextMenu, { controller, graph, hooks });
|
|
232
|
+
}
|
|
269
233
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
234
|
+
// Install plugins
|
|
235
|
+
if (plugins && plugins.length > 0) {
|
|
236
|
+
for (const plugin of plugins) {
|
|
237
|
+
if (typeof plugin.install === "function") {
|
|
238
|
+
try {
|
|
239
|
+
plugin.install({ graph, registry, hooks, runner, controller, contextMenu }, plugin.options || {});
|
|
240
|
+
} catch (err) {
|
|
241
|
+
console.error(`[createGraphEditor] Failed to install plugin "${plugin.name || 'unknown'}":`, err);
|
|
242
|
+
hooks?.emit?.("error", err);
|
|
273
243
|
}
|
|
244
|
+
} else {
|
|
245
|
+
console.warn(`[createGraphEditor] Plugin "${plugin.name || 'unknown'}" does not have an install() method`);
|
|
274
246
|
}
|
|
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
247
|
}
|
|
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
248
|
}
|
|
972
249
|
|
|
973
250
|
|
|
974
|
-
// Setup default context menu items
|
|
975
|
-
// Users can easily override, remove, or add items here
|
|
976
|
-
setupDefaultContextMenu(contextMenu, { controller, graph, hooks });
|
|
977
|
-
|
|
978
251
|
// initial render & resize
|
|
979
252
|
renderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
253
|
+
edgeRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
980
254
|
portRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
981
255
|
controller.render();
|
|
982
256
|
|
|
983
257
|
const ro = new ResizeObserver(() => {
|
|
984
258
|
renderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
259
|
+
edgeRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
985
260
|
portRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
986
261
|
controller.render();
|
|
987
262
|
});
|