html-overlay-node 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/example.json +9 -9
- package/dist/html-overlay-node.es.js +351 -78
- package/dist/html-overlay-node.es.js.map +1 -1
- package/dist/html-overlay-node.umd.js +1 -1
- package/dist/html-overlay-node.umd.js.map +1 -1
- package/dist/img/favicon.svg +1 -0
- package/index.css +45 -0
- package/package.json +1 -1
- package/readme.md +143 -134
- package/src/core/Graph.js +13 -8
- package/src/core/Runner.js +359 -201
- package/src/index.js +12 -26
- package/src/interact/Controller.js +25 -3
- package/src/nodes/math.js +2 -0
- package/src/nodes/util.js +241 -176
- package/src/nodes/value.js +3 -2
- package/src/render/CanvasRenderer.js +884 -784
- package/src/render/HtmlOverlay.js +64 -2
- package/src/ui/HelpOverlay.js +1 -1
|
@@ -535,10 +535,13 @@ class Graph {
|
|
|
535
535
|
const outCount = ((_b = def.outputs) == null ? void 0 : _b.length) || 0;
|
|
536
536
|
const maxPorts = Math.max(inCount, outCount);
|
|
537
537
|
const headerHeight = 26;
|
|
538
|
-
const padding = 8;
|
|
539
538
|
const portSpacing = 20;
|
|
539
|
+
if (def.html) {
|
|
540
|
+
const lastPortBottom = maxPorts > 0 ? 50 + (maxPorts - 1) * portSpacing : 26;
|
|
541
|
+
return Math.max(lastPortBottom + 50, 90);
|
|
542
|
+
}
|
|
543
|
+
const padding = 8;
|
|
540
544
|
let h = headerHeight + padding + maxPorts * portSpacing + padding;
|
|
541
|
-
if (def.html) h += 16;
|
|
542
545
|
return Math.max(h, 40);
|
|
543
546
|
}
|
|
544
547
|
}
|
|
@@ -590,7 +593,8 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
590
593
|
port: "#4f46e5",
|
|
591
594
|
portExec: "#10b981",
|
|
592
595
|
edge: "rgba(255, 255, 255, 0.12)",
|
|
593
|
-
edgeActive: "#
|
|
596
|
+
edgeActive: "#34c38f",
|
|
597
|
+
// green for active edge animation
|
|
594
598
|
accent: "#6366f1",
|
|
595
599
|
accentBright: "#818cf8",
|
|
596
600
|
accentGlow: "rgba(99, 102, 241, 0.25)"
|
|
@@ -669,12 +673,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
669
673
|
ctx.closePath();
|
|
670
674
|
ctx.fill();
|
|
671
675
|
}
|
|
672
|
-
_drawScreenText(text, lx, ly, {
|
|
673
|
-
fontPx = 11,
|
|
674
|
-
color = this.theme.text,
|
|
675
|
-
align = "left",
|
|
676
|
-
baseline = "alphabetic"
|
|
677
|
-
} = {}) {
|
|
676
|
+
_drawScreenText(text, lx, ly, { fontPx = 11, color = this.theme.text, align = "left", baseline = "alphabetic" } = {}) {
|
|
678
677
|
const { ctx } = this;
|
|
679
678
|
const { x: sx, y: sy } = this.worldToScreen(lx, ly);
|
|
680
679
|
ctx.save();
|
|
@@ -731,8 +730,12 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
731
730
|
selection = /* @__PURE__ */ new Set(),
|
|
732
731
|
tempEdge = null,
|
|
733
732
|
time = performance.now(),
|
|
733
|
+
activeNodes = /* @__PURE__ */ new Set(),
|
|
734
|
+
// Now explicitly passing active nodes
|
|
734
735
|
activeEdges = /* @__PURE__ */ new Set(),
|
|
735
|
-
|
|
736
|
+
activeEdgeTimes = /* @__PURE__ */ new Map(),
|
|
737
|
+
drawEdges = true,
|
|
738
|
+
loopActiveEdges = false
|
|
736
739
|
} = {}) {
|
|
737
740
|
var _a, _b, _c, _d;
|
|
738
741
|
graph.updateWorldTransforms();
|
|
@@ -749,7 +752,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
749
752
|
}
|
|
750
753
|
}
|
|
751
754
|
if (drawEdges) {
|
|
752
|
-
ctx.lineWidth =
|
|
755
|
+
ctx.lineWidth = 2.5 / this.scale;
|
|
753
756
|
for (const e of graph.edges.values()) {
|
|
754
757
|
const isActive = activeEdges && activeEdges.has(e.id);
|
|
755
758
|
if (isActive) {
|
|
@@ -757,11 +760,14 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
757
760
|
ctx.shadowColor = this.theme.edgeActive;
|
|
758
761
|
ctx.shadowBlur = 8 / this.scale;
|
|
759
762
|
ctx.strokeStyle = this.theme.edgeActive;
|
|
760
|
-
ctx.lineWidth =
|
|
763
|
+
ctx.lineWidth = 3 / this.scale;
|
|
761
764
|
ctx.setLineDash([]);
|
|
762
765
|
this._drawEdge(graph, e);
|
|
763
766
|
ctx.restore();
|
|
764
|
-
|
|
767
|
+
(activeEdgeTimes == null ? void 0 : activeEdgeTimes.get(e.id)) ?? time;
|
|
768
|
+
const flowSpeed = this.theme.flowSpeed || 150;
|
|
769
|
+
edgeLen / flowSpeed * 1e3;
|
|
770
|
+
const dotT = loopActiveEdges ? time / 1e3 * (flowSpeed / edgeLen) % 1 : time / 1e3 * 1.2 % 1;
|
|
765
771
|
const dotPos = this._getEdgeDotPosition(graph, e, dotT);
|
|
766
772
|
if (dotPos) {
|
|
767
773
|
ctx.save();
|
|
@@ -776,7 +782,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
776
782
|
} else {
|
|
777
783
|
ctx.setLineDash([]);
|
|
778
784
|
ctx.strokeStyle = theme.edge;
|
|
779
|
-
ctx.lineWidth =
|
|
785
|
+
ctx.lineWidth = 2.5 / this.scale;
|
|
780
786
|
this._drawEdge(graph, e);
|
|
781
787
|
}
|
|
782
788
|
}
|
|
@@ -787,16 +793,22 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
787
793
|
const prevDash = this.ctx.getLineDash();
|
|
788
794
|
this.ctx.setLineDash([5 / this.scale, 5 / this.scale]);
|
|
789
795
|
this.ctx.strokeStyle = this._rgba(this.theme.accentBright, 0.7);
|
|
790
|
-
this.ctx.lineWidth =
|
|
796
|
+
this.ctx.lineWidth = 2.5 / this.scale;
|
|
791
797
|
let ptsForArrow = null;
|
|
792
798
|
if (this.edgeStyle === "line") {
|
|
793
799
|
this._drawLine(a.x, a.y, b.x, b.y);
|
|
794
|
-
ptsForArrow = [
|
|
800
|
+
ptsForArrow = [
|
|
801
|
+
{ x: a.x, y: a.y },
|
|
802
|
+
{ x: b.x, y: b.y }
|
|
803
|
+
];
|
|
795
804
|
} else if (this.edgeStyle === "orthogonal") {
|
|
796
805
|
ptsForArrow = this._drawOrthogonal(a.x, a.y, b.x, b.y);
|
|
797
806
|
} else {
|
|
798
807
|
this._drawCurve(a.x, a.y, b.x, b.y);
|
|
799
|
-
ptsForArrow = [
|
|
808
|
+
ptsForArrow = [
|
|
809
|
+
{ x: a.x, y: a.y },
|
|
810
|
+
{ x: b.x, y: b.y }
|
|
811
|
+
];
|
|
800
812
|
}
|
|
801
813
|
this.ctx.setLineDash(prevDash);
|
|
802
814
|
if (ptsForArrow && ptsForArrow.length >= 2) {
|
|
@@ -820,6 +832,12 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
820
832
|
}
|
|
821
833
|
}
|
|
822
834
|
}
|
|
835
|
+
if (activeNodes.size > 0) {
|
|
836
|
+
for (const nodeId of activeNodes) {
|
|
837
|
+
const node = graph.nodes.get(nodeId);
|
|
838
|
+
if (node) this._drawActiveNodeBorder(node, time);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
823
841
|
this._resetTransform();
|
|
824
842
|
}
|
|
825
843
|
_rgba(hex, a) {
|
|
@@ -841,11 +859,11 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
841
859
|
const categoryColor = node.color || (typeDef == null ? void 0 : typeDef.color) || theme.accent;
|
|
842
860
|
if (selected) {
|
|
843
861
|
ctx.save();
|
|
844
|
-
ctx.shadowColor =
|
|
845
|
-
ctx.shadowBlur =
|
|
846
|
-
ctx.strokeStyle =
|
|
847
|
-
ctx.lineWidth =
|
|
848
|
-
const pad =
|
|
862
|
+
ctx.shadowColor = "rgba(255,255,255,0.3)";
|
|
863
|
+
ctx.shadowBlur = 8 / this.scale;
|
|
864
|
+
ctx.strokeStyle = "#ffffff";
|
|
865
|
+
ctx.lineWidth = 1.5 / this.scale;
|
|
866
|
+
const pad = 8 / this.scale;
|
|
849
867
|
roundRect(ctx, x - pad, y - pad, w + pad * 2, h + pad * 2, r + pad);
|
|
850
868
|
ctx.stroke();
|
|
851
869
|
ctx.restore();
|
|
@@ -859,7 +877,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
859
877
|
ctx.fill();
|
|
860
878
|
ctx.restore();
|
|
861
879
|
ctx.fillStyle = theme.node;
|
|
862
|
-
ctx.strokeStyle = selected ?
|
|
880
|
+
ctx.strokeStyle = selected ? "rgba(255,255,255,0.4)" : theme.nodeBorder;
|
|
863
881
|
ctx.lineWidth = 1 / this.scale;
|
|
864
882
|
roundRect(ctx, x, y, w, h, r);
|
|
865
883
|
ctx.fill();
|
|
@@ -873,7 +891,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
873
891
|
ctx.globalAlpha = 0.25;
|
|
874
892
|
ctx.fillRect(x, y, w, headerH);
|
|
875
893
|
ctx.restore();
|
|
876
|
-
ctx.strokeStyle = selected ?
|
|
894
|
+
ctx.strokeStyle = selected ? "rgba(255,255,255,0.2)" : this._rgba(theme.nodeBorder, 0.6);
|
|
877
895
|
ctx.lineWidth = 1 / this.scale;
|
|
878
896
|
ctx.beginPath();
|
|
879
897
|
ctx.moveTo(x, y + headerH);
|
|
@@ -919,6 +937,43 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
919
937
|
}
|
|
920
938
|
});
|
|
921
939
|
}
|
|
940
|
+
_drawActiveNodeBorder(node, time) {
|
|
941
|
+
const { ctx, theme } = this;
|
|
942
|
+
const { x, y, w, h } = node.computed;
|
|
943
|
+
const r = node.radius || 12;
|
|
944
|
+
const speed = 30;
|
|
945
|
+
const dashLen = 6;
|
|
946
|
+
const gapLen = 6;
|
|
947
|
+
ctx.save();
|
|
948
|
+
ctx.shadowColor = theme.accentBright || "#7080d8";
|
|
949
|
+
ctx.shadowBlur = (12 + Math.sin(time / 200) * 4) / this.scale;
|
|
950
|
+
ctx.strokeStyle = theme.accentBright || "#7080d8";
|
|
951
|
+
ctx.lineWidth = 3 / this.scale;
|
|
952
|
+
ctx.setLineDash([dashLen / this.scale, gapLen / this.scale]);
|
|
953
|
+
ctx.lineDashOffset = -(time / 1e3) * speed / this.scale;
|
|
954
|
+
ctx.beginPath();
|
|
955
|
+
const pad = 2 / this.scale;
|
|
956
|
+
this._roundRect(ctx, x - pad, y - pad, w + pad * 2, h + pad * 2, r + pad);
|
|
957
|
+
ctx.stroke();
|
|
958
|
+
ctx.restore();
|
|
959
|
+
}
|
|
960
|
+
// Internal helper for rounded rectangles if not using the browser's native one
|
|
961
|
+
_roundRect(ctx, x, y, w, h, r) {
|
|
962
|
+
if (typeof r === "number") {
|
|
963
|
+
r = { tl: r, tr: r, br: r, bl: r };
|
|
964
|
+
}
|
|
965
|
+
ctx.beginPath();
|
|
966
|
+
ctx.moveTo(x + r.tl, y);
|
|
967
|
+
ctx.lineTo(x + w - r.tr, y);
|
|
968
|
+
ctx.quadraticCurveTo(x + w, y, x + w, y + r.tr);
|
|
969
|
+
ctx.lineTo(x + w, y + h - r.br);
|
|
970
|
+
ctx.quadraticCurveTo(x + w, y + h, x + w - r.br, y + h);
|
|
971
|
+
ctx.lineTo(x + r.bl, y + h);
|
|
972
|
+
ctx.quadraticCurveTo(x, y + h, x, y + h - r.bl);
|
|
973
|
+
ctx.lineTo(x, y + r.tl);
|
|
974
|
+
ctx.quadraticCurveTo(x, y, x + r.tl, y);
|
|
975
|
+
ctx.closePath();
|
|
976
|
+
}
|
|
922
977
|
_drawPortShape(cx, cy, portType) {
|
|
923
978
|
const { ctx, theme } = this;
|
|
924
979
|
if (portType === "exec") {
|
|
@@ -966,14 +1021,14 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
966
1021
|
}
|
|
967
1022
|
/** Selection border for HTML overlay nodes, drawn on the edge canvas */
|
|
968
1023
|
_drawHtmlSelectionBorder(node) {
|
|
969
|
-
const { ctx
|
|
1024
|
+
const { ctx } = this;
|
|
970
1025
|
const { x, y, w, h } = node.computed;
|
|
971
1026
|
const r = 2;
|
|
972
|
-
const pad =
|
|
1027
|
+
const pad = 2.5 / this.scale;
|
|
973
1028
|
ctx.save();
|
|
974
|
-
ctx.shadowColor =
|
|
975
|
-
ctx.shadowBlur =
|
|
976
|
-
ctx.strokeStyle =
|
|
1029
|
+
ctx.shadowColor = "rgba(255,255,255,0.3)";
|
|
1030
|
+
ctx.shadowBlur = 8 / this.scale;
|
|
1031
|
+
ctx.strokeStyle = "#ffffff";
|
|
977
1032
|
ctx.lineWidth = 1.5 / this.scale;
|
|
978
1033
|
roundRect(ctx, x - pad, y - pad, w + pad * 2, h + pad * 2, r);
|
|
979
1034
|
ctx.stroke();
|
|
@@ -981,24 +1036,41 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
981
1036
|
}
|
|
982
1037
|
/** Rotating dashed border drawn on the edge canvas for executing nodes */
|
|
983
1038
|
_drawActiveNodeBorder(node, time) {
|
|
984
|
-
const { ctx
|
|
1039
|
+
const { ctx } = this;
|
|
985
1040
|
const { x, y, w, h } = node.computed;
|
|
986
1041
|
const r = 2;
|
|
987
|
-
const pad =
|
|
1042
|
+
const pad = 8 / this.scale;
|
|
988
1043
|
const dashLen = 8 / this.scale;
|
|
989
1044
|
const gapLen = 6 / this.scale;
|
|
990
1045
|
const offset = -(time / 1e3) * (50 / this.scale);
|
|
991
1046
|
ctx.save();
|
|
992
1047
|
ctx.setLineDash([dashLen, gapLen]);
|
|
993
1048
|
ctx.lineDashOffset = offset;
|
|
994
|
-
ctx.strokeStyle =
|
|
995
|
-
ctx.lineWidth =
|
|
996
|
-
ctx.shadowColor =
|
|
997
|
-
ctx.shadowBlur =
|
|
1049
|
+
ctx.strokeStyle = "rgba(74,176,217,0.9)";
|
|
1050
|
+
ctx.lineWidth = 2 / this.scale;
|
|
1051
|
+
ctx.shadowColor = "#4ab0d9";
|
|
1052
|
+
ctx.shadowBlur = 6 / this.scale;
|
|
998
1053
|
roundRect(ctx, x - pad, y - pad, w + pad * 2, h + pad * 2, r + pad);
|
|
999
1054
|
ctx.stroke();
|
|
1000
1055
|
ctx.restore();
|
|
1001
1056
|
}
|
|
1057
|
+
/** Approximate arc length of an edge in world coordinates */
|
|
1058
|
+
_getEdgeLength(graph, e) {
|
|
1059
|
+
const from = graph.nodes.get(e.fromNode);
|
|
1060
|
+
const to = graph.nodes.get(e.toNode);
|
|
1061
|
+
if (!from || !to) return 200;
|
|
1062
|
+
const iOut = from.outputs.findIndex((p) => p.id === e.fromPort);
|
|
1063
|
+
const iIn = to.inputs.findIndex((p) => p.id === e.toPort);
|
|
1064
|
+
const pr1 = portRect(from, null, iOut, "out");
|
|
1065
|
+
const pr2 = portRect(to, null, iIn, "in");
|
|
1066
|
+
const x1 = pr1.x + pr1.w / 2, y1 = pr1.y + pr1.h / 2;
|
|
1067
|
+
const x2 = pr2.x + pr2.w / 2, y2 = pr2.y + pr2.h / 2;
|
|
1068
|
+
if (this.edgeStyle === "orthogonal") {
|
|
1069
|
+
return Math.abs(x2 - x1) + Math.abs(y2 - y1);
|
|
1070
|
+
}
|
|
1071
|
+
const chord = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
|
1072
|
+
return this.edgeStyle === "bezier" ? chord * 1.4 : chord;
|
|
1073
|
+
}
|
|
1002
1074
|
_drawEdge(graph, e) {
|
|
1003
1075
|
const from = graph.nodes.get(e.fromNode);
|
|
1004
1076
|
const to = graph.nodes.get(e.toNode);
|
|
@@ -1088,7 +1160,8 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
1088
1160
|
activeNodes = /* @__PURE__ */ new Set(),
|
|
1089
1161
|
selection = /* @__PURE__ */ new Set(),
|
|
1090
1162
|
time = performance.now(),
|
|
1091
|
-
tempEdge = null
|
|
1163
|
+
tempEdge = null,
|
|
1164
|
+
loopActiveEdges = false
|
|
1092
1165
|
} = {}) {
|
|
1093
1166
|
var _a, _b;
|
|
1094
1167
|
this._resetTransform();
|
|
@@ -1102,12 +1175,16 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
1102
1175
|
ctx.shadowColor = theme.edgeActive;
|
|
1103
1176
|
ctx.shadowBlur = 6 / this.scale;
|
|
1104
1177
|
ctx.strokeStyle = theme.edgeActive;
|
|
1105
|
-
ctx.lineWidth =
|
|
1178
|
+
ctx.lineWidth = 3 / this.scale;
|
|
1106
1179
|
ctx.setLineDash([]);
|
|
1107
1180
|
this._drawEdge(graph, e);
|
|
1108
1181
|
ctx.restore();
|
|
1182
|
+
const flowSpeed = this.theme.flowSpeed || 150;
|
|
1109
1183
|
const activationTime = activeEdgeTimes.get(e.id) ?? time;
|
|
1110
|
-
const
|
|
1184
|
+
const edgeLen2 = Math.max(50, this._getEdgeLength(graph, e));
|
|
1185
|
+
const duration = edgeLen2 / flowSpeed * 1e3;
|
|
1186
|
+
const rawT = (time - activationTime) / duration;
|
|
1187
|
+
const dotT = loopActiveEdges ? time / 1e3 * (flowSpeed / edgeLen2) % 1 : Math.min(1, rawT);
|
|
1111
1188
|
const dotPos = this._getEdgeDotPosition(graph, e, dotT);
|
|
1112
1189
|
if (dotPos) {
|
|
1113
1190
|
ctx.save();
|
|
@@ -1122,7 +1199,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
1122
1199
|
} else {
|
|
1123
1200
|
ctx.setLineDash([]);
|
|
1124
1201
|
ctx.strokeStyle = theme.edge;
|
|
1125
|
-
ctx.lineWidth =
|
|
1202
|
+
ctx.lineWidth = 2.5 / this.scale;
|
|
1126
1203
|
this._drawEdge(graph, e);
|
|
1127
1204
|
}
|
|
1128
1205
|
}
|
|
@@ -1144,16 +1221,22 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
1144
1221
|
const prevDash = this.ctx.getLineDash();
|
|
1145
1222
|
this.ctx.setLineDash([5 / this.scale, 5 / this.scale]);
|
|
1146
1223
|
this.ctx.strokeStyle = this._rgba(this.theme.accentBright, 0.7);
|
|
1147
|
-
this.ctx.lineWidth =
|
|
1224
|
+
this.ctx.lineWidth = 2.5 / this.scale;
|
|
1148
1225
|
let ptsForArrow = null;
|
|
1149
1226
|
if (this.edgeStyle === "line") {
|
|
1150
1227
|
this._drawLine(a.x, a.y, b.x, b.y);
|
|
1151
|
-
ptsForArrow = [
|
|
1228
|
+
ptsForArrow = [
|
|
1229
|
+
{ x: a.x, y: a.y },
|
|
1230
|
+
{ x: b.x, y: b.y }
|
|
1231
|
+
];
|
|
1152
1232
|
} else if (this.edgeStyle === "orthogonal") {
|
|
1153
1233
|
ptsForArrow = this._drawOrthogonal(a.x, a.y, b.x, b.y);
|
|
1154
1234
|
} else {
|
|
1155
1235
|
this._drawCurve(a.x, a.y, b.x, b.y);
|
|
1156
|
-
ptsForArrow = [
|
|
1236
|
+
ptsForArrow = [
|
|
1237
|
+
{ x: a.x, y: a.y },
|
|
1238
|
+
{ x: b.x, y: b.y }
|
|
1239
|
+
];
|
|
1157
1240
|
}
|
|
1158
1241
|
this.ctx.setLineDash(prevDash);
|
|
1159
1242
|
if (ptsForArrow && ptsForArrow.length >= 2) {
|
|
@@ -1351,6 +1434,16 @@ const _Controller = class _Controller {
|
|
|
1351
1434
|
this._onContextMenuEvt = this._onContextMenu.bind(this);
|
|
1352
1435
|
this._onDblClickEvt = this._onDblClick.bind(this);
|
|
1353
1436
|
this._bindEvents();
|
|
1437
|
+
this.hooks.on("runner:step-updated", ({ activeNodeId, activeEdgeIds = [] }) => {
|
|
1438
|
+
this.activeNodes = activeNodeId ? /* @__PURE__ */ new Set([activeNodeId]) : /* @__PURE__ */ new Set();
|
|
1439
|
+
this.activeEdges = new Set(activeEdgeIds);
|
|
1440
|
+
this.activeEdgeTimes.clear();
|
|
1441
|
+
const now = performance.now();
|
|
1442
|
+
for (const edgeId of activeEdgeIds) {
|
|
1443
|
+
this.activeEdgeTimes.set(edgeId, now);
|
|
1444
|
+
}
|
|
1445
|
+
this.render();
|
|
1446
|
+
});
|
|
1354
1447
|
}
|
|
1355
1448
|
destroy() {
|
|
1356
1449
|
const c = this.renderer.canvas;
|
|
@@ -1642,7 +1735,8 @@ const _Controller = class _Controller {
|
|
|
1642
1735
|
const dx = w.x - this.resizing.startX;
|
|
1643
1736
|
const dy = w.y - this.resizing.startY;
|
|
1644
1737
|
const minW = _Controller.MIN_NODE_WIDTH;
|
|
1645
|
-
const
|
|
1738
|
+
const maxPorts = Math.max(n.inputs.length, n.outputs.length);
|
|
1739
|
+
const minH = maxPorts > 0 ? Math.max(_Controller.MIN_NODE_HEIGHT, 42 + maxPorts * 20) : _Controller.MIN_NODE_HEIGHT;
|
|
1646
1740
|
n.size.width = Math.max(minW, this.resizing.startW + dx);
|
|
1647
1741
|
n.size.height = Math.max(minH, this.resizing.startH + dy);
|
|
1648
1742
|
(_a = this.hooks) == null ? void 0 : _a.emit("node:resize", n);
|
|
@@ -1900,17 +1994,22 @@ const _Controller = class _Controller {
|
|
|
1900
1994
|
this.graph.updateWorldTransforms();
|
|
1901
1995
|
this.render();
|
|
1902
1996
|
}
|
|
1903
|
-
render() {
|
|
1997
|
+
render(time = performance.now()) {
|
|
1904
1998
|
var _a;
|
|
1905
1999
|
const tEdge = this.renderTempEdge();
|
|
2000
|
+
const runner = this.graph.runner;
|
|
2001
|
+
const isStepMode = !!runner && runner.executionMode === "step";
|
|
1906
2002
|
this.renderer.draw(this.graph, {
|
|
1907
2003
|
selection: this.selection,
|
|
1908
2004
|
tempEdge: null,
|
|
1909
2005
|
// Don't draw temp edge on background
|
|
1910
2006
|
boxSelecting: this.boxSelecting,
|
|
1911
2007
|
activeEdges: this.activeEdges || /* @__PURE__ */ new Set(),
|
|
1912
|
-
|
|
2008
|
+
activeEdgeTimes: this.activeEdgeTimes,
|
|
2009
|
+
drawEdges: !this.edgeRenderer,
|
|
1913
2010
|
// Only draw edges here if no separate edge renderer
|
|
2011
|
+
time,
|
|
2012
|
+
loopActiveEdges: isStepMode
|
|
1914
2013
|
});
|
|
1915
2014
|
(_a = this.htmlOverlay) == null ? void 0 : _a.draw(this.graph, this.selection);
|
|
1916
2015
|
if (this.edgeRenderer) {
|
|
@@ -1922,8 +2021,9 @@ const _Controller = class _Controller {
|
|
|
1922
2021
|
activeEdgeTimes: this.activeEdgeTimes,
|
|
1923
2022
|
activeNodes: this.activeNodes,
|
|
1924
2023
|
selection: this.selection,
|
|
1925
|
-
time
|
|
1926
|
-
tempEdge: tEdge
|
|
2024
|
+
time,
|
|
2025
|
+
tempEdge: tEdge,
|
|
2026
|
+
loopActiveEdges: isStepMode
|
|
1927
2027
|
});
|
|
1928
2028
|
this.edgeRenderer._resetTransform();
|
|
1929
2029
|
}
|
|
@@ -2317,6 +2417,10 @@ class Runner {
|
|
|
2317
2417
|
this._raf = null;
|
|
2318
2418
|
this._last = 0;
|
|
2319
2419
|
this.cyclesPerFrame = Math.max(1, cyclesPerFrame | 0);
|
|
2420
|
+
this.executionMode = "run";
|
|
2421
|
+
this.activePlan = null;
|
|
2422
|
+
this.activeStepIndex = -1;
|
|
2423
|
+
this.stepCache = /* @__PURE__ */ new Map();
|
|
2320
2424
|
}
|
|
2321
2425
|
isRunning() {
|
|
2322
2426
|
return this.running;
|
|
@@ -2359,6 +2463,7 @@ class Runner {
|
|
|
2359
2463
|
runOnce(startNodeId, dt = 0) {
|
|
2360
2464
|
const allConnectedNodes = /* @__PURE__ */ new Set();
|
|
2361
2465
|
const execEdgeOrder = [];
|
|
2466
|
+
const runCache = /* @__PURE__ */ new Map();
|
|
2362
2467
|
const queue = [{ nodeId: startNodeId, fromEdgeId: null }];
|
|
2363
2468
|
const visited = /* @__PURE__ */ new Set();
|
|
2364
2469
|
while (queue.length > 0) {
|
|
@@ -2376,15 +2481,15 @@ class Runner {
|
|
|
2376
2481
|
const sourceNode = this.graph.nodes.get(edge.fromNode);
|
|
2377
2482
|
if (sourceNode && !allConnectedNodes.has(edge.fromNode)) {
|
|
2378
2483
|
allConnectedNodes.add(edge.fromNode);
|
|
2379
|
-
this.
|
|
2484
|
+
this._executeNodeWithCache(edge.fromNode, dt, runCache);
|
|
2380
2485
|
}
|
|
2381
2486
|
}
|
|
2382
2487
|
}
|
|
2383
2488
|
}
|
|
2384
2489
|
}
|
|
2385
|
-
this.
|
|
2386
|
-
const
|
|
2387
|
-
for (const execOutput of
|
|
2490
|
+
this._executeNodeWithCache(currentNodeId, dt, runCache);
|
|
2491
|
+
const execOutputPorts = node.outputs.filter((p) => p.portType === "exec");
|
|
2492
|
+
for (const execOutput of execOutputPorts) {
|
|
2388
2493
|
for (const edge of this.graph.edges.values()) {
|
|
2389
2494
|
if (edge.fromNode === currentNodeId && edge.fromPort === execOutput.id) {
|
|
2390
2495
|
queue.push({ nodeId: edge.toNode, fromEdgeId: edge.id });
|
|
@@ -2400,6 +2505,130 @@ class Runner {
|
|
|
2400
2505
|
}
|
|
2401
2506
|
return { connectedNodes: allConnectedNodes, connectedEdges, execEdgeOrder };
|
|
2402
2507
|
}
|
|
2508
|
+
setExecutionMode(mode) {
|
|
2509
|
+
this.executionMode = mode;
|
|
2510
|
+
if (mode === "run") this.resetStepping();
|
|
2511
|
+
}
|
|
2512
|
+
resetStepping() {
|
|
2513
|
+
var _a, _b;
|
|
2514
|
+
this.activePlan = null;
|
|
2515
|
+
this.activeStepIndex = -1;
|
|
2516
|
+
this.stepCache.clear();
|
|
2517
|
+
(_b = (_a = this.hooks) == null ? void 0 : _a.emit) == null ? void 0 : _b.call(_a, "runner:step-updated", { activeNodeId: null });
|
|
2518
|
+
}
|
|
2519
|
+
buildPlan(startNodeId) {
|
|
2520
|
+
const plan = [];
|
|
2521
|
+
const allConnectedNodes = /* @__PURE__ */ new Set();
|
|
2522
|
+
const queue = [{ nodeId: startNodeId, fromEdgeId: null }];
|
|
2523
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2524
|
+
while (queue.length > 0) {
|
|
2525
|
+
const { nodeId: currentNodeId, fromEdgeId } = queue.shift();
|
|
2526
|
+
if (visited.has(currentNodeId)) continue;
|
|
2527
|
+
visited.add(currentNodeId);
|
|
2528
|
+
allConnectedNodes.add(currentNodeId);
|
|
2529
|
+
const node = this.graph.nodes.get(currentNodeId);
|
|
2530
|
+
if (!node) continue;
|
|
2531
|
+
const dataDeps = [];
|
|
2532
|
+
for (const input of node.inputs) {
|
|
2533
|
+
if (input.portType === "data") {
|
|
2534
|
+
for (const edge of this.graph.edges.values()) {
|
|
2535
|
+
if (edge.toNode === currentNodeId && edge.toPort === input.id) {
|
|
2536
|
+
const srcId = edge.fromNode;
|
|
2537
|
+
if (!allConnectedNodes.has(srcId)) {
|
|
2538
|
+
allConnectedNodes.add(srcId);
|
|
2539
|
+
dataDeps.push(srcId);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
const incomingEdges = [];
|
|
2546
|
+
for (const edge of this.graph.edges.values()) {
|
|
2547
|
+
if (edge.toNode === currentNodeId) {
|
|
2548
|
+
incomingEdges.push(edge.id);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
plan.push({ nodeId: currentNodeId, fromEdgeId, incomingEdges, dataDeps });
|
|
2552
|
+
const execOutputs = node.outputs.filter((p) => p.portType === "exec");
|
|
2553
|
+
for (const execOutput of execOutputs) {
|
|
2554
|
+
for (const edge of this.graph.edges.values()) {
|
|
2555
|
+
if (edge.fromNode === currentNodeId && edge.fromPort === execOutput.id) {
|
|
2556
|
+
queue.push({ nodeId: edge.toNode, fromEdgeId: edge.id });
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
return plan;
|
|
2562
|
+
}
|
|
2563
|
+
startStepping(startNodeId) {
|
|
2564
|
+
var _a, _b;
|
|
2565
|
+
this.stepCache.clear();
|
|
2566
|
+
this.activePlan = this.buildPlan(startNodeId);
|
|
2567
|
+
this.activeStepIndex = 0;
|
|
2568
|
+
const step = this.activePlan[0];
|
|
2569
|
+
(_b = (_a = this.hooks) == null ? void 0 : _a.emit) == null ? void 0 : _b.call(_a, "runner:step-updated", {
|
|
2570
|
+
activeNodeId: step == null ? void 0 : step.nodeId,
|
|
2571
|
+
activeEdgeIds: (step == null ? void 0 : step.incomingEdges) || []
|
|
2572
|
+
});
|
|
2573
|
+
this.start();
|
|
2574
|
+
}
|
|
2575
|
+
executeNextStep() {
|
|
2576
|
+
var _a, _b, _c, _d;
|
|
2577
|
+
if (!this.activePlan || this.activeStepIndex < 0 || this.activeStepIndex >= this.activePlan.length) {
|
|
2578
|
+
this.resetStepping();
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
const step = this.activePlan[this.activeStepIndex];
|
|
2582
|
+
for (const depId of step.dataDeps) {
|
|
2583
|
+
this._executeNodeWithCache(depId, 0, this.stepCache);
|
|
2584
|
+
}
|
|
2585
|
+
this._executeNodeWithCache(step.nodeId, 0, this.stepCache);
|
|
2586
|
+
this.activeStepIndex++;
|
|
2587
|
+
if (this.activeStepIndex < this.activePlan.length) {
|
|
2588
|
+
const nextStep = this.activePlan[this.activeStepIndex];
|
|
2589
|
+
(_b = (_a = this.hooks) == null ? void 0 : _a.emit) == null ? void 0 : _b.call(_a, "runner:step-updated", {
|
|
2590
|
+
activeNodeId: nextStep.nodeId,
|
|
2591
|
+
activeEdgeIds: nextStep.incomingEdges || []
|
|
2592
|
+
});
|
|
2593
|
+
} else {
|
|
2594
|
+
(_d = (_c = this.hooks) == null ? void 0 : _c.emit) == null ? void 0 : _d.call(_c, "runner:step-updated", { activeNodeId: null });
|
|
2595
|
+
this.resetStepping();
|
|
2596
|
+
}
|
|
2597
|
+
return step.nodeId;
|
|
2598
|
+
}
|
|
2599
|
+
/** Execute a node using a shared run-local output cache for reliable data passing. */
|
|
2600
|
+
_executeNodeWithCache(nodeId, dt, runCache) {
|
|
2601
|
+
var _a, _b;
|
|
2602
|
+
const node = this.graph.nodes.get(nodeId);
|
|
2603
|
+
if (!node) return;
|
|
2604
|
+
const def = this.registry.types.get(node.type);
|
|
2605
|
+
if (!(def == null ? void 0 : def.onExecute)) return;
|
|
2606
|
+
try {
|
|
2607
|
+
def.onExecute(node, {
|
|
2608
|
+
dt,
|
|
2609
|
+
graph: this.graph,
|
|
2610
|
+
getInput: (portName) => {
|
|
2611
|
+
const p = node.inputs.find((i) => i.name === portName) || node.inputs[0];
|
|
2612
|
+
if (!p) return void 0;
|
|
2613
|
+
for (const edge of this.graph.edges.values()) {
|
|
2614
|
+
if (edge.toNode === nodeId && edge.toPort === p.id) {
|
|
2615
|
+
const key = `${edge.fromNode}:${edge.fromPort}`;
|
|
2616
|
+
return runCache.has(key) ? runCache.get(key) : this.graph._curBuf().get(key);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
return void 0;
|
|
2620
|
+
},
|
|
2621
|
+
setOutput: (portName, value) => {
|
|
2622
|
+
const p = node.outputs.find((o) => o.name === portName) || node.outputs[0];
|
|
2623
|
+
if (p) {
|
|
2624
|
+
runCache.set(`${node.id}:${p.id}`, value);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2628
|
+
} catch (err) {
|
|
2629
|
+
(_b = (_a = this.hooks) == null ? void 0 : _a.emit) == null ? void 0 : _b.call(_a, "error", err);
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2403
2632
|
findAllNextExecNodes(nodeId) {
|
|
2404
2633
|
const node = this.graph.nodes.get(nodeId);
|
|
2405
2634
|
if (!node) return [];
|
|
@@ -2453,7 +2682,9 @@ class Runner {
|
|
|
2453
2682
|
const dtMs = this._last ? t - this._last : 0;
|
|
2454
2683
|
this._last = t;
|
|
2455
2684
|
const dt = dtMs / 1e3;
|
|
2456
|
-
|
|
2685
|
+
if (this.executionMode === "run") {
|
|
2686
|
+
this.step(this.cyclesPerFrame, dt);
|
|
2687
|
+
}
|
|
2457
2688
|
(_b2 = (_a2 = this.hooks) == null ? void 0 : _a2.emit) == null ? void 0 : _b2.call(_a2, "runner:tick", {
|
|
2458
2689
|
time: t,
|
|
2459
2690
|
dt,
|
|
@@ -2587,6 +2818,7 @@ class HtmlOverlay {
|
|
|
2587
2818
|
}
|
|
2588
2819
|
seen.add(node.id);
|
|
2589
2820
|
}
|
|
2821
|
+
this._drawStepOverlay(graph);
|
|
2590
2822
|
for (const [id, el] of this.nodes) {
|
|
2591
2823
|
if (!seen.has(id)) {
|
|
2592
2824
|
el.remove();
|
|
@@ -2594,6 +2826,59 @@ class HtmlOverlay {
|
|
|
2594
2826
|
}
|
|
2595
2827
|
}
|
|
2596
2828
|
}
|
|
2829
|
+
_drawStepOverlay(graph) {
|
|
2830
|
+
const runner = graph.runner;
|
|
2831
|
+
if (!runner || runner.executionMode !== "step" || !runner.activePlan) {
|
|
2832
|
+
if (this._stepBtn) {
|
|
2833
|
+
this._stepBtn.style.display = "none";
|
|
2834
|
+
}
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2837
|
+
const nextStep = runner.activePlan[runner.activeStepIndex];
|
|
2838
|
+
if (!nextStep) {
|
|
2839
|
+
if (this._stepBtn) this._stepBtn.style.display = "none";
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
const node = graph.nodes.get(nextStep.nodeId);
|
|
2843
|
+
if (!node) return;
|
|
2844
|
+
if (!this._stepBtn) {
|
|
2845
|
+
this._stepBtn = document.createElement("button");
|
|
2846
|
+
this._stepBtn.className = "step-play-button";
|
|
2847
|
+
this._stepBtn.innerHTML = "▶";
|
|
2848
|
+
Object.assign(this._stepBtn.style, {
|
|
2849
|
+
position: "absolute",
|
|
2850
|
+
zIndex: "100",
|
|
2851
|
+
width: "20px",
|
|
2852
|
+
height: "20px",
|
|
2853
|
+
borderRadius: "4px",
|
|
2854
|
+
border: "none",
|
|
2855
|
+
background: "transparent",
|
|
2856
|
+
color: "white",
|
|
2857
|
+
fontSize: "12px",
|
|
2858
|
+
cursor: "pointer",
|
|
2859
|
+
display: "flex",
|
|
2860
|
+
alignItems: "center",
|
|
2861
|
+
justifyContent: "center",
|
|
2862
|
+
// boxShadow: "0 2px 6px rgba(0,0,0,0.3)",
|
|
2863
|
+
pointerEvents: "auto",
|
|
2864
|
+
transition: "transform 0.1s, background 0.2s"
|
|
2865
|
+
});
|
|
2866
|
+
this._stepBtn.addEventListener("mouseover", () => {
|
|
2867
|
+
this._stepBtn.style.transform = "scale(1)";
|
|
2868
|
+
});
|
|
2869
|
+
this._stepBtn.addEventListener("mouseout", () => {
|
|
2870
|
+
this._stepBtn.style.transform = "scale(1)";
|
|
2871
|
+
});
|
|
2872
|
+
this._stepBtn.addEventListener("click", (e) => {
|
|
2873
|
+
e.stopPropagation();
|
|
2874
|
+
runner.executeNextStep();
|
|
2875
|
+
});
|
|
2876
|
+
this.container.appendChild(this._stepBtn);
|
|
2877
|
+
}
|
|
2878
|
+
this._stepBtn.style.display = "flex";
|
|
2879
|
+
this._stepBtn.style.left = `${node.computed.x + node.computed.w - 26}px`;
|
|
2880
|
+
this._stepBtn.style.top = `${node.computed.y + 2}px`;
|
|
2881
|
+
}
|
|
2597
2882
|
/**
|
|
2598
2883
|
* Sync container transform with renderer state (lightweight update)
|
|
2599
2884
|
* Called when zoom/pan occurs without needing full redraw
|
|
@@ -3165,7 +3450,7 @@ class HelpOverlay {
|
|
|
3165
3450
|
_createElements() {
|
|
3166
3451
|
this.toggleBtn = document.createElement("div");
|
|
3167
3452
|
this.toggleBtn.id = "helpToggle";
|
|
3168
|
-
this.toggleBtn.title = "
|
|
3453
|
+
this.toggleBtn.title = "단축키 (?)";
|
|
3169
3454
|
this.toggleBtn.textContent = "?";
|
|
3170
3455
|
this.container.appendChild(this.toggleBtn);
|
|
3171
3456
|
this.overlay = document.createElement("div");
|
|
@@ -3463,35 +3748,13 @@ function createGraphEditor(target, {
|
|
|
3463
3748
|
graph.runner = runner;
|
|
3464
3749
|
graph.controller = controller;
|
|
3465
3750
|
hooks.on("runner:tick", ({ time, dt }) => {
|
|
3466
|
-
|
|
3467
|
-
selection: controller.selection,
|
|
3468
|
-
tempEdge: controller.connecting ? controller.renderTempEdge() : null,
|
|
3469
|
-
// 필요시 helper
|
|
3470
|
-
running: true,
|
|
3471
|
-
time,
|
|
3472
|
-
dt
|
|
3473
|
-
});
|
|
3474
|
-
htmlOverlay.draw(graph, controller.selection);
|
|
3751
|
+
controller.render(time);
|
|
3475
3752
|
});
|
|
3476
3753
|
hooks.on("runner:start", () => {
|
|
3477
|
-
|
|
3478
|
-
selection: controller.selection,
|
|
3479
|
-
tempEdge: controller.connecting ? controller.renderTempEdge() : null,
|
|
3480
|
-
running: true,
|
|
3481
|
-
time: performance.now(),
|
|
3482
|
-
dt: 0
|
|
3483
|
-
});
|
|
3484
|
-
htmlOverlay.draw(graph, controller.selection);
|
|
3754
|
+
controller.render(performance.now());
|
|
3485
3755
|
});
|
|
3486
3756
|
hooks.on("runner:stop", () => {
|
|
3487
|
-
|
|
3488
|
-
selection: controller.selection,
|
|
3489
|
-
tempEdge: controller.connecting ? controller.renderTempEdge() : null,
|
|
3490
|
-
running: false,
|
|
3491
|
-
time: performance.now(),
|
|
3492
|
-
dt: 0
|
|
3493
|
-
});
|
|
3494
|
-
htmlOverlay.draw(graph, controller.selection);
|
|
3757
|
+
controller.render(performance.now());
|
|
3495
3758
|
});
|
|
3496
3759
|
hooks.on("node:updated", () => {
|
|
3497
3760
|
controller.render();
|
|
@@ -3545,6 +3808,8 @@ function createGraphEditor(target, {
|
|
|
3545
3808
|
},
|
|
3546
3809
|
graph,
|
|
3547
3810
|
renderer,
|
|
3811
|
+
edgeRenderer,
|
|
3812
|
+
// Expose edge renderer for style changes
|
|
3548
3813
|
controller,
|
|
3549
3814
|
// Expose controller for snap-to-grid access
|
|
3550
3815
|
runner,
|
|
@@ -3563,6 +3828,14 @@ function createGraphEditor(target, {
|
|
|
3563
3828
|
render: () => controller.render(),
|
|
3564
3829
|
start: () => runner.start(),
|
|
3565
3830
|
stop: () => runner.stop(),
|
|
3831
|
+
setEdgeStyle: (style) => {
|
|
3832
|
+
renderer.setEdgeStyle(style);
|
|
3833
|
+
edgeRenderer.setEdgeStyle(style);
|
|
3834
|
+
},
|
|
3835
|
+
setExecutionMode: (mode) => {
|
|
3836
|
+
runner.setExecutionMode(mode);
|
|
3837
|
+
controller.render();
|
|
3838
|
+
},
|
|
3566
3839
|
destroy: () => {
|
|
3567
3840
|
runner.stop();
|
|
3568
3841
|
ro.disconnect();
|