html-overlay-node 0.1.5 → 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/ui/PropertyPanel.css +1 -1
- package/src/utils/utils.js +4 -4
|
@@ -153,27 +153,36 @@ class Node {
|
|
|
153
153
|
* @param {string} [portType="data"] - Port type: "exec" or "data"
|
|
154
154
|
* @returns {Object} The created port
|
|
155
155
|
*/
|
|
156
|
+
/**
|
|
157
|
+
* Recalculate minimum size based on ports
|
|
158
|
+
*/
|
|
159
|
+
_updateMinSize() {
|
|
160
|
+
const HEADER_HEIGHT = 28;
|
|
161
|
+
const PORT_SPACING = 24;
|
|
162
|
+
const BOTTOM_PADDING = 10;
|
|
163
|
+
const inHeight = HEADER_HEIGHT + 10 + this.inputs.length * PORT_SPACING + BOTTOM_PADDING;
|
|
164
|
+
const outHeight = HEADER_HEIGHT + 10 + this.outputs.length * PORT_SPACING + BOTTOM_PADDING;
|
|
165
|
+
const minHeight = Math.max(inHeight, outHeight, 60);
|
|
166
|
+
if (this.size.height < minHeight) {
|
|
167
|
+
this.size.height = minHeight;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
156
170
|
addInput(name, datatype = "any", portType = "data") {
|
|
157
|
-
if (
|
|
158
|
-
throw new Error("Input port name must be a non-empty
|
|
171
|
+
if (typeof name !== "string" || portType === "data" && !name) {
|
|
172
|
+
throw new Error("Input port name must be a string (non-empty for data ports)");
|
|
159
173
|
}
|
|
160
174
|
const port = { id: randomUUID(), name, datatype, portType, dir: "in" };
|
|
161
175
|
this.inputs.push(port);
|
|
176
|
+
this._updateMinSize();
|
|
162
177
|
return port;
|
|
163
178
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Add an output port to this node
|
|
166
|
-
* @param {string} name - Port name
|
|
167
|
-
* @param {string} [datatype="any"] - Data type for the port
|
|
168
|
-
* @param {string} [portType="data"] - Port type: "exec" or "data"
|
|
169
|
-
* @returns {Object} The created port
|
|
170
|
-
*/
|
|
171
179
|
addOutput(name, datatype = "any", portType = "data") {
|
|
172
|
-
if (
|
|
173
|
-
throw new Error("Output port name must be a non-empty
|
|
180
|
+
if (typeof name !== "string" || portType === "data" && !name) {
|
|
181
|
+
throw new Error("Output port name must be a string (non-empty for data ports)");
|
|
174
182
|
}
|
|
175
183
|
const port = { id: randomUUID(), name, datatype, portType, dir: "out" };
|
|
176
184
|
this.outputs.push(port);
|
|
185
|
+
this._updateMinSize();
|
|
177
186
|
return port;
|
|
178
187
|
}
|
|
179
188
|
}
|
|
@@ -188,8 +197,8 @@ class Edge {
|
|
|
188
197
|
* @param {string} options.toPort - Target port ID
|
|
189
198
|
*/
|
|
190
199
|
constructor({ id, fromNode, fromPort, toNode, toPort }) {
|
|
191
|
-
if (
|
|
192
|
-
throw new Error("Edge requires fromNode, fromPort, toNode, and toPort");
|
|
200
|
+
if (fromNode == null || fromPort == null || toNode == null || toPort == null) {
|
|
201
|
+
throw new Error("Edge requires fromNode, fromPort, toNode, and toPort (null/undefined not allowed)");
|
|
193
202
|
}
|
|
194
203
|
this.id = id ?? randomUUID();
|
|
195
204
|
this.fromNode = fromNode;
|
|
@@ -518,17 +527,19 @@ class Graph {
|
|
|
518
527
|
}
|
|
519
528
|
}
|
|
520
529
|
function portRect(node, port, idx, dir) {
|
|
521
|
-
const {
|
|
530
|
+
const {
|
|
531
|
+
x: nx,
|
|
532
|
+
y: ny,
|
|
533
|
+
w: width,
|
|
534
|
+
h: height
|
|
535
|
+
} = node.computed || {
|
|
522
536
|
x: node.pos.x,
|
|
523
537
|
y: node.pos.y,
|
|
524
538
|
w: node.size.width,
|
|
525
539
|
h: node.size.height
|
|
526
540
|
};
|
|
527
|
-
const portCount = dir === "in" ? node.inputs.length : node.outputs.length;
|
|
528
541
|
const headerHeight = 28;
|
|
529
|
-
const
|
|
530
|
-
const spacing = availableHeight / (portCount + 1);
|
|
531
|
-
const y = ny + headerHeight + spacing * (idx + 1);
|
|
542
|
+
const y = ny + headerHeight + 10 + idx * 24;
|
|
532
543
|
const portWidth = 12;
|
|
533
544
|
const portHeight = 12;
|
|
534
545
|
if (dir === "in") {
|
|
@@ -591,31 +602,37 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
591
602
|
this.canvas.width = w;
|
|
592
603
|
this.canvas.height = h;
|
|
593
604
|
}
|
|
594
|
-
setTransform({
|
|
595
|
-
|
|
596
|
-
offsetX = this.offsetX,
|
|
597
|
-
offsetY = this.offsetY
|
|
598
|
-
} = {}) {
|
|
605
|
+
setTransform({ scale = this.scale, offsetX = this.offsetX, offsetY = this.offsetY } = {}) {
|
|
606
|
+
var _a;
|
|
599
607
|
this.scale = Math.min(this.maxScale, Math.max(this.minScale, scale));
|
|
600
608
|
this.offsetX = offsetX;
|
|
601
609
|
this.offsetY = offsetY;
|
|
610
|
+
(_a = this._onTransformChange) == null ? void 0 : _a.call(this);
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Set callback to be called when transform changes (zoom/pan)
|
|
614
|
+
* @param {Function} callback - Function to call on transform change
|
|
615
|
+
*/
|
|
616
|
+
setTransformChangeCallback(callback) {
|
|
617
|
+
this._onTransformChange = callback;
|
|
602
618
|
}
|
|
603
619
|
panBy(dx, dy) {
|
|
620
|
+
var _a;
|
|
604
621
|
this.offsetX += dx;
|
|
605
622
|
this.offsetY += dy;
|
|
623
|
+
(_a = this._onTransformChange) == null ? void 0 : _a.call(this);
|
|
606
624
|
}
|
|
607
625
|
zoomAt(factor, cx, cy) {
|
|
626
|
+
var _a;
|
|
608
627
|
const prev = this.scale;
|
|
609
|
-
const next = Math.min(
|
|
610
|
-
this.maxScale,
|
|
611
|
-
Math.max(this.minScale, prev * factor)
|
|
612
|
-
);
|
|
628
|
+
const next = Math.min(this.maxScale, Math.max(this.minScale, prev * factor));
|
|
613
629
|
if (next === prev) return;
|
|
614
630
|
const wx = (cx - this.offsetX) / prev;
|
|
615
631
|
const wy = (cy - this.offsetY) / prev;
|
|
616
632
|
this.offsetX = cx - wx * next;
|
|
617
633
|
this.offsetY = cy - wy * next;
|
|
618
634
|
this.scale = next;
|
|
635
|
+
(_a = this._onTransformChange) == null ? void 0 : _a.call(this);
|
|
619
636
|
}
|
|
620
637
|
screenToWorld(x, y) {
|
|
621
638
|
return {
|
|
@@ -631,7 +648,9 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
631
648
|
}
|
|
632
649
|
_applyTransform() {
|
|
633
650
|
const { ctx } = this;
|
|
634
|
-
ctx.setTransform(
|
|
651
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
652
|
+
ctx.translate(this.offsetX, this.offsetY);
|
|
653
|
+
ctx.scale(this.scale, this.scale);
|
|
635
654
|
}
|
|
636
655
|
_resetTransform() {
|
|
637
656
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
@@ -643,14 +662,8 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
643
662
|
const ang = Math.atan2(y2 - y1, x2 - x1);
|
|
644
663
|
ctx.beginPath();
|
|
645
664
|
ctx.moveTo(x2, y2);
|
|
646
|
-
ctx.lineTo(
|
|
647
|
-
|
|
648
|
-
y2 - s * Math.sin(ang - Math.PI / 6)
|
|
649
|
-
);
|
|
650
|
-
ctx.lineTo(
|
|
651
|
-
x2 - s * Math.cos(ang + Math.PI / 6),
|
|
652
|
-
y2 - s * Math.sin(ang + Math.PI / 6)
|
|
653
|
-
);
|
|
665
|
+
ctx.lineTo(x2 - s * Math.cos(ang - Math.PI / 6), y2 - s * Math.sin(ang - Math.PI / 6));
|
|
666
|
+
ctx.lineTo(x2 - s * Math.cos(ang + Math.PI / 6), y2 - s * Math.sin(ang + Math.PI / 6));
|
|
654
667
|
ctx.closePath();
|
|
655
668
|
ctx.fill();
|
|
656
669
|
}
|
|
@@ -710,7 +723,8 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
710
723
|
time = performance.now(),
|
|
711
724
|
dt = 0,
|
|
712
725
|
groups = null,
|
|
713
|
-
activeEdges = /* @__PURE__ */ new Set()
|
|
726
|
+
activeEdges = /* @__PURE__ */ new Set(),
|
|
727
|
+
drawEdges = true
|
|
714
728
|
} = {}) {
|
|
715
729
|
var _a, _b, _c, _d, _e, _f;
|
|
716
730
|
graph.updateWorldTransforms();
|
|
@@ -722,37 +736,39 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
722
736
|
if (n.type === "core/Group") {
|
|
723
737
|
const sel = selection.has(n.id);
|
|
724
738
|
const def = (_b = (_a = this.registry) == null ? void 0 : _a.types) == null ? void 0 : _b.get(n.type);
|
|
725
|
-
if (def == null ? void 0 : def.onDraw) def.onDraw(n, { ctx, theme });
|
|
739
|
+
if (def == null ? void 0 : def.onDraw) def.onDraw(n, { ctx, theme, renderer: this });
|
|
726
740
|
else this._drawNode(n, sel);
|
|
727
741
|
}
|
|
728
742
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
743
|
+
if (drawEdges) {
|
|
744
|
+
ctx.lineWidth = 1.5 / this.scale;
|
|
745
|
+
let dashArray = null;
|
|
746
|
+
let dashOffset = 0;
|
|
747
|
+
if (running) {
|
|
748
|
+
const speed = 120;
|
|
749
|
+
const phase = time / 1e3 * speed / this.scale % _CanvasRenderer.FONT_SIZE;
|
|
750
|
+
dashArray = [6 / this.scale, 6 / this.scale];
|
|
751
|
+
dashOffset = -phase;
|
|
752
|
+
}
|
|
753
|
+
for (const e of graph.edges.values()) {
|
|
754
|
+
const shouldAnimate = activeEdges && activeEdges.size > 0 && activeEdges.has(e.id);
|
|
755
|
+
if (running && shouldAnimate && dashArray) {
|
|
756
|
+
ctx.setLineDash(dashArray);
|
|
757
|
+
ctx.lineDashOffset = dashOffset;
|
|
758
|
+
} else {
|
|
759
|
+
ctx.setLineDash([]);
|
|
760
|
+
ctx.lineDashOffset = 0;
|
|
761
|
+
}
|
|
762
|
+
const isActive = activeEdges && activeEdges.has(e.id);
|
|
763
|
+
if (isActive) {
|
|
764
|
+
ctx.strokeStyle = "#00ffff";
|
|
765
|
+
ctx.lineWidth = 3 / this.scale;
|
|
766
|
+
} else {
|
|
767
|
+
ctx.strokeStyle = theme.edge;
|
|
768
|
+
ctx.lineWidth = 1.5 / this.scale;
|
|
769
|
+
}
|
|
770
|
+
this._drawEdge(graph, e);
|
|
754
771
|
}
|
|
755
|
-
this._drawEdge(graph, e);
|
|
756
772
|
}
|
|
757
773
|
if (tempEdge) {
|
|
758
774
|
const a = this.screenToWorld(tempEdge.x1, tempEdge.y1);
|
|
@@ -762,12 +778,18 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
762
778
|
let ptsForArrow = null;
|
|
763
779
|
if (this.edgeStyle === "line") {
|
|
764
780
|
this._drawLine(a.x, a.y, b.x, b.y);
|
|
765
|
-
ptsForArrow = [
|
|
781
|
+
ptsForArrow = [
|
|
782
|
+
{ x: a.x, y: a.y },
|
|
783
|
+
{ x: b.x, y: b.y }
|
|
784
|
+
];
|
|
766
785
|
} else if (this.edgeStyle === "orthogonal") {
|
|
767
786
|
ptsForArrow = this._drawOrthogonal(a.x, a.y, b.x, b.y);
|
|
768
787
|
} else {
|
|
769
788
|
this._drawCurve(a.x, a.y, b.x, b.y);
|
|
770
|
-
ptsForArrow = [
|
|
789
|
+
ptsForArrow = [
|
|
790
|
+
{ x: a.x, y: a.y },
|
|
791
|
+
{ x: b.x, y: b.y }
|
|
792
|
+
];
|
|
771
793
|
}
|
|
772
794
|
this.ctx.setLineDash(prevDash);
|
|
773
795
|
if (ptsForArrow && ptsForArrow.length >= 2) {
|
|
@@ -783,8 +805,10 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
783
805
|
const sel = selection.has(n.id);
|
|
784
806
|
const def = (_d = (_c = this.registry) == null ? void 0 : _c.types) == null ? void 0 : _d.get(n.type);
|
|
785
807
|
const hasHtmlOverlay = !!(def == null ? void 0 : def.html);
|
|
786
|
-
|
|
787
|
-
|
|
808
|
+
if (!hasHtmlOverlay) {
|
|
809
|
+
this._drawNode(n, sel, true);
|
|
810
|
+
if (def == null ? void 0 : def.onDraw) def.onDraw(n, { ctx, theme, renderer: this });
|
|
811
|
+
}
|
|
788
812
|
}
|
|
789
813
|
}
|
|
790
814
|
for (const n of graph.nodes.values()) {
|
|
@@ -951,7 +975,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
951
975
|
const iIn = to.inputs.findIndex((p) => p.id === e.toPort);
|
|
952
976
|
const pr1 = portRect(from, null, iOut, "out");
|
|
953
977
|
const pr2 = portRect(to, null, iIn, "in");
|
|
954
|
-
const x1 = pr1.x, y1 = pr1.y +
|
|
978
|
+
const x1 = pr1.x + pr1.w / 2, y1 = pr1.y + pr1.h / 2, x2 = pr2.x + pr2.w / 2, y2 = pr2.y + pr2.h / 2;
|
|
955
979
|
if (this.edgeStyle === "line") {
|
|
956
980
|
this._drawLine(x1, y1, x2, y2);
|
|
957
981
|
} else if (this.edgeStyle === "orthogonal") {
|
|
@@ -971,8 +995,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
971
995
|
const { ctx } = this;
|
|
972
996
|
ctx.beginPath();
|
|
973
997
|
ctx.moveTo(points[0].x, points[0].y);
|
|
974
|
-
for (let i = 1; i < points.length; i++)
|
|
975
|
-
ctx.lineTo(points[i].x, points[i].y);
|
|
998
|
+
for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
|
|
976
999
|
ctx.stroke();
|
|
977
1000
|
}
|
|
978
1001
|
_drawOrthogonal(x1, y1, x2, y2) {
|
|
@@ -1003,6 +1026,72 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
1003
1026
|
ctx.bezierCurveTo(x1 + dx, y1, x2 - dx, y2, x2, y2);
|
|
1004
1027
|
ctx.stroke();
|
|
1005
1028
|
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Draw only edges on a separate canvas (for layering above HTML overlay)
|
|
1031
|
+
* @param {Graph} graph - The graph
|
|
1032
|
+
* @param {Object} options - Rendering options
|
|
1033
|
+
*/
|
|
1034
|
+
drawEdgesOnly(graph, { activeEdges = /* @__PURE__ */ new Set(), running = false, time = performance.now(), tempEdge = null } = {}) {
|
|
1035
|
+
this._resetTransform();
|
|
1036
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
1037
|
+
this._applyTransform();
|
|
1038
|
+
const { ctx, theme } = this;
|
|
1039
|
+
let dashArray = null;
|
|
1040
|
+
let dashOffset = 0;
|
|
1041
|
+
if (running || activeEdges.size > 0) {
|
|
1042
|
+
const speed = 120;
|
|
1043
|
+
const phase = time / 1e3 * speed / this.scale % 12;
|
|
1044
|
+
dashArray = [6 / this.scale, 6 / this.scale];
|
|
1045
|
+
dashOffset = -phase;
|
|
1046
|
+
}
|
|
1047
|
+
ctx.lineWidth = 1.5 / this.scale;
|
|
1048
|
+
ctx.strokeStyle = theme.edge;
|
|
1049
|
+
for (const e of graph.edges.values()) {
|
|
1050
|
+
const isActive = activeEdges && activeEdges.has(e.id);
|
|
1051
|
+
if (isActive && dashArray) {
|
|
1052
|
+
ctx.setLineDash(dashArray);
|
|
1053
|
+
ctx.lineDashOffset = dashOffset;
|
|
1054
|
+
ctx.strokeStyle = "#00ffff";
|
|
1055
|
+
ctx.lineWidth = 3 / this.scale;
|
|
1056
|
+
} else {
|
|
1057
|
+
ctx.setLineDash([]);
|
|
1058
|
+
ctx.strokeStyle = theme.edge;
|
|
1059
|
+
ctx.lineWidth = 1.5 / this.scale;
|
|
1060
|
+
}
|
|
1061
|
+
this._drawEdge(graph, e);
|
|
1062
|
+
}
|
|
1063
|
+
if (tempEdge) {
|
|
1064
|
+
const a = this.screenToWorld(tempEdge.x1, tempEdge.y1);
|
|
1065
|
+
const b = this.screenToWorld(tempEdge.x2, tempEdge.y2);
|
|
1066
|
+
const prevDash = this.ctx.getLineDash();
|
|
1067
|
+
this.ctx.setLineDash([6 / this.scale, 6 / this.scale]);
|
|
1068
|
+
let ptsForArrow = null;
|
|
1069
|
+
if (this.edgeStyle === "line") {
|
|
1070
|
+
this._drawLine(a.x, a.y, b.x, b.y);
|
|
1071
|
+
ptsForArrow = [
|
|
1072
|
+
{ x: a.x, y: a.y },
|
|
1073
|
+
{ x: b.x, y: b.y }
|
|
1074
|
+
];
|
|
1075
|
+
} else if (this.edgeStyle === "orthogonal") {
|
|
1076
|
+
ptsForArrow = this._drawOrthogonal(a.x, a.y, b.x, b.y);
|
|
1077
|
+
} else {
|
|
1078
|
+
this._drawCurve(a.x, a.y, b.x, b.y);
|
|
1079
|
+
ptsForArrow = [
|
|
1080
|
+
{ x: a.x, y: a.y },
|
|
1081
|
+
{ x: b.x, y: b.y }
|
|
1082
|
+
];
|
|
1083
|
+
}
|
|
1084
|
+
this.ctx.setLineDash(prevDash);
|
|
1085
|
+
if (ptsForArrow && ptsForArrow.length >= 2) {
|
|
1086
|
+
const p1 = ptsForArrow[ptsForArrow.length - 2];
|
|
1087
|
+
const p2 = ptsForArrow[ptsForArrow.length - 1];
|
|
1088
|
+
this.ctx.fillStyle = this.theme.edge;
|
|
1089
|
+
this.ctx.strokeStyle = this.theme.edge;
|
|
1090
|
+
this._drawArrowhead(p1.x, p1.y, p2.x, p2.y, 12);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
this._resetTransform();
|
|
1094
|
+
}
|
|
1006
1095
|
};
|
|
1007
1096
|
__publicField(_CanvasRenderer, "FONT_SIZE", 12);
|
|
1008
1097
|
__publicField(_CanvasRenderer, "SELECTED_NODE_COLOR", "#6cf");
|
|
@@ -1126,12 +1215,13 @@ class CommandStack {
|
|
|
1126
1215
|
}
|
|
1127
1216
|
}
|
|
1128
1217
|
const _Controller = class _Controller {
|
|
1129
|
-
constructor({ graph, renderer, hooks, htmlOverlay, contextMenu, portRenderer }) {
|
|
1218
|
+
constructor({ graph, renderer, hooks, htmlOverlay, contextMenu, edgeRenderer, portRenderer }) {
|
|
1130
1219
|
this.graph = graph;
|
|
1131
1220
|
this.renderer = renderer;
|
|
1132
1221
|
this.hooks = hooks;
|
|
1133
1222
|
this.htmlOverlay = htmlOverlay;
|
|
1134
1223
|
this.contextMenu = contextMenu;
|
|
1224
|
+
this.edgeRenderer = edgeRenderer;
|
|
1135
1225
|
this.portRenderer = portRenderer;
|
|
1136
1226
|
this.stack = new CommandStack();
|
|
1137
1227
|
this.selection = /* @__PURE__ */ new Set();
|
|
@@ -1282,13 +1372,11 @@ const _Controller = class _Controller {
|
|
|
1282
1372
|
for (const n of this.graph.nodes.values()) {
|
|
1283
1373
|
for (let i = 0; i < n.inputs.length; i++) {
|
|
1284
1374
|
const r = portRect(n, n.inputs[i], i, "in");
|
|
1285
|
-
if (rectHas(r, x, y))
|
|
1286
|
-
return { node: n, port: n.inputs[i], dir: "in", idx: i };
|
|
1375
|
+
if (rectHas(r, x, y)) return { node: n, port: n.inputs[i], dir: "in", idx: i };
|
|
1287
1376
|
}
|
|
1288
1377
|
for (let i = 0; i < n.outputs.length; i++) {
|
|
1289
1378
|
const r = portRect(n, n.outputs[i], i, "out");
|
|
1290
|
-
if (rectHas(r, x, y))
|
|
1291
|
-
return { node: n, port: n.outputs[i], dir: "out", idx: i };
|
|
1379
|
+
if (rectHas(r, x, y)) return { node: n, port: n.outputs[i], dir: "out", idx: i };
|
|
1292
1380
|
}
|
|
1293
1381
|
}
|
|
1294
1382
|
return null;
|
|
@@ -1537,13 +1625,7 @@ const _Controller = class _Controller {
|
|
|
1537
1625
|
const portIn = this._findPortAtWorld(w.x, w.y);
|
|
1538
1626
|
if (portIn && portIn.dir === "in") {
|
|
1539
1627
|
this.stack.exec(
|
|
1540
|
-
AddEdgeCmd(
|
|
1541
|
-
this.graph,
|
|
1542
|
-
from.fromNode,
|
|
1543
|
-
from.fromPort,
|
|
1544
|
-
portIn.node.id,
|
|
1545
|
-
portIn.port.id
|
|
1546
|
-
)
|
|
1628
|
+
AddEdgeCmd(this.graph, from.fromNode, from.fromPort, portIn.node.id, portIn.port.id)
|
|
1547
1629
|
);
|
|
1548
1630
|
}
|
|
1549
1631
|
this.connecting = null;
|
|
@@ -1711,16 +1793,31 @@ const _Controller = class _Controller {
|
|
|
1711
1793
|
this.render();
|
|
1712
1794
|
}
|
|
1713
1795
|
render() {
|
|
1714
|
-
var _a
|
|
1796
|
+
var _a;
|
|
1715
1797
|
const tEdge = this.renderTempEdge();
|
|
1716
1798
|
this.renderer.draw(this.graph, {
|
|
1717
1799
|
selection: this.selection,
|
|
1718
|
-
tempEdge:
|
|
1800
|
+
tempEdge: null,
|
|
1801
|
+
// Don't draw temp edge on background
|
|
1719
1802
|
boxSelecting: this.boxSelecting,
|
|
1720
|
-
activeEdges: this.activeEdges || /* @__PURE__ */ new Set()
|
|
1721
|
-
|
|
1803
|
+
activeEdges: this.activeEdges || /* @__PURE__ */ new Set(),
|
|
1804
|
+
drawEdges: !this.edgeRenderer
|
|
1805
|
+
// Only draw edges here if no separate edge renderer
|
|
1722
1806
|
});
|
|
1723
1807
|
(_a = this.htmlOverlay) == null ? void 0 : _a.draw(this.graph, this.selection);
|
|
1808
|
+
if (this.edgeRenderer) {
|
|
1809
|
+
const edgeCtx = this.edgeRenderer.ctx;
|
|
1810
|
+
edgeCtx.clearRect(0, 0, this.edgeRenderer.canvas.width, this.edgeRenderer.canvas.height);
|
|
1811
|
+
this.edgeRenderer._applyTransform();
|
|
1812
|
+
this.edgeRenderer.drawEdgesOnly(this.graph, {
|
|
1813
|
+
activeEdges: this.activeEdges || /* @__PURE__ */ new Set(),
|
|
1814
|
+
running: false,
|
|
1815
|
+
time: performance.now(),
|
|
1816
|
+
tempEdge: tEdge
|
|
1817
|
+
// Draw temp edge on edge layer
|
|
1818
|
+
});
|
|
1819
|
+
this.edgeRenderer._resetTransform();
|
|
1820
|
+
}
|
|
1724
1821
|
if (this.boxSelecting) {
|
|
1725
1822
|
const { startX, startY, currentX, currentY } = this.boxSelecting;
|
|
1726
1823
|
const minX = Math.min(startX, currentX);
|
|
@@ -1729,14 +1826,28 @@ const _Controller = class _Controller {
|
|
|
1729
1826
|
const height = Math.abs(currentY - startY);
|
|
1730
1827
|
const screenStart = this.renderer.worldToScreen(minX, minY);
|
|
1731
1828
|
const screenEnd = this.renderer.worldToScreen(minX + width, minY + height);
|
|
1732
|
-
const ctx = this.renderer.ctx;
|
|
1829
|
+
const ctx = this.edgeRenderer ? this.edgeRenderer.ctx : this.renderer.ctx;
|
|
1733
1830
|
ctx.save();
|
|
1734
|
-
this.
|
|
1831
|
+
if (this.edgeRenderer) {
|
|
1832
|
+
this.edgeRenderer._resetTransform();
|
|
1833
|
+
} else {
|
|
1834
|
+
this.renderer._resetTransform();
|
|
1835
|
+
}
|
|
1735
1836
|
ctx.strokeStyle = "#6cf";
|
|
1736
1837
|
ctx.fillStyle = "rgba(102, 204, 255, 0.1)";
|
|
1737
1838
|
ctx.lineWidth = 2;
|
|
1738
|
-
ctx.strokeRect(
|
|
1739
|
-
|
|
1839
|
+
ctx.strokeRect(
|
|
1840
|
+
screenStart.x,
|
|
1841
|
+
screenStart.y,
|
|
1842
|
+
screenEnd.x - screenStart.x,
|
|
1843
|
+
screenEnd.y - screenStart.y
|
|
1844
|
+
);
|
|
1845
|
+
ctx.fillRect(
|
|
1846
|
+
screenStart.x,
|
|
1847
|
+
screenStart.y,
|
|
1848
|
+
screenEnd.x - screenStart.x,
|
|
1849
|
+
screenEnd.y - screenStart.y
|
|
1850
|
+
);
|
|
1740
1851
|
ctx.restore();
|
|
1741
1852
|
}
|
|
1742
1853
|
if (this.portRenderer) {
|
|
@@ -1748,11 +1859,7 @@ const _Controller = class _Controller {
|
|
|
1748
1859
|
this.portRenderer._applyTransform();
|
|
1749
1860
|
for (const n of this.graph.nodes.values()) {
|
|
1750
1861
|
if (n.type !== "core/Group") {
|
|
1751
|
-
|
|
1752
|
-
const hasHtmlOverlay = !!(def == null ? void 0 : def.html);
|
|
1753
|
-
if (hasHtmlOverlay) {
|
|
1754
|
-
this.portRenderer._drawPorts(n);
|
|
1755
|
-
}
|
|
1862
|
+
this.portRenderer._drawPorts(n);
|
|
1756
1863
|
}
|
|
1757
1864
|
}
|
|
1758
1865
|
this.portRenderer._resetTransform();
|
|
@@ -1760,10 +1867,7 @@ const _Controller = class _Controller {
|
|
|
1760
1867
|
}
|
|
1761
1868
|
renderTempEdge() {
|
|
1762
1869
|
if (!this.connecting) return null;
|
|
1763
|
-
const a = this._portAnchorScreen(
|
|
1764
|
-
this.connecting.fromNode,
|
|
1765
|
-
this.connecting.fromPort
|
|
1766
|
-
);
|
|
1870
|
+
const a = this._portAnchorScreen(this.connecting.fromNode, this.connecting.fromPort);
|
|
1767
1871
|
return {
|
|
1768
1872
|
x1: a.x,
|
|
1769
1873
|
y1: a.y,
|
|
@@ -1775,7 +1879,7 @@ const _Controller = class _Controller {
|
|
|
1775
1879
|
const n = this.graph.nodes.get(nodeId);
|
|
1776
1880
|
const iOut = n.outputs.findIndex((p) => p.id === portId);
|
|
1777
1881
|
const r = portRect(n, null, iOut, "out");
|
|
1778
|
-
return this.renderer.worldToScreen(r.x, r.y +
|
|
1882
|
+
return this.renderer.worldToScreen(r.x + r.w / 2, r.y + r.h / 2);
|
|
1779
1883
|
}
|
|
1780
1884
|
};
|
|
1781
1885
|
__publicField(_Controller, "MIN_NODE_WIDTH", 80);
|
|
@@ -1964,7 +2068,8 @@ class ContextMenu {
|
|
|
1964
2068
|
itemEl._hideTimeout = null;
|
|
1965
2069
|
}
|
|
1966
2070
|
if (item.submenu) {
|
|
1967
|
-
|
|
2071
|
+
const submenuItems = typeof item.submenu === "function" ? item.submenu() : item.submenu;
|
|
2072
|
+
this._showSubmenu(submenuItems, itemEl);
|
|
1968
2073
|
}
|
|
1969
2074
|
});
|
|
1970
2075
|
itemEl.addEventListener("mouseleave", (e) => {
|
|
@@ -2142,6 +2247,7 @@ class Runner {
|
|
|
2142
2247
|
}
|
|
2143
2248
|
/**
|
|
2144
2249
|
* Execute connected nodes once from a starting node
|
|
2250
|
+
* Uses queue-based traversal to support branching exec flows
|
|
2145
2251
|
* @param {string} startNodeId - ID of the node to start from
|
|
2146
2252
|
* @param {number} dt - Delta time
|
|
2147
2253
|
*/
|
|
@@ -2149,12 +2255,16 @@ class Runner {
|
|
|
2149
2255
|
console.log("[Runner.runOnce] Starting exec flow from node:", startNodeId);
|
|
2150
2256
|
const executedNodes = [];
|
|
2151
2257
|
const allConnectedNodes = /* @__PURE__ */ new Set();
|
|
2152
|
-
|
|
2153
|
-
|
|
2258
|
+
const queue = [startNodeId];
|
|
2259
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2260
|
+
while (queue.length > 0) {
|
|
2261
|
+
const currentNodeId = queue.shift();
|
|
2262
|
+
if (visited.has(currentNodeId)) continue;
|
|
2263
|
+
visited.add(currentNodeId);
|
|
2154
2264
|
const node = this.graph.nodes.get(currentNodeId);
|
|
2155
2265
|
if (!node) {
|
|
2156
2266
|
console.warn(`[Runner.runOnce] Node not found: ${currentNodeId}`);
|
|
2157
|
-
|
|
2267
|
+
continue;
|
|
2158
2268
|
}
|
|
2159
2269
|
executedNodes.push(currentNodeId);
|
|
2160
2270
|
allConnectedNodes.add(currentNodeId);
|
|
@@ -2173,7 +2283,8 @@ class Runner {
|
|
|
2173
2283
|
}
|
|
2174
2284
|
}
|
|
2175
2285
|
this.executeNode(currentNodeId, dt);
|
|
2176
|
-
|
|
2286
|
+
const nextNodes = this.findAllNextExecNodes(currentNodeId);
|
|
2287
|
+
queue.push(...nextNodes);
|
|
2177
2288
|
}
|
|
2178
2289
|
console.log("[Runner.runOnce] Executed nodes:", executedNodes.length);
|
|
2179
2290
|
const connectedEdges = /* @__PURE__ */ new Set();
|
|
@@ -2186,21 +2297,25 @@ class Runner {
|
|
|
2186
2297
|
return { connectedNodes: allConnectedNodes, connectedEdges };
|
|
2187
2298
|
}
|
|
2188
2299
|
/**
|
|
2189
|
-
* Find
|
|
2300
|
+
* Find all nodes connected via exec outputs
|
|
2301
|
+
* Supports multiple connections from a single exec output
|
|
2190
2302
|
* @param {string} nodeId - Current node ID
|
|
2191
|
-
* @returns {string
|
|
2303
|
+
* @returns {string[]} Array of next node IDs
|
|
2192
2304
|
*/
|
|
2193
|
-
|
|
2305
|
+
findAllNextExecNodes(nodeId) {
|
|
2194
2306
|
const node = this.graph.nodes.get(nodeId);
|
|
2195
|
-
if (!node) return
|
|
2196
|
-
const
|
|
2197
|
-
if (
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2307
|
+
if (!node) return [];
|
|
2308
|
+
const execOutputs = node.outputs.filter((p) => p.portType === "exec");
|
|
2309
|
+
if (execOutputs.length === 0) return [];
|
|
2310
|
+
const nextNodes = [];
|
|
2311
|
+
for (const execOutput of execOutputs) {
|
|
2312
|
+
for (const edge of this.graph.edges.values()) {
|
|
2313
|
+
if (edge.fromNode === nodeId && edge.fromPort === execOutput.id) {
|
|
2314
|
+
nextNodes.push(edge.toNode);
|
|
2315
|
+
}
|
|
2201
2316
|
}
|
|
2202
2317
|
}
|
|
2203
|
-
return
|
|
2318
|
+
return nextNodes;
|
|
2204
2319
|
}
|
|
2205
2320
|
/**
|
|
2206
2321
|
* Execute a single node
|
|
@@ -2331,7 +2446,7 @@ class HtmlOverlay {
|
|
|
2331
2446
|
return container;
|
|
2332
2447
|
}
|
|
2333
2448
|
/** 노드용 엘리먼트 생성(한 번만) */
|
|
2334
|
-
_ensureNodeElement(node, def) {
|
|
2449
|
+
_ensureNodeElement(node, def, graph) {
|
|
2335
2450
|
var _a;
|
|
2336
2451
|
let el = this.nodes.get(node.id);
|
|
2337
2452
|
if (!el) {
|
|
@@ -2340,7 +2455,7 @@ class HtmlOverlay {
|
|
|
2340
2455
|
} else if (def.html) {
|
|
2341
2456
|
el = this._createDefaultNodeLayout(node);
|
|
2342
2457
|
if (def.html.init) {
|
|
2343
|
-
def.html.init(node, el, el._domParts);
|
|
2458
|
+
def.html.init(node, el, { ...el._domParts, graph });
|
|
2344
2459
|
}
|
|
2345
2460
|
} else {
|
|
2346
2461
|
return null;
|
|
@@ -2363,7 +2478,7 @@ class HtmlOverlay {
|
|
|
2363
2478
|
const def = this.registry.types.get(node.type);
|
|
2364
2479
|
const hasHtml = !!(def == null ? void 0 : def.html);
|
|
2365
2480
|
if (!hasHtml) continue;
|
|
2366
|
-
const el = this._ensureNodeElement(node, def);
|
|
2481
|
+
const el = this._ensureNodeElement(node, def, graph);
|
|
2367
2482
|
if (!el) continue;
|
|
2368
2483
|
el.style.left = `${node.computed.x}px`;
|
|
2369
2484
|
el.style.top = `${node.computed.y}px`;
|
|
@@ -2386,6 +2501,15 @@ class HtmlOverlay {
|
|
|
2386
2501
|
}
|
|
2387
2502
|
}
|
|
2388
2503
|
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Sync container transform with renderer state (lightweight update)
|
|
2506
|
+
* Called when zoom/pan occurs without needing full redraw
|
|
2507
|
+
*/
|
|
2508
|
+
syncTransform() {
|
|
2509
|
+
const { scale, offsetX, offsetY } = this.renderer;
|
|
2510
|
+
this.container.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
|
2511
|
+
this.container.style.transformOrigin = "0 0";
|
|
2512
|
+
}
|
|
2389
2513
|
clear() {
|
|
2390
2514
|
for (const [, el] of this.nodes) {
|
|
2391
2515
|
el.remove();
|
|
@@ -2725,14 +2849,87 @@ class PropertyPanel {
|
|
|
2725
2849
|
}
|
|
2726
2850
|
}
|
|
2727
2851
|
}
|
|
2852
|
+
function setupDefaultContextMenu(contextMenu, { controller, graph, hooks }) {
|
|
2853
|
+
const getNodeTypes = () => {
|
|
2854
|
+
const nodeTypes = [];
|
|
2855
|
+
for (const [key, typeDef] of graph.registry.types.entries()) {
|
|
2856
|
+
nodeTypes.push({
|
|
2857
|
+
id: `add-${key}`,
|
|
2858
|
+
label: typeDef.title || key,
|
|
2859
|
+
action: () => {
|
|
2860
|
+
const worldPos = contextMenu.worldPosition || { x: 100, y: 100 };
|
|
2861
|
+
const node = graph.addNode(key, {
|
|
2862
|
+
x: worldPos.x,
|
|
2863
|
+
y: worldPos.y
|
|
2864
|
+
});
|
|
2865
|
+
hooks == null ? void 0 : hooks.emit("node:updated", node);
|
|
2866
|
+
controller.render();
|
|
2867
|
+
}
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
return nodeTypes;
|
|
2871
|
+
};
|
|
2872
|
+
contextMenu.addItem("add-node", "Add Node", {
|
|
2873
|
+
condition: (target) => !target,
|
|
2874
|
+
submenu: getNodeTypes,
|
|
2875
|
+
// Pass function instead of array
|
|
2876
|
+
order: 5
|
|
2877
|
+
});
|
|
2878
|
+
contextMenu.addItem("delete-node", "Delete Node", {
|
|
2879
|
+
condition: (target) => target && target.type !== "core/Group",
|
|
2880
|
+
action: (target) => {
|
|
2881
|
+
const cmd = RemoveNodeCmd(graph, target);
|
|
2882
|
+
controller.stack.exec(cmd);
|
|
2883
|
+
hooks == null ? void 0 : hooks.emit("node:updated", target);
|
|
2884
|
+
},
|
|
2885
|
+
order: 10
|
|
2886
|
+
});
|
|
2887
|
+
const colors = [
|
|
2888
|
+
{ name: "Default", color: "#39424e" },
|
|
2889
|
+
{ name: "Slate", color: "#4a5568" },
|
|
2890
|
+
{ name: "Gray", color: "#2d3748" },
|
|
2891
|
+
{ name: "Blue", color: "#1a365d" },
|
|
2892
|
+
{ name: "Green", color: "#22543d" },
|
|
2893
|
+
{ name: "Red", color: "#742a2a" },
|
|
2894
|
+
{ name: "Purple", color: "#44337a" }
|
|
2895
|
+
];
|
|
2896
|
+
contextMenu.addItem("change-group-color", "Change Color", {
|
|
2897
|
+
condition: (target) => target && target.type === "core/Group",
|
|
2898
|
+
submenu: colors.map((colorInfo) => ({
|
|
2899
|
+
id: `color-${colorInfo.color}`,
|
|
2900
|
+
label: colorInfo.name,
|
|
2901
|
+
color: colorInfo.color,
|
|
2902
|
+
action: (target) => {
|
|
2903
|
+
const currentColor = target.state.color || "#39424e";
|
|
2904
|
+
const cmd = ChangeGroupColorCmd(target, currentColor, colorInfo.color);
|
|
2905
|
+
controller.stack.exec(cmd);
|
|
2906
|
+
hooks == null ? void 0 : hooks.emit("node:updated", target);
|
|
2907
|
+
}
|
|
2908
|
+
})),
|
|
2909
|
+
order: 20
|
|
2910
|
+
});
|
|
2911
|
+
contextMenu.addItem("delete-group", "Delete Group", {
|
|
2912
|
+
condition: (target) => target && target.type === "core/Group",
|
|
2913
|
+
action: (target) => {
|
|
2914
|
+
const cmd = RemoveNodeCmd(graph, target);
|
|
2915
|
+
controller.stack.exec(cmd);
|
|
2916
|
+
hooks == null ? void 0 : hooks.emit("node:updated", target);
|
|
2917
|
+
},
|
|
2918
|
+
order: 20
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2728
2921
|
function createGraphEditor(target, {
|
|
2729
2922
|
theme,
|
|
2730
2923
|
hooks: customHooks,
|
|
2731
2924
|
autorun = true,
|
|
2732
2925
|
showMinimap = true,
|
|
2733
2926
|
enablePropertyPanel = true,
|
|
2734
|
-
propertyPanelContainer = null
|
|
2927
|
+
propertyPanelContainer = null,
|
|
2928
|
+
setupDefaultContextMenu: setupDefaultContextMenu$1 = true,
|
|
2929
|
+
setupContextMenu = null,
|
|
2930
|
+
plugins = []
|
|
2735
2931
|
} = {}) {
|
|
2932
|
+
var _a;
|
|
2736
2933
|
let canvas;
|
|
2737
2934
|
let container;
|
|
2738
2935
|
if (typeof target === "string") {
|
|
@@ -2780,6 +2977,46 @@ function createGraphEditor(target, {
|
|
|
2780
2977
|
const graph = new Graph({ hooks, registry });
|
|
2781
2978
|
const renderer = new CanvasRenderer(canvas, { theme, registry });
|
|
2782
2979
|
const htmlOverlay = new HtmlOverlay(canvas.parentElement, renderer, registry);
|
|
2980
|
+
renderer.setTransformChangeCallback(() => {
|
|
2981
|
+
htmlOverlay.syncTransform();
|
|
2982
|
+
});
|
|
2983
|
+
const edgeCanvas = document.createElement("canvas");
|
|
2984
|
+
edgeCanvas.id = "edge-canvas";
|
|
2985
|
+
Object.assign(edgeCanvas.style, {
|
|
2986
|
+
position: "absolute",
|
|
2987
|
+
top: "0",
|
|
2988
|
+
left: "0",
|
|
2989
|
+
pointerEvents: "none",
|
|
2990
|
+
// Pass through clicks
|
|
2991
|
+
zIndex: "15"
|
|
2992
|
+
// Above HTML overlay (10), below port canvas (20)
|
|
2993
|
+
});
|
|
2994
|
+
canvas.parentElement.appendChild(edgeCanvas);
|
|
2995
|
+
const edgeRenderer = new CanvasRenderer(edgeCanvas, { theme, registry });
|
|
2996
|
+
Object.defineProperty(edgeRenderer, "scale", {
|
|
2997
|
+
get() {
|
|
2998
|
+
return renderer.scale;
|
|
2999
|
+
},
|
|
3000
|
+
set(v) {
|
|
3001
|
+
renderer.scale = v;
|
|
3002
|
+
}
|
|
3003
|
+
});
|
|
3004
|
+
Object.defineProperty(edgeRenderer, "offsetX", {
|
|
3005
|
+
get() {
|
|
3006
|
+
return renderer.offsetX;
|
|
3007
|
+
},
|
|
3008
|
+
set(v) {
|
|
3009
|
+
renderer.offsetX = v;
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
Object.defineProperty(edgeRenderer, "offsetY", {
|
|
3013
|
+
get() {
|
|
3014
|
+
return renderer.offsetY;
|
|
3015
|
+
},
|
|
3016
|
+
set(v) {
|
|
3017
|
+
renderer.offsetY = v;
|
|
3018
|
+
}
|
|
3019
|
+
});
|
|
2783
3020
|
const portCanvas = document.createElement("canvas");
|
|
2784
3021
|
portCanvas.id = "port-canvas";
|
|
2785
3022
|
Object.assign(portCanvas.style, {
|
|
@@ -2789,7 +3026,7 @@ function createGraphEditor(target, {
|
|
|
2789
3026
|
pointerEvents: "none",
|
|
2790
3027
|
// Pass through clicks
|
|
2791
3028
|
zIndex: "20"
|
|
2792
|
-
// Above
|
|
3029
|
+
// Above edge canvas (15)
|
|
2793
3030
|
});
|
|
2794
3031
|
canvas.parentElement.appendChild(portCanvas);
|
|
2795
3032
|
const portRenderer = new CanvasRenderer(portCanvas, { theme, registry });
|
|
@@ -2797,7 +3034,7 @@ function createGraphEditor(target, {
|
|
|
2797
3034
|
portRenderer.scale = renderer.scale;
|
|
2798
3035
|
portRenderer.offsetX = renderer.offsetX;
|
|
2799
3036
|
portRenderer.offsetY = renderer.offsetY;
|
|
2800
|
-
const controller = new Controller({ graph, renderer, hooks, htmlOverlay, portRenderer });
|
|
3037
|
+
const controller = new Controller({ graph, renderer, hooks, htmlOverlay, edgeRenderer, portRenderer });
|
|
2801
3038
|
const contextMenu = new ContextMenu({
|
|
2802
3039
|
graph,
|
|
2803
3040
|
hooks,
|
|
@@ -2822,6 +3059,8 @@ function createGraphEditor(target, {
|
|
|
2822
3059
|
});
|
|
2823
3060
|
}
|
|
2824
3061
|
const runner = new Runner({ graph, registry, hooks });
|
|
3062
|
+
graph.runner = runner;
|
|
3063
|
+
graph.controller = controller;
|
|
2825
3064
|
hooks.on("runner:tick", ({ time, dt }) => {
|
|
2826
3065
|
renderer.draw(graph, {
|
|
2827
3066
|
selection: controller.selection,
|
|
@@ -2856,695 +3095,33 @@ function createGraphEditor(target, {
|
|
|
2856
3095
|
hooks.on("node:updated", () => {
|
|
2857
3096
|
controller.render();
|
|
2858
3097
|
});
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
);
|
|
2874
|
-
},
|
|
2875
|
-
onDraw(node, { ctx, theme: theme2 }) {
|
|
2876
|
-
const { x, y } = node.pos;
|
|
2877
|
-
const { width: w } = node.size;
|
|
2878
|
-
}
|
|
2879
|
-
});
|
|
2880
|
-
registry.register("core/HtmlNote", {
|
|
2881
|
-
title: "HTML Note",
|
|
2882
|
-
size: { w: 200, h: 150 },
|
|
2883
|
-
inputs: [{ name: "in", datatype: "any" }],
|
|
2884
|
-
outputs: [{ name: "out", datatype: "any" }],
|
|
2885
|
-
// HTML Overlay Configuration
|
|
2886
|
-
html: {
|
|
2887
|
-
// 초기화: 헤더/바디 구성
|
|
2888
|
-
init(node, el, { header, body }) {
|
|
2889
|
-
el.style.backgroundColor = "#222";
|
|
2890
|
-
el.style.borderRadius = "8px";
|
|
2891
|
-
el.style.border = "1px solid #444";
|
|
2892
|
-
el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.3)";
|
|
2893
|
-
header.style.backgroundColor = "#333";
|
|
2894
|
-
header.style.borderBottom = "1px solid #444";
|
|
2895
|
-
header.style.color = "#eee";
|
|
2896
|
-
header.style.fontSize = "12px";
|
|
2897
|
-
header.style.fontWeight = "bold";
|
|
2898
|
-
header.textContent = "My HTML Node";
|
|
2899
|
-
body.style.padding = "8px";
|
|
2900
|
-
body.style.color = "#ccc";
|
|
2901
|
-
body.style.fontSize = "12px";
|
|
2902
|
-
const contentDiv = document.createElement("div");
|
|
2903
|
-
contentDiv.textContent = "Event Name";
|
|
2904
|
-
body.appendChild(contentDiv);
|
|
2905
|
-
const input = document.createElement("input");
|
|
2906
|
-
Object.assign(input.style, {
|
|
2907
|
-
marginTop: "4px",
|
|
2908
|
-
padding: "4px",
|
|
2909
|
-
background: "#111",
|
|
2910
|
-
border: "1px solid #555",
|
|
2911
|
-
color: "#fff",
|
|
2912
|
-
borderRadius: "4px",
|
|
2913
|
-
pointerEvents: "auto"
|
|
2914
|
-
});
|
|
2915
|
-
input.placeholder = "Type here...";
|
|
2916
|
-
input.addEventListener("input", (e) => {
|
|
2917
|
-
node.state.text = e.target.value;
|
|
2918
|
-
});
|
|
2919
|
-
input.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
2920
|
-
body.appendChild(input);
|
|
2921
|
-
el._input = input;
|
|
2922
|
-
},
|
|
2923
|
-
// 매 프레임(또는 필요시) 업데이트
|
|
2924
|
-
update(node, el, { header, _body, selected }) {
|
|
2925
|
-
el.style.borderColor = selected ? "#6cf" : "#444";
|
|
2926
|
-
header.style.backgroundColor = selected ? "#3a4a5a" : "#333";
|
|
2927
|
-
if (el._input.value !== (node.state.text || "")) {
|
|
2928
|
-
el._input.value = node.state.text || "";
|
|
3098
|
+
if (setupDefaultContextMenu$1) {
|
|
3099
|
+
setupDefaultContextMenu(contextMenu, { controller, graph, hooks });
|
|
3100
|
+
}
|
|
3101
|
+
if (setupContextMenu) {
|
|
3102
|
+
setupContextMenu(contextMenu, { controller, graph, hooks });
|
|
3103
|
+
}
|
|
3104
|
+
if (plugins && plugins.length > 0) {
|
|
3105
|
+
for (const plugin of plugins) {
|
|
3106
|
+
if (typeof plugin.install === "function") {
|
|
3107
|
+
try {
|
|
3108
|
+
plugin.install({ graph, registry, hooks, runner, controller, contextMenu }, plugin.options || {});
|
|
3109
|
+
} catch (err) {
|
|
3110
|
+
console.error(`[createGraphEditor] Failed to install plugin "${plugin.name || "unknown"}":`, err);
|
|
3111
|
+
(_a = hooks == null ? void 0 : hooks.emit) == null ? void 0 : _a.call(hooks, "error", err);
|
|
2929
3112
|
}
|
|
3113
|
+
} else {
|
|
3114
|
+
console.warn(`[createGraphEditor] Plugin "${plugin.name || "unknown"}" does not have an install() method`);
|
|
2930
3115
|
}
|
|
2931
|
-
},
|
|
2932
|
-
onCreate(node) {
|
|
2933
|
-
node.state.text = "";
|
|
2934
|
-
},
|
|
2935
|
-
onExecute(node, { getInput, setOutput }) {
|
|
2936
|
-
const incoming = getInput("in");
|
|
2937
|
-
setOutput("out", incoming);
|
|
2938
|
-
}
|
|
2939
|
-
// onDraw는 생략 가능 (HTML이 덮으니까)
|
|
2940
|
-
// 하지만 포트 등은 그려야 할 수도 있음.
|
|
2941
|
-
// 현재 구조상 CanvasRenderer가 기본 노드를 그리므로,
|
|
2942
|
-
// 투명하게 하거나 겹쳐서 그릴 수 있음.
|
|
2943
|
-
});
|
|
2944
|
-
registry.register("core/TodoNode", {
|
|
2945
|
-
title: "Todo List",
|
|
2946
|
-
size: { w: 240, h: 300 },
|
|
2947
|
-
inputs: [{ name: "in", datatype: "any" }],
|
|
2948
|
-
outputs: [{ name: "out", datatype: "any" }],
|
|
2949
|
-
html: {
|
|
2950
|
-
init(node, el, { header, body }) {
|
|
2951
|
-
el.style.backgroundColor = "#1e1e24";
|
|
2952
|
-
el.style.borderRadius = "8px";
|
|
2953
|
-
el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.5)";
|
|
2954
|
-
el.style.border = "1px solid #333";
|
|
2955
|
-
header.style.backgroundColor = "#2a2a31";
|
|
2956
|
-
header.style.padding = "8px";
|
|
2957
|
-
header.style.fontWeight = "bold";
|
|
2958
|
-
header.style.color = "#e9e9ef";
|
|
2959
|
-
header.textContent = node.title;
|
|
2960
|
-
body.style.display = "flex";
|
|
2961
|
-
body.style.flexDirection = "column";
|
|
2962
|
-
body.style.padding = "8px";
|
|
2963
|
-
body.style.color = "#e9e9ef";
|
|
2964
|
-
const inputRow = document.createElement("div");
|
|
2965
|
-
Object.assign(inputRow.style, { display: "flex", gap: "4px", marginBottom: "8px" });
|
|
2966
|
-
const input = document.createElement("input");
|
|
2967
|
-
Object.assign(input.style, {
|
|
2968
|
-
flex: "1",
|
|
2969
|
-
padding: "6px",
|
|
2970
|
-
borderRadius: "4px",
|
|
2971
|
-
border: "1px solid #444",
|
|
2972
|
-
background: "#141417",
|
|
2973
|
-
color: "#fff",
|
|
2974
|
-
pointerEvents: "auto"
|
|
2975
|
-
});
|
|
2976
|
-
input.placeholder = "Add task...";
|
|
2977
|
-
const addBtn = document.createElement("button");
|
|
2978
|
-
addBtn.textContent = "+";
|
|
2979
|
-
Object.assign(addBtn.style, {
|
|
2980
|
-
padding: "0 12px",
|
|
2981
|
-
cursor: "pointer",
|
|
2982
|
-
background: "#4f5b66",
|
|
2983
|
-
color: "#fff",
|
|
2984
|
-
border: "none",
|
|
2985
|
-
borderRadius: "4px",
|
|
2986
|
-
pointerEvents: "auto"
|
|
2987
|
-
});
|
|
2988
|
-
inputRow.append(input, addBtn);
|
|
2989
|
-
const list = document.createElement("ul");
|
|
2990
|
-
Object.assign(list.style, {
|
|
2991
|
-
listStyle: "none",
|
|
2992
|
-
padding: "0",
|
|
2993
|
-
margin: "0",
|
|
2994
|
-
overflow: "hidden",
|
|
2995
|
-
flex: "1"
|
|
2996
|
-
});
|
|
2997
|
-
body.append(inputRow, list);
|
|
2998
|
-
const addTodo = () => {
|
|
2999
|
-
const text = input.value.trim();
|
|
3000
|
-
if (!text) return;
|
|
3001
|
-
const todos = node.state.todos || [];
|
|
3002
|
-
node.state.todos = [...todos, { id: Date.now(), text, done: false }];
|
|
3003
|
-
input.value = "";
|
|
3004
|
-
hooks.emit("node:updated", node);
|
|
3005
|
-
};
|
|
3006
|
-
addBtn.onclick = addTodo;
|
|
3007
|
-
input.onkeydown = (e) => {
|
|
3008
|
-
if (e.key === "Enter") addTodo();
|
|
3009
|
-
e.stopPropagation();
|
|
3010
|
-
};
|
|
3011
|
-
input.onmousedown = (e) => e.stopPropagation();
|
|
3012
|
-
el._refs = { list };
|
|
3013
|
-
},
|
|
3014
|
-
update(node, el, { selected }) {
|
|
3015
|
-
el.style.borderColor = selected ? "#6cf" : "#333";
|
|
3016
|
-
const { list } = el._refs;
|
|
3017
|
-
const todos = node.state.todos || [];
|
|
3018
|
-
list.innerHTML = "";
|
|
3019
|
-
todos.forEach((todo) => {
|
|
3020
|
-
const li = document.createElement("li");
|
|
3021
|
-
Object.assign(li.style, {
|
|
3022
|
-
display: "flex",
|
|
3023
|
-
alignItems: "center",
|
|
3024
|
-
padding: "6px 0",
|
|
3025
|
-
borderBottom: "1px solid #2a2a31"
|
|
3026
|
-
});
|
|
3027
|
-
const chk = document.createElement("input");
|
|
3028
|
-
chk.type = "checkbox";
|
|
3029
|
-
chk.checked = todo.done;
|
|
3030
|
-
chk.style.marginRight = "8px";
|
|
3031
|
-
chk.style.pointerEvents = "auto";
|
|
3032
|
-
chk.onchange = () => {
|
|
3033
|
-
todo.done = chk.checked;
|
|
3034
|
-
hooks.emit("node:updated", node);
|
|
3035
|
-
};
|
|
3036
|
-
chk.onmousedown = (e) => e.stopPropagation();
|
|
3037
|
-
const span = document.createElement("span");
|
|
3038
|
-
span.textContent = todo.text;
|
|
3039
|
-
span.style.flex = "1";
|
|
3040
|
-
span.style.textDecoration = todo.done ? "line-through" : "none";
|
|
3041
|
-
span.style.color = todo.done ? "#777" : "#eee";
|
|
3042
|
-
const del = document.createElement("button");
|
|
3043
|
-
del.textContent = "×";
|
|
3044
|
-
Object.assign(del.style, {
|
|
3045
|
-
background: "none",
|
|
3046
|
-
border: "none",
|
|
3047
|
-
color: "#f44",
|
|
3048
|
-
cursor: "pointer",
|
|
3049
|
-
fontSize: "16px",
|
|
3050
|
-
pointerEvents: "auto"
|
|
3051
|
-
});
|
|
3052
|
-
del.onclick = () => {
|
|
3053
|
-
node.state.todos = node.state.todos.filter((t) => t.id !== todo.id);
|
|
3054
|
-
hooks.emit("node:updated", node);
|
|
3055
|
-
};
|
|
3056
|
-
del.onmousedown = (e) => e.stopPropagation();
|
|
3057
|
-
li.append(chk, span, del);
|
|
3058
|
-
list.appendChild(li);
|
|
3059
|
-
});
|
|
3060
|
-
}
|
|
3061
|
-
},
|
|
3062
|
-
onCreate(node) {
|
|
3063
|
-
node.state.todos = [
|
|
3064
|
-
{ id: 1, text: "Welcome to Free Node", done: false },
|
|
3065
|
-
{ id: 2, text: "Try adding a task", done: true }
|
|
3066
|
-
];
|
|
3067
|
-
}
|
|
3068
|
-
});
|
|
3069
|
-
registry.register("math/Add", {
|
|
3070
|
-
title: "Add",
|
|
3071
|
-
size: { w: 140, h: 100 },
|
|
3072
|
-
inputs: [
|
|
3073
|
-
{ name: "exec", portType: "exec" },
|
|
3074
|
-
{ name: "a", portType: "data", datatype: "number" },
|
|
3075
|
-
{ name: "b", portType: "data", datatype: "number" }
|
|
3076
|
-
],
|
|
3077
|
-
outputs: [
|
|
3078
|
-
{ name: "exec", portType: "exec" },
|
|
3079
|
-
{ name: "result", portType: "data", datatype: "number" }
|
|
3080
|
-
],
|
|
3081
|
-
onCreate(node) {
|
|
3082
|
-
node.state.a = 0;
|
|
3083
|
-
node.state.b = 0;
|
|
3084
|
-
},
|
|
3085
|
-
onExecute(node, { getInput, setOutput }) {
|
|
3086
|
-
const a = getInput("a") ?? 0;
|
|
3087
|
-
const b = getInput("b") ?? 0;
|
|
3088
|
-
const result = a + b;
|
|
3089
|
-
console.log("[Add] a:", a, "b:", b, "result:", result);
|
|
3090
|
-
setOutput("result", result);
|
|
3091
|
-
}
|
|
3092
|
-
});
|
|
3093
|
-
registry.register("math/Subtract", {
|
|
3094
|
-
title: "Subtract",
|
|
3095
|
-
size: { w: 140, h: 80 },
|
|
3096
|
-
inputs: [
|
|
3097
|
-
{ name: "a", datatype: "number" },
|
|
3098
|
-
{ name: "b", datatype: "number" }
|
|
3099
|
-
],
|
|
3100
|
-
outputs: [{ name: "result", datatype: "number" }],
|
|
3101
|
-
onExecute(node, { getInput, setOutput }) {
|
|
3102
|
-
const a = getInput("a") ?? 0;
|
|
3103
|
-
const b = getInput("b") ?? 0;
|
|
3104
|
-
setOutput("result", a - b);
|
|
3105
|
-
}
|
|
3106
|
-
});
|
|
3107
|
-
registry.register("math/Multiply", {
|
|
3108
|
-
title: "Multiply",
|
|
3109
|
-
size: { w: 140, h: 100 },
|
|
3110
|
-
inputs: [
|
|
3111
|
-
{ name: "exec", portType: "exec" },
|
|
3112
|
-
{ name: "a", portType: "data", datatype: "number" },
|
|
3113
|
-
{ name: "b", portType: "data", datatype: "number" }
|
|
3114
|
-
],
|
|
3115
|
-
outputs: [
|
|
3116
|
-
{ name: "exec", portType: "exec" },
|
|
3117
|
-
{ name: "result", portType: "data", datatype: "number" }
|
|
3118
|
-
],
|
|
3119
|
-
onExecute(node, { getInput, setOutput }) {
|
|
3120
|
-
const a = getInput("a") ?? 0;
|
|
3121
|
-
const b = getInput("b") ?? 0;
|
|
3122
|
-
const result = a * b;
|
|
3123
|
-
console.log("[Multiply] a:", a, "b:", b, "result:", result);
|
|
3124
|
-
setOutput("result", result);
|
|
3125
|
-
}
|
|
3126
|
-
});
|
|
3127
|
-
registry.register("math/Divide", {
|
|
3128
|
-
title: "Divide",
|
|
3129
|
-
size: { w: 140, h: 80 },
|
|
3130
|
-
inputs: [
|
|
3131
|
-
{ name: "a", datatype: "number" },
|
|
3132
|
-
{ name: "b", datatype: "number" }
|
|
3133
|
-
],
|
|
3134
|
-
outputs: [{ name: "result", datatype: "number" }],
|
|
3135
|
-
onExecute(node, { getInput, setOutput }) {
|
|
3136
|
-
const a = getInput("a") ?? 0;
|
|
3137
|
-
const b = getInput("b") ?? 1;
|
|
3138
|
-
setOutput("result", b !== 0 ? a / b : 0);
|
|
3139
|
-
}
|
|
3140
|
-
});
|
|
3141
|
-
registry.register("logic/AND", {
|
|
3142
|
-
title: "AND",
|
|
3143
|
-
size: { w: 120, h: 100 },
|
|
3144
|
-
inputs: [
|
|
3145
|
-
{ name: "exec", portType: "exec" },
|
|
3146
|
-
{ name: "a", portType: "data", datatype: "boolean" },
|
|
3147
|
-
{ name: "b", portType: "data", datatype: "boolean" }
|
|
3148
|
-
],
|
|
3149
|
-
outputs: [
|
|
3150
|
-
{ name: "exec", portType: "exec" },
|
|
3151
|
-
{ name: "result", portType: "data", datatype: "boolean" }
|
|
3152
|
-
],
|
|
3153
|
-
onExecute(node, { getInput, setOutput }) {
|
|
3154
|
-
const a = getInput("a") ?? false;
|
|
3155
|
-
const b = getInput("b") ?? false;
|
|
3156
|
-
console.log("[AND] Inputs - a:", a, "b:", b);
|
|
3157
|
-
const result = a && b;
|
|
3158
|
-
console.log("[AND] Result:", result);
|
|
3159
|
-
setOutput("result", result);
|
|
3160
|
-
}
|
|
3161
|
-
});
|
|
3162
|
-
registry.register("logic/OR", {
|
|
3163
|
-
title: "OR",
|
|
3164
|
-
size: { w: 120, h: 80 },
|
|
3165
|
-
inputs: [
|
|
3166
|
-
{ name: "a", datatype: "boolean" },
|
|
3167
|
-
{ name: "b", datatype: "boolean" }
|
|
3168
|
-
],
|
|
3169
|
-
outputs: [{ name: "result", datatype: "boolean" }],
|
|
3170
|
-
onExecute(node, { getInput, setOutput }) {
|
|
3171
|
-
const a = getInput("a") ?? false;
|
|
3172
|
-
const b = getInput("b") ?? false;
|
|
3173
|
-
setOutput("result", a || b);
|
|
3174
|
-
}
|
|
3175
|
-
});
|
|
3176
|
-
registry.register("logic/NOT", {
|
|
3177
|
-
title: "NOT",
|
|
3178
|
-
size: { w: 120, h: 70 },
|
|
3179
|
-
inputs: [{ name: "in", datatype: "boolean" }],
|
|
3180
|
-
outputs: [{ name: "out", datatype: "boolean" }],
|
|
3181
|
-
onExecute(node, { getInput, setOutput }) {
|
|
3182
|
-
const val = getInput("in") ?? false;
|
|
3183
|
-
setOutput("out", !val);
|
|
3184
|
-
}
|
|
3185
|
-
});
|
|
3186
|
-
registry.register("value/Number", {
|
|
3187
|
-
title: "Number",
|
|
3188
|
-
size: { w: 140, h: 60 },
|
|
3189
|
-
outputs: [{ name: "value", portType: "data", datatype: "number" }],
|
|
3190
|
-
onCreate(node) {
|
|
3191
|
-
node.state.value = 0;
|
|
3192
|
-
},
|
|
3193
|
-
onExecute(node, { setOutput }) {
|
|
3194
|
-
console.log("[Number] Outputting value:", node.state.value ?? 0);
|
|
3195
|
-
setOutput("value", node.state.value ?? 0);
|
|
3196
|
-
},
|
|
3197
|
-
html: {
|
|
3198
|
-
init(node, el, { header, body }) {
|
|
3199
|
-
el.style.backgroundColor = "#1e1e24";
|
|
3200
|
-
el.style.border = "1px solid #444";
|
|
3201
|
-
el.style.borderRadius = "8px";
|
|
3202
|
-
header.style.backgroundColor = "#2a2a31";
|
|
3203
|
-
header.style.borderBottom = "1px solid #444";
|
|
3204
|
-
header.style.color = "#eee";
|
|
3205
|
-
header.style.fontSize = "12px";
|
|
3206
|
-
header.textContent = "Number";
|
|
3207
|
-
body.style.padding = "12px";
|
|
3208
|
-
body.style.display = "flex";
|
|
3209
|
-
body.style.alignItems = "center";
|
|
3210
|
-
body.style.justifyContent = "center";
|
|
3211
|
-
const input = document.createElement("input");
|
|
3212
|
-
input.type = "number";
|
|
3213
|
-
input.value = node.state.value ?? 0;
|
|
3214
|
-
Object.assign(input.style, {
|
|
3215
|
-
width: "100%",
|
|
3216
|
-
padding: "6px",
|
|
3217
|
-
background: "#141417",
|
|
3218
|
-
border: "1px solid #444",
|
|
3219
|
-
borderRadius: "4px",
|
|
3220
|
-
color: "#fff",
|
|
3221
|
-
fontSize: "14px",
|
|
3222
|
-
textAlign: "center",
|
|
3223
|
-
pointerEvents: "auto"
|
|
3224
|
-
});
|
|
3225
|
-
input.addEventListener("change", (e) => {
|
|
3226
|
-
node.state.value = parseFloat(e.target.value) || 0;
|
|
3227
|
-
});
|
|
3228
|
-
input.addEventListener("mousedown", (e) => e.stopPropagation());
|
|
3229
|
-
input.addEventListener("keydown", (e) => e.stopPropagation());
|
|
3230
|
-
body.appendChild(input);
|
|
3231
|
-
},
|
|
3232
|
-
update(node, el, { header, body, selected }) {
|
|
3233
|
-
el.style.borderColor = selected ? "#6cf" : "#444";
|
|
3234
|
-
header.style.backgroundColor = selected ? "#3a4a5a" : "#2a2a31";
|
|
3235
|
-
}
|
|
3236
|
-
},
|
|
3237
|
-
onDraw(node, { ctx, theme: theme2 }) {
|
|
3238
|
-
const { x, y } = node.computed;
|
|
3239
|
-
ctx.fillStyle = "#8f8";
|
|
3240
|
-
ctx.font = "14px sans-serif";
|
|
3241
|
-
ctx.textAlign = "center";
|
|
3242
|
-
ctx.fillText(String(node.state.value ?? 0), x + 70, y + 42);
|
|
3243
|
-
}
|
|
3244
|
-
});
|
|
3245
|
-
registry.register("value/String", {
|
|
3246
|
-
title: "String",
|
|
3247
|
-
size: { w: 160, h: 60 },
|
|
3248
|
-
outputs: [{ name: "value", datatype: "string" }],
|
|
3249
|
-
onCreate(node) {
|
|
3250
|
-
node.state.value = "Hello";
|
|
3251
|
-
},
|
|
3252
|
-
onExecute(node, { setOutput }) {
|
|
3253
|
-
setOutput("value", node.state.value ?? "");
|
|
3254
|
-
},
|
|
3255
|
-
onDraw(node, { ctx, theme: theme2 }) {
|
|
3256
|
-
const { x, y } = node.computed;
|
|
3257
|
-
ctx.fillStyle = "#8f8";
|
|
3258
|
-
ctx.font = "12px sans-serif";
|
|
3259
|
-
ctx.textAlign = "center";
|
|
3260
|
-
const text = String(node.state.value ?? "");
|
|
3261
|
-
const displayText = text.length > 15 ? text.substring(0, 15) + "..." : text;
|
|
3262
|
-
ctx.fillText(displayText, x + 80, y + 42);
|
|
3263
|
-
}
|
|
3264
|
-
});
|
|
3265
|
-
registry.register("value/Boolean", {
|
|
3266
|
-
title: "Boolean",
|
|
3267
|
-
size: { w: 140, h: 60 },
|
|
3268
|
-
outputs: [{ name: "value", portType: "data", datatype: "boolean" }],
|
|
3269
|
-
onCreate(node) {
|
|
3270
|
-
node.state.value = true;
|
|
3271
|
-
},
|
|
3272
|
-
onExecute(node, { setOutput }) {
|
|
3273
|
-
console.log("[Boolean] Outputting value:", node.state.value ?? false);
|
|
3274
|
-
setOutput("value", node.state.value ?? false);
|
|
3275
|
-
},
|
|
3276
|
-
onDraw(node, { ctx, theme: theme2 }) {
|
|
3277
|
-
const { x, y } = node.computed;
|
|
3278
|
-
ctx.fillStyle = node.state.value ? "#8f8" : "#f88";
|
|
3279
|
-
ctx.font = "14px sans-serif";
|
|
3280
|
-
ctx.textAlign = "center";
|
|
3281
|
-
ctx.fillText(String(node.state.value), x + 70, y + 42);
|
|
3282
|
-
}
|
|
3283
|
-
});
|
|
3284
|
-
registry.register("util/Print", {
|
|
3285
|
-
title: "Print",
|
|
3286
|
-
size: { w: 140, h: 80 },
|
|
3287
|
-
inputs: [
|
|
3288
|
-
{ name: "exec", portType: "exec" },
|
|
3289
|
-
{ name: "value", portType: "data", datatype: "any" }
|
|
3290
|
-
],
|
|
3291
|
-
onCreate(node) {
|
|
3292
|
-
node.state.lastValue = null;
|
|
3293
|
-
},
|
|
3294
|
-
onExecute(node, { getInput }) {
|
|
3295
|
-
const val = getInput("value");
|
|
3296
|
-
if (val !== node.state.lastValue) {
|
|
3297
|
-
console.log("[Print]", val);
|
|
3298
|
-
node.state.lastValue = val;
|
|
3299
|
-
}
|
|
3300
|
-
}
|
|
3301
|
-
});
|
|
3302
|
-
registry.register("util/Watch", {
|
|
3303
|
-
title: "Watch",
|
|
3304
|
-
size: { w: 180, h: 110 },
|
|
3305
|
-
inputs: [
|
|
3306
|
-
{ name: "exec", portType: "exec" },
|
|
3307
|
-
{ name: "value", portType: "data", datatype: "any" }
|
|
3308
|
-
],
|
|
3309
|
-
outputs: [
|
|
3310
|
-
{ name: "exec", portType: "exec" },
|
|
3311
|
-
{ name: "value", portType: "data", datatype: "any" }
|
|
3312
|
-
],
|
|
3313
|
-
onCreate(node) {
|
|
3314
|
-
node.state.displayValue = "---";
|
|
3315
|
-
},
|
|
3316
|
-
onExecute(node, { getInput, setOutput }) {
|
|
3317
|
-
const val = getInput("value");
|
|
3318
|
-
console.log("[Watch] onExecute called, value:", val);
|
|
3319
|
-
node.state.displayValue = String(val ?? "---");
|
|
3320
|
-
setOutput("value", val);
|
|
3321
|
-
},
|
|
3322
|
-
onDraw(node, { ctx, theme: theme2 }) {
|
|
3323
|
-
const { x, y } = node.computed;
|
|
3324
|
-
ctx.fillStyle = "#fa3";
|
|
3325
|
-
ctx.font = "11px monospace";
|
|
3326
|
-
ctx.textAlign = "left";
|
|
3327
|
-
const text = String(node.state.displayValue ?? "---");
|
|
3328
|
-
const displayText = text.length > 20 ? text.substring(0, 20) + "..." : text;
|
|
3329
|
-
ctx.fillText(displayText, x + 8, y + 50);
|
|
3330
|
-
}
|
|
3331
|
-
});
|
|
3332
|
-
registry.register("util/Timer", {
|
|
3333
|
-
title: "Timer",
|
|
3334
|
-
size: { w: 140, h: 60 },
|
|
3335
|
-
outputs: [{ name: "time", datatype: "number" }],
|
|
3336
|
-
onCreate(node) {
|
|
3337
|
-
node.state.startTime = performance.now();
|
|
3338
|
-
},
|
|
3339
|
-
onExecute(node, { setOutput }) {
|
|
3340
|
-
const elapsed = (performance.now() - (node.state.startTime ?? 0)) / 1e3;
|
|
3341
|
-
setOutput("time", elapsed.toFixed(2));
|
|
3342
|
-
}
|
|
3343
|
-
});
|
|
3344
|
-
registry.register("util/Trigger", {
|
|
3345
|
-
title: "Trigger",
|
|
3346
|
-
size: { w: 140, h: 80 },
|
|
3347
|
-
outputs: [{ name: "exec", portType: "exec" }],
|
|
3348
|
-
// Changed to exec port
|
|
3349
|
-
html: {
|
|
3350
|
-
init(node, el, { header, body }) {
|
|
3351
|
-
el.style.backgroundColor = "#1e1e24";
|
|
3352
|
-
el.style.border = "1px solid #444";
|
|
3353
|
-
el.style.borderRadius = "8px";
|
|
3354
|
-
header.style.backgroundColor = "#2a2a31";
|
|
3355
|
-
header.style.borderBottom = "1px solid #444";
|
|
3356
|
-
header.style.color = "#eee";
|
|
3357
|
-
header.style.fontSize = "12px";
|
|
3358
|
-
header.textContent = "Trigger";
|
|
3359
|
-
body.style.padding = "12px";
|
|
3360
|
-
body.style.display = "flex";
|
|
3361
|
-
body.style.alignItems = "center";
|
|
3362
|
-
body.style.justifyContent = "center";
|
|
3363
|
-
const button = document.createElement("button");
|
|
3364
|
-
button.textContent = "Fire!";
|
|
3365
|
-
Object.assign(button.style, {
|
|
3366
|
-
padding: "8px 16px",
|
|
3367
|
-
background: "#4a9eff",
|
|
3368
|
-
border: "none",
|
|
3369
|
-
borderRadius: "4px",
|
|
3370
|
-
color: "#fff",
|
|
3371
|
-
fontWeight: "bold",
|
|
3372
|
-
cursor: "pointer",
|
|
3373
|
-
pointerEvents: "auto",
|
|
3374
|
-
transition: "background 0.2s"
|
|
3375
|
-
});
|
|
3376
|
-
button.addEventListener("mousedown", (e) => {
|
|
3377
|
-
e.stopPropagation();
|
|
3378
|
-
button.style.background = "#2a7ede";
|
|
3379
|
-
});
|
|
3380
|
-
button.addEventListener("mouseup", () => {
|
|
3381
|
-
button.style.background = "#4a9eff";
|
|
3382
|
-
});
|
|
3383
|
-
button.addEventListener("click", (e) => {
|
|
3384
|
-
e.stopPropagation();
|
|
3385
|
-
node.state.triggered = true;
|
|
3386
|
-
console.log("[Trigger] Button clicked!");
|
|
3387
|
-
if (node.__runnerRef && node.__controllerRef) {
|
|
3388
|
-
console.log("[Trigger] Runner and controller found");
|
|
3389
|
-
const runner2 = node.__runnerRef;
|
|
3390
|
-
const controller2 = node.__controllerRef;
|
|
3391
|
-
const graph2 = controller2.graph;
|
|
3392
|
-
console.log("[Trigger] Calling runner.runOnce with node.id:", node.id);
|
|
3393
|
-
const result = runner2.runOnce(node.id, 0);
|
|
3394
|
-
const connectedEdges = result.connectedEdges;
|
|
3395
|
-
const startTime = performance.now();
|
|
3396
|
-
const animationDuration = 500;
|
|
3397
|
-
const animate = () => {
|
|
3398
|
-
var _a;
|
|
3399
|
-
const elapsed = performance.now() - startTime;
|
|
3400
|
-
if (elapsed < animationDuration) {
|
|
3401
|
-
controller2.renderer.draw(graph2, {
|
|
3402
|
-
selection: controller2.selection,
|
|
3403
|
-
tempEdge: null,
|
|
3404
|
-
running: true,
|
|
3405
|
-
time: performance.now(),
|
|
3406
|
-
dt: 0,
|
|
3407
|
-
activeEdges: connectedEdges
|
|
3408
|
-
// Only animate connected edges
|
|
3409
|
-
});
|
|
3410
|
-
(_a = controller2.htmlOverlay) == null ? void 0 : _a.draw(graph2, controller2.selection);
|
|
3411
|
-
requestAnimationFrame(animate);
|
|
3412
|
-
} else {
|
|
3413
|
-
controller2.render();
|
|
3414
|
-
node.state.triggered = false;
|
|
3415
|
-
}
|
|
3416
|
-
};
|
|
3417
|
-
animate();
|
|
3418
|
-
}
|
|
3419
|
-
});
|
|
3420
|
-
body.appendChild(button);
|
|
3421
|
-
},
|
|
3422
|
-
update(node, el, { header, body, selected }) {
|
|
3423
|
-
el.style.borderColor = selected ? "#6cf" : "#444";
|
|
3424
|
-
header.style.backgroundColor = selected ? "#3a4a5a" : "#2a2a31";
|
|
3425
|
-
}
|
|
3426
|
-
},
|
|
3427
|
-
onCreate(node) {
|
|
3428
|
-
node.state.triggered = false;
|
|
3429
|
-
},
|
|
3430
|
-
onExecute(node, { setOutput }) {
|
|
3431
|
-
console.log("[Trigger] Outputting triggered:", node.state.triggered);
|
|
3432
|
-
setOutput("triggered", node.state.triggered);
|
|
3433
|
-
}
|
|
3434
|
-
});
|
|
3435
|
-
registry.register("core/Group", {
|
|
3436
|
-
title: "Group",
|
|
3437
|
-
size: { w: 240, h: 160 },
|
|
3438
|
-
onDraw(node, { ctx, theme: theme2 }) {
|
|
3439
|
-
const { x, y, w, h } = node.computed;
|
|
3440
|
-
const headerH = 24;
|
|
3441
|
-
const color = node.state.color || "#39424e";
|
|
3442
|
-
const bgAlpha = 0.5;
|
|
3443
|
-
const textColor = theme2.text || "#e9e9ef";
|
|
3444
|
-
const rgba = (hex, a) => {
|
|
3445
|
-
const c = hex.replace("#", "");
|
|
3446
|
-
const n = parseInt(
|
|
3447
|
-
c.length === 3 ? c.split("").map((x2) => x2 + x2).join("") : c,
|
|
3448
|
-
16
|
|
3449
|
-
);
|
|
3450
|
-
const r = n >> 16 & 255, g = n >> 8 & 255, b = n & 255;
|
|
3451
|
-
return `rgba(${r},${g},${b},${a})`;
|
|
3452
|
-
};
|
|
3453
|
-
const roundRect2 = (ctx2, x2, y2, w2, h2, r) => {
|
|
3454
|
-
if (w2 < 2 * r) r = w2 / 2;
|
|
3455
|
-
if (h2 < 2 * r) r = h2 / 2;
|
|
3456
|
-
ctx2.beginPath();
|
|
3457
|
-
ctx2.moveTo(x2 + r, y2);
|
|
3458
|
-
ctx2.arcTo(x2 + w2, y2, x2 + w2, y2 + h2, r);
|
|
3459
|
-
ctx2.arcTo(x2 + w2, y2 + h2, x2, y2 + h2, r);
|
|
3460
|
-
ctx2.arcTo(x2, y2 + h2, x2, y2, r);
|
|
3461
|
-
ctx2.arcTo(x2, y2, x2 + w2, y2, r);
|
|
3462
|
-
ctx2.closePath();
|
|
3463
|
-
};
|
|
3464
|
-
ctx.fillStyle = rgba(color, bgAlpha);
|
|
3465
|
-
roundRect2(ctx, x, y, w, h, 10);
|
|
3466
|
-
ctx.fill();
|
|
3467
|
-
ctx.fillStyle = rgba(color, 0.3);
|
|
3468
|
-
ctx.beginPath();
|
|
3469
|
-
ctx.roundRect(x, y, w, headerH, [10, 10, 0, 0]);
|
|
3470
|
-
ctx.fill();
|
|
3471
|
-
ctx.fillStyle = textColor;
|
|
3472
|
-
ctx.font = "600 13px system-ui";
|
|
3473
|
-
ctx.textBaseline = "top";
|
|
3474
|
-
ctx.fillText(node.title, x + 12, y + 6);
|
|
3475
|
-
}
|
|
3476
|
-
});
|
|
3477
|
-
function setupDefaultContextMenu(contextMenu2, { controller: controller2, graph: graph2, hooks: hooks2 }) {
|
|
3478
|
-
const nodeTypes = [];
|
|
3479
|
-
for (const [key, typeDef] of graph2.registry.types.entries()) {
|
|
3480
|
-
nodeTypes.push({
|
|
3481
|
-
id: `add-${key}`,
|
|
3482
|
-
label: typeDef.title || key,
|
|
3483
|
-
action: () => {
|
|
3484
|
-
const worldPos = contextMenu2.worldPosition || { x: 100, y: 100 };
|
|
3485
|
-
const node = graph2.addNode(key, {
|
|
3486
|
-
x: worldPos.x,
|
|
3487
|
-
y: worldPos.y
|
|
3488
|
-
});
|
|
3489
|
-
hooks2 == null ? void 0 : hooks2.emit("node:updated", node);
|
|
3490
|
-
controller2.render();
|
|
3491
|
-
}
|
|
3492
|
-
});
|
|
3493
3116
|
}
|
|
3494
|
-
contextMenu2.addItem("add-node", "Add Node", {
|
|
3495
|
-
condition: (target2) => !target2,
|
|
3496
|
-
submenu: nodeTypes,
|
|
3497
|
-
order: 5
|
|
3498
|
-
});
|
|
3499
|
-
contextMenu2.addItem("delete-node", "Delete Node", {
|
|
3500
|
-
condition: (target2) => target2 && target2.type !== "core/Group",
|
|
3501
|
-
action: (target2) => {
|
|
3502
|
-
const cmd = RemoveNodeCmd(graph2, target2);
|
|
3503
|
-
controller2.stack.exec(cmd);
|
|
3504
|
-
hooks2 == null ? void 0 : hooks2.emit("node:updated", target2);
|
|
3505
|
-
},
|
|
3506
|
-
order: 10
|
|
3507
|
-
});
|
|
3508
|
-
const colors = [
|
|
3509
|
-
{ name: "Default", color: "#39424e" },
|
|
3510
|
-
{ name: "Slate", color: "#4a5568" },
|
|
3511
|
-
{ name: "Gray", color: "#2d3748" },
|
|
3512
|
-
{ name: "Blue", color: "#1a365d" },
|
|
3513
|
-
{ name: "Green", color: "#22543d" },
|
|
3514
|
-
{ name: "Red", color: "#742a2a" },
|
|
3515
|
-
{ name: "Purple", color: "#44337a" }
|
|
3516
|
-
];
|
|
3517
|
-
contextMenu2.addItem("change-group-color", "Change Color", {
|
|
3518
|
-
condition: (target2) => target2 && target2.type === "core/Group",
|
|
3519
|
-
submenu: colors.map((colorInfo) => ({
|
|
3520
|
-
id: `color-${colorInfo.color}`,
|
|
3521
|
-
label: colorInfo.name,
|
|
3522
|
-
color: colorInfo.color,
|
|
3523
|
-
action: (target2) => {
|
|
3524
|
-
const currentColor = target2.state.color || "#39424e";
|
|
3525
|
-
const cmd = ChangeGroupColorCmd(target2, currentColor, colorInfo.color);
|
|
3526
|
-
controller2.stack.exec(cmd);
|
|
3527
|
-
hooks2 == null ? void 0 : hooks2.emit("node:updated", target2);
|
|
3528
|
-
}
|
|
3529
|
-
})),
|
|
3530
|
-
order: 20
|
|
3531
|
-
});
|
|
3532
|
-
contextMenu2.addItem("delete-group", "Delete Group", {
|
|
3533
|
-
condition: (target2) => target2 && target2.type === "core/Group",
|
|
3534
|
-
action: (target2) => {
|
|
3535
|
-
const cmd = RemoveNodeCmd(graph2, target2);
|
|
3536
|
-
controller2.stack.exec(cmd);
|
|
3537
|
-
hooks2 == null ? void 0 : hooks2.emit("node:updated", target2);
|
|
3538
|
-
},
|
|
3539
|
-
order: 20
|
|
3540
|
-
});
|
|
3541
3117
|
}
|
|
3542
|
-
setupDefaultContextMenu(contextMenu, { controller, graph, hooks });
|
|
3543
3118
|
renderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
3119
|
+
edgeRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
3544
3120
|
portRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
3545
3121
|
controller.render();
|
|
3546
3122
|
const ro = new ResizeObserver(() => {
|
|
3547
3123
|
renderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
3124
|
+
edgeRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
3548
3125
|
portRenderer.resize(canvas.clientWidth, canvas.clientHeight);
|
|
3549
3126
|
controller.render();
|
|
3550
3127
|
});
|