html-overlay-node 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/example.json +3 -3
- package/dist/html-overlay-node.es.js +704 -298
- package/dist/html-overlay-node.es.js.map +1 -1
- package/dist/html-overlay-node.umd.js +1 -1
- package/dist/html-overlay-node.umd.js.map +1 -1
- package/index.css +391 -232
- package/package.json +1 -1
- package/readme.md +58 -364
- package/src/core/Graph.js +29 -5
- package/src/core/Runner.js +201 -225
- package/src/index.js +17 -0
- package/src/interact/Controller.js +10 -3
- package/src/nodes/core.js +55 -77
- package/src/nodes/logic.js +51 -48
- package/src/nodes/math.js +21 -8
- package/src/nodes/util.js +176 -134
- package/src/nodes/value.js +86 -102
- package/src/render/CanvasRenderer.js +784 -704
- package/src/render/HtmlOverlay.js +1 -1
- package/src/render/hitTest.js +5 -2
- package/src/ui/HelpOverlay.js +158 -0
- package/src/ui/PropertyPanel.css +58 -27
- package/src/ui/PropertyPanel.js +441 -268
|
@@ -342,11 +342,12 @@ class Graph {
|
|
|
342
342
|
const available = Array.from(this.registry.types.keys()).join(", ") || "none";
|
|
343
343
|
throw new Error(`Unknown node type: "${type}". Available types: ${available}`);
|
|
344
344
|
}
|
|
345
|
+
const height = opts.height || ((_a = def.size) == null ? void 0 : _a.h) || this._calculateDefaultNodeHeight(def);
|
|
345
346
|
const node = new Node({
|
|
346
347
|
type,
|
|
347
348
|
title: def.title,
|
|
348
|
-
width: (
|
|
349
|
-
height
|
|
349
|
+
width: opts.width || ((_b = def.size) == null ? void 0 : _b.w) || 140,
|
|
350
|
+
height,
|
|
350
351
|
...opts
|
|
351
352
|
});
|
|
352
353
|
for (const i of def.inputs || []) node.addInput(i.name, i.datatype, i.portType || "data");
|
|
@@ -488,8 +489,12 @@ class Graph {
|
|
|
488
489
|
}
|
|
489
490
|
fromJSON(json) {
|
|
490
491
|
var _a, _b, _c;
|
|
491
|
-
this.clear();
|
|
492
|
+
this.nodes.clear();
|
|
493
|
+
this.edges.clear();
|
|
492
494
|
for (const nd of json.nodes) {
|
|
495
|
+
const def = (_b = (_a = this.registry) == null ? void 0 : _a.types) == null ? void 0 : _b.get(nd.type);
|
|
496
|
+
const minH = def ? this._calculateDefaultNodeHeight(def) : 60;
|
|
497
|
+
const height = nd.h !== void 0 ? nd.h : minH;
|
|
493
498
|
const node = new Node({
|
|
494
499
|
id: nd.id,
|
|
495
500
|
type: nd.type,
|
|
@@ -497,9 +502,8 @@ class Graph {
|
|
|
497
502
|
x: nd.x,
|
|
498
503
|
y: nd.y,
|
|
499
504
|
width: nd.w,
|
|
500
|
-
height
|
|
505
|
+
height
|
|
501
506
|
});
|
|
502
|
-
const def = (_b = (_a = this.registry) == null ? void 0 : _a.types) == null ? void 0 : _b.get(nd.type);
|
|
503
507
|
if (def == null ? void 0 : def.onCreate) {
|
|
504
508
|
def.onCreate(node);
|
|
505
509
|
}
|
|
@@ -525,6 +529,18 @@ class Graph {
|
|
|
525
529
|
(_c = this.hooks) == null ? void 0 : _c.emit("graph:deserialize", json);
|
|
526
530
|
return this;
|
|
527
531
|
}
|
|
532
|
+
_calculateDefaultNodeHeight(def) {
|
|
533
|
+
var _a, _b;
|
|
534
|
+
const inCount = ((_a = def.inputs) == null ? void 0 : _a.length) || 0;
|
|
535
|
+
const outCount = ((_b = def.outputs) == null ? void 0 : _b.length) || 0;
|
|
536
|
+
const maxPorts = Math.max(inCount, outCount);
|
|
537
|
+
const headerHeight = 26;
|
|
538
|
+
const padding = 8;
|
|
539
|
+
const portSpacing = 20;
|
|
540
|
+
let h = headerHeight + padding + maxPorts * portSpacing + padding;
|
|
541
|
+
if (def.html) h += 16;
|
|
542
|
+
return Math.max(h, 40);
|
|
543
|
+
}
|
|
528
544
|
}
|
|
529
545
|
function portRect(node, port, idx, dir) {
|
|
530
546
|
const {
|
|
@@ -538,8 +554,10 @@ function portRect(node, port, idx, dir) {
|
|
|
538
554
|
w: node.size.width,
|
|
539
555
|
h: node.size.height
|
|
540
556
|
};
|
|
541
|
-
const headerHeight =
|
|
542
|
-
const
|
|
557
|
+
const headerHeight = 26;
|
|
558
|
+
const padding = 8;
|
|
559
|
+
const portSpacing = 20;
|
|
560
|
+
const y = ny + headerHeight + padding + idx * portSpacing + portSpacing / 2;
|
|
543
561
|
const portWidth = 12;
|
|
544
562
|
const portHeight = 12;
|
|
545
563
|
if (dir === "in") {
|
|
@@ -562,32 +580,20 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
562
580
|
this.edgeStyle = edgeStyle;
|
|
563
581
|
this.theme = Object.assign(
|
|
564
582
|
{
|
|
565
|
-
bg: "#
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
title: "#1f1f24",
|
|
574
|
-
// Darker header
|
|
575
|
-
text: "#e4e4e7",
|
|
576
|
-
// Softer white
|
|
577
|
-
textMuted: "#a1a1aa",
|
|
578
|
-
// Muted text
|
|
579
|
-
port: "#6366f1",
|
|
580
|
-
// Indigo for data ports
|
|
583
|
+
bg: "#0e0e16",
|
|
584
|
+
grid: "#1c1c2c",
|
|
585
|
+
node: "rgba(22, 22, 34, 0.9)",
|
|
586
|
+
nodeBorder: "rgba(255, 255, 255, 0.08)",
|
|
587
|
+
title: "rgba(28, 28, 42, 0.95)",
|
|
588
|
+
text: "#f5f5f7",
|
|
589
|
+
textMuted: "#8e8eaf",
|
|
590
|
+
port: "#4f46e5",
|
|
581
591
|
portExec: "#10b981",
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
// Neutral edge color
|
|
585
|
-
edgeActive: "#8b5cf6",
|
|
586
|
-
// Purple for active
|
|
592
|
+
edge: "rgba(255, 255, 255, 0.12)",
|
|
593
|
+
edgeActive: "#6366f1",
|
|
587
594
|
accent: "#6366f1",
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
// Brighter accent
|
|
595
|
+
accentBright: "#818cf8",
|
|
596
|
+
accentGlow: "rgba(99, 102, 241, 0.25)"
|
|
591
597
|
},
|
|
592
598
|
theme
|
|
593
599
|
);
|
|
@@ -609,10 +615,6 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
609
615
|
this.offsetY = offsetY;
|
|
610
616
|
(_a = this._onTransformChange) == null ? void 0 : _a.call(this);
|
|
611
617
|
}
|
|
612
|
-
/**
|
|
613
|
-
* Set callback to be called when transform changes (zoom/pan)
|
|
614
|
-
* @param {Function} callback - Function to call on transform change
|
|
615
|
-
*/
|
|
616
618
|
setTransformChangeCallback(callback) {
|
|
617
619
|
this._onTransformChange = callback;
|
|
618
620
|
}
|
|
@@ -656,7 +658,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
656
658
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
657
659
|
}
|
|
658
660
|
// ── Drawing ────────────────────────────────────────────────────────────────
|
|
659
|
-
_drawArrowhead(x1, y1, x2, y2, size =
|
|
661
|
+
_drawArrowhead(x1, y1, x2, y2, size = 8) {
|
|
660
662
|
const { ctx } = this;
|
|
661
663
|
const s = size / this.scale;
|
|
662
664
|
const ang = Math.atan2(y2 - y1, x2 - x1);
|
|
@@ -668,12 +670,10 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
668
670
|
ctx.fill();
|
|
669
671
|
}
|
|
670
672
|
_drawScreenText(text, lx, ly, {
|
|
671
|
-
fontPx =
|
|
673
|
+
fontPx = 11,
|
|
672
674
|
color = this.theme.text,
|
|
673
675
|
align = "left",
|
|
674
|
-
baseline = "alphabetic"
|
|
675
|
-
dpr = 1
|
|
676
|
-
// 추후 devicePixelRatio 도입
|
|
676
|
+
baseline = "alphabetic"
|
|
677
677
|
} = {}) {
|
|
678
678
|
const { ctx } = this;
|
|
679
679
|
const { x: sx, y: sy } = this.worldToScreen(lx, ly);
|
|
@@ -681,7 +681,7 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
681
681
|
this._resetTransform();
|
|
682
682
|
const px = Math.round(sx) + 0.5;
|
|
683
683
|
const py = Math.round(sy) + 0.5;
|
|
684
|
-
ctx.font = `${fontPx * this.scale}px system-ui`;
|
|
684
|
+
ctx.font = `${fontPx * this.scale}px "Inter", system-ui, sans-serif`;
|
|
685
685
|
ctx.fillStyle = color;
|
|
686
686
|
ctx.textAlign = align;
|
|
687
687
|
ctx.textBaseline = baseline;
|
|
@@ -694,39 +694,47 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
694
694
|
ctx.fillStyle = theme.bg;
|
|
695
695
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
696
696
|
this._applyTransform();
|
|
697
|
-
ctx.strokeStyle = this._rgba(theme.grid, 0.35);
|
|
698
|
-
ctx.lineWidth = 1 / scale;
|
|
699
|
-
const base = 20;
|
|
700
|
-
const step = base;
|
|
701
697
|
const x0 = -offsetX / scale;
|
|
702
698
|
const y0 = -offsetY / scale;
|
|
703
699
|
const x1 = (canvas.width - offsetX) / scale;
|
|
704
700
|
const y1 = (canvas.height - offsetY) / scale;
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
701
|
+
const minorStep = 24;
|
|
702
|
+
const majorStep = 120;
|
|
703
|
+
const minorR = 1 / scale;
|
|
704
|
+
const majorR = 1.5 / scale;
|
|
705
|
+
const startX = Math.floor(x0 / minorStep) * minorStep;
|
|
706
|
+
const startY = Math.floor(y0 / minorStep) * minorStep;
|
|
707
|
+
ctx.fillStyle = this._rgba(theme.grid, 0.7);
|
|
708
|
+
for (let gx = startX; gx <= x1; gx += minorStep) {
|
|
709
|
+
for (let gy = startY; gy <= y1; gy += minorStep) {
|
|
710
|
+
const isMajorX = Math.round(gx / majorStep) * majorStep === Math.round(gx);
|
|
711
|
+
const isMajorY = Math.round(gy / majorStep) * majorStep === Math.round(gy);
|
|
712
|
+
if (isMajorX && isMajorY) continue;
|
|
713
|
+
ctx.beginPath();
|
|
714
|
+
ctx.arc(gx, gy, minorR, 0, Math.PI * 2);
|
|
715
|
+
ctx.fill();
|
|
716
|
+
}
|
|
711
717
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
718
|
+
const majorStartX = Math.floor(x0 / majorStep) * majorStep;
|
|
719
|
+
const majorStartY = Math.floor(y0 / majorStep) * majorStep;
|
|
720
|
+
ctx.fillStyle = this._rgba(theme.grid, 1);
|
|
721
|
+
for (let gx = majorStartX; gx <= x1; gx += majorStep) {
|
|
722
|
+
for (let gy = majorStartY; gy <= y1; gy += majorStep) {
|
|
723
|
+
ctx.beginPath();
|
|
724
|
+
ctx.arc(gx, gy, majorR, 0, Math.PI * 2);
|
|
725
|
+
ctx.fill();
|
|
726
|
+
}
|
|
715
727
|
}
|
|
716
|
-
ctx.stroke();
|
|
717
728
|
this._resetTransform();
|
|
718
729
|
}
|
|
719
730
|
draw(graph, {
|
|
720
731
|
selection = /* @__PURE__ */ new Set(),
|
|
721
732
|
tempEdge = null,
|
|
722
|
-
running = false,
|
|
723
733
|
time = performance.now(),
|
|
724
|
-
dt = 0,
|
|
725
|
-
groups = null,
|
|
726
734
|
activeEdges = /* @__PURE__ */ new Set(),
|
|
727
735
|
drawEdges = true
|
|
728
736
|
} = {}) {
|
|
729
|
-
var _a, _b, _c, _d
|
|
737
|
+
var _a, _b, _c, _d;
|
|
730
738
|
graph.updateWorldTransforms();
|
|
731
739
|
this.drawGrid();
|
|
732
740
|
const { ctx, theme } = this;
|
|
@@ -742,62 +750,60 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
742
750
|
}
|
|
743
751
|
if (drawEdges) {
|
|
744
752
|
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
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
754
|
const isActive = activeEdges && activeEdges.has(e.id);
|
|
763
755
|
if (isActive) {
|
|
764
|
-
ctx.
|
|
765
|
-
ctx.
|
|
756
|
+
ctx.save();
|
|
757
|
+
ctx.shadowColor = this.theme.edgeActive;
|
|
758
|
+
ctx.shadowBlur = 8 / this.scale;
|
|
759
|
+
ctx.strokeStyle = this.theme.edgeActive;
|
|
760
|
+
ctx.lineWidth = 2 / this.scale;
|
|
761
|
+
ctx.setLineDash([]);
|
|
762
|
+
this._drawEdge(graph, e);
|
|
763
|
+
ctx.restore();
|
|
764
|
+
const dotT = time / 1e3 * 1.2 % 1;
|
|
765
|
+
const dotPos = this._getEdgeDotPosition(graph, e, dotT);
|
|
766
|
+
if (dotPos) {
|
|
767
|
+
ctx.save();
|
|
768
|
+
ctx.fillStyle = "#ffffff";
|
|
769
|
+
ctx.shadowColor = this.theme.edgeActive;
|
|
770
|
+
ctx.shadowBlur = 10 / this.scale;
|
|
771
|
+
ctx.beginPath();
|
|
772
|
+
ctx.arc(dotPos.x, dotPos.y, 3 / this.scale, 0, Math.PI * 2);
|
|
773
|
+
ctx.fill();
|
|
774
|
+
ctx.restore();
|
|
775
|
+
}
|
|
766
776
|
} else {
|
|
777
|
+
ctx.setLineDash([]);
|
|
767
778
|
ctx.strokeStyle = theme.edge;
|
|
768
779
|
ctx.lineWidth = 1.5 / this.scale;
|
|
780
|
+
this._drawEdge(graph, e);
|
|
769
781
|
}
|
|
770
|
-
this._drawEdge(graph, e);
|
|
771
782
|
}
|
|
772
783
|
}
|
|
773
784
|
if (tempEdge) {
|
|
774
785
|
const a = this.screenToWorld(tempEdge.x1, tempEdge.y1);
|
|
775
786
|
const b = this.screenToWorld(tempEdge.x2, tempEdge.y2);
|
|
776
787
|
const prevDash = this.ctx.getLineDash();
|
|
777
|
-
this.ctx.setLineDash([
|
|
788
|
+
this.ctx.setLineDash([5 / this.scale, 5 / this.scale]);
|
|
789
|
+
this.ctx.strokeStyle = this._rgba(this.theme.accentBright, 0.7);
|
|
790
|
+
this.ctx.lineWidth = 1.5 / this.scale;
|
|
778
791
|
let ptsForArrow = null;
|
|
779
792
|
if (this.edgeStyle === "line") {
|
|
780
793
|
this._drawLine(a.x, a.y, b.x, b.y);
|
|
781
|
-
ptsForArrow = [
|
|
782
|
-
{ x: a.x, y: a.y },
|
|
783
|
-
{ x: b.x, y: b.y }
|
|
784
|
-
];
|
|
794
|
+
ptsForArrow = [{ x: a.x, y: a.y }, { x: b.x, y: b.y }];
|
|
785
795
|
} else if (this.edgeStyle === "orthogonal") {
|
|
786
796
|
ptsForArrow = this._drawOrthogonal(a.x, a.y, b.x, b.y);
|
|
787
797
|
} else {
|
|
788
798
|
this._drawCurve(a.x, a.y, b.x, b.y);
|
|
789
|
-
ptsForArrow = [
|
|
790
|
-
{ x: a.x, y: a.y },
|
|
791
|
-
{ x: b.x, y: b.y }
|
|
792
|
-
];
|
|
799
|
+
ptsForArrow = [{ x: a.x, y: a.y }, { x: b.x, y: b.y }];
|
|
793
800
|
}
|
|
794
801
|
this.ctx.setLineDash(prevDash);
|
|
795
802
|
if (ptsForArrow && ptsForArrow.length >= 2) {
|
|
796
803
|
const p1 = ptsForArrow[ptsForArrow.length - 2];
|
|
797
804
|
const p2 = ptsForArrow[ptsForArrow.length - 1];
|
|
798
|
-
this.ctx.fillStyle = this.theme.
|
|
799
|
-
this.
|
|
800
|
-
this._drawArrowhead(p1.x, p1.y, p2.x, p2.y, 12);
|
|
805
|
+
this.ctx.fillStyle = this.theme.accentBright;
|
|
806
|
+
this._drawArrowhead(p1.x, p1.y, p2.x, p2.y, 10);
|
|
801
807
|
}
|
|
802
808
|
}
|
|
803
809
|
for (const n of graph.nodes.values()) {
|
|
@@ -805,16 +811,10 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
805
811
|
const sel = selection.has(n.id);
|
|
806
812
|
const def = (_d = (_c = this.registry) == null ? void 0 : _c.types) == null ? void 0 : _d.get(n.type);
|
|
807
813
|
const hasHtmlOverlay = !!(def == null ? void 0 : def.html);
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
814
|
+
this._drawNode(n, sel, !hasHtmlOverlay ? true : false);
|
|
815
|
+
if (def == null ? void 0 : def.onDraw) {
|
|
816
|
+
def.onDraw(n, { ctx, theme, renderer: this });
|
|
811
817
|
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
for (const n of graph.nodes.values()) {
|
|
815
|
-
if (n.type !== "core/Group") {
|
|
816
|
-
const def = (_f = (_e = this.registry) == null ? void 0 : _e.types) == null ? void 0 : _f.get(n.type);
|
|
817
|
-
const hasHtmlOverlay = !!(def == null ? void 0 : def.html);
|
|
818
818
|
if (hasHtmlOverlay) {
|
|
819
819
|
this._drawPorts(n);
|
|
820
820
|
}
|
|
@@ -832,40 +832,58 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
832
832
|
return `rgba(${r},${g},${b},${a})`;
|
|
833
833
|
}
|
|
834
834
|
_drawNode(node, selected, skipPorts = false) {
|
|
835
|
+
var _a, _b;
|
|
835
836
|
const { ctx, theme } = this;
|
|
836
|
-
const r =
|
|
837
|
+
const r = 2;
|
|
837
838
|
const { x, y, w, h } = node.computed;
|
|
838
|
-
|
|
839
|
+
const headerH = 26;
|
|
840
|
+
const typeDef = (_b = (_a = this.registry) == null ? void 0 : _a.types) == null ? void 0 : _b.get(node.type);
|
|
841
|
+
const categoryColor = node.color || (typeDef == null ? void 0 : typeDef.color) || theme.accent;
|
|
842
|
+
if (selected) {
|
|
839
843
|
ctx.save();
|
|
840
|
-
ctx.shadowColor =
|
|
841
|
-
ctx.shadowBlur =
|
|
842
|
-
ctx.
|
|
843
|
-
ctx.
|
|
844
|
-
|
|
845
|
-
ctx
|
|
844
|
+
ctx.shadowColor = theme.accentGlow;
|
|
845
|
+
ctx.shadowBlur = 10 / this.scale;
|
|
846
|
+
ctx.strokeStyle = theme.accentBright;
|
|
847
|
+
ctx.lineWidth = 2 / this.scale;
|
|
848
|
+
const pad = 1.5 / this.scale;
|
|
849
|
+
roundRect(ctx, x - pad, y - pad, w + pad * 2, h + pad * 2, r + pad);
|
|
850
|
+
ctx.stroke();
|
|
846
851
|
ctx.restore();
|
|
847
852
|
}
|
|
853
|
+
ctx.save();
|
|
854
|
+
ctx.shadowColor = "rgba(0,0,0,0.7)";
|
|
855
|
+
ctx.shadowBlur = 20 / this.scale;
|
|
856
|
+
ctx.shadowOffsetY = 6 / this.scale;
|
|
857
|
+
ctx.fillStyle = theme.node;
|
|
858
|
+
roundRect(ctx, x, y, w, h, r);
|
|
859
|
+
ctx.fill();
|
|
860
|
+
ctx.restore();
|
|
848
861
|
ctx.fillStyle = theme.node;
|
|
849
862
|
ctx.strokeStyle = selected ? theme.accentBright : theme.nodeBorder;
|
|
850
|
-
ctx.lineWidth =
|
|
863
|
+
ctx.lineWidth = 1 / this.scale;
|
|
851
864
|
roundRect(ctx, x, y, w, h, r);
|
|
852
865
|
ctx.fill();
|
|
853
866
|
ctx.stroke();
|
|
854
867
|
ctx.fillStyle = theme.title;
|
|
855
|
-
roundRect(ctx, x, y, w,
|
|
868
|
+
roundRect(ctx, x, y, w, headerH, { tl: r, tr: r, br: 0, bl: 0 });
|
|
856
869
|
ctx.fill();
|
|
857
|
-
ctx.
|
|
858
|
-
ctx.
|
|
870
|
+
ctx.save();
|
|
871
|
+
ctx.globalCompositeOperation = "source-atop";
|
|
872
|
+
ctx.fillStyle = categoryColor;
|
|
873
|
+
ctx.globalAlpha = 0.25;
|
|
874
|
+
ctx.fillRect(x, y, w, headerH);
|
|
875
|
+
ctx.restore();
|
|
876
|
+
ctx.strokeStyle = selected ? this._rgba(theme.accentBright, 0.3) : this._rgba(theme.nodeBorder, 0.6);
|
|
877
|
+
ctx.lineWidth = 1 / this.scale;
|
|
859
878
|
ctx.beginPath();
|
|
860
|
-
ctx.moveTo(x +
|
|
861
|
-
ctx.lineTo(x + w
|
|
862
|
-
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
863
|
-
ctx.lineTo(x + w, y + 24);
|
|
864
|
-
ctx.moveTo(x, y + 24);
|
|
865
|
-
ctx.lineTo(x, y + r);
|
|
866
|
-
ctx.quadraticCurveTo(x, y, x + r, y);
|
|
879
|
+
ctx.moveTo(x, y + headerH);
|
|
880
|
+
ctx.lineTo(x + w, y + headerH);
|
|
867
881
|
ctx.stroke();
|
|
868
|
-
|
|
882
|
+
ctx.fillStyle = categoryColor;
|
|
883
|
+
ctx.beginPath();
|
|
884
|
+
ctx.roundRect(x, y, w, 2.5 / this.scale, { tl: r, tr: r, br: 0, bl: 0 });
|
|
885
|
+
ctx.fill();
|
|
886
|
+
this._drawScreenText(node.title, x + 10, y + headerH / 2, {
|
|
869
887
|
fontPx: _CanvasRenderer.FONT_SIZE,
|
|
870
888
|
color: theme.text,
|
|
871
889
|
baseline: "middle",
|
|
@@ -876,97 +894,111 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
876
894
|
const rct = portRect(node, p, i, "in");
|
|
877
895
|
const cx = rct.x + rct.w / 2;
|
|
878
896
|
const cy = rct.y + rct.h / 2;
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
ctx.stroke();
|
|
888
|
-
} else {
|
|
889
|
-
ctx.fillStyle = theme.port;
|
|
890
|
-
ctx.strokeStyle = "rgba(99, 102, 241, 0.3)";
|
|
891
|
-
ctx.lineWidth = 2 / this.scale;
|
|
892
|
-
ctx.beginPath();
|
|
893
|
-
ctx.arc(cx, cy, 5, 0, Math.PI * 2);
|
|
894
|
-
ctx.fill();
|
|
895
|
-
ctx.stroke();
|
|
897
|
+
this._drawPortShape(cx, cy, p.portType);
|
|
898
|
+
if (p.name) {
|
|
899
|
+
this._drawScreenText(p.name, cx + 10, cy, {
|
|
900
|
+
fontPx: 10,
|
|
901
|
+
color: theme.textMuted,
|
|
902
|
+
baseline: "middle",
|
|
903
|
+
align: "left"
|
|
904
|
+
});
|
|
896
905
|
}
|
|
897
906
|
});
|
|
898
907
|
node.outputs.forEach((p, i) => {
|
|
899
908
|
const rct = portRect(node, p, i, "out");
|
|
900
909
|
const cx = rct.x + rct.w / 2;
|
|
901
910
|
const cy = rct.y + rct.h / 2;
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
ctx.stroke();
|
|
911
|
-
} else {
|
|
912
|
-
ctx.fillStyle = theme.port;
|
|
913
|
-
ctx.strokeStyle = "rgba(99, 102, 241, 0.3)";
|
|
914
|
-
ctx.lineWidth = 2 / this.scale;
|
|
915
|
-
ctx.beginPath();
|
|
916
|
-
ctx.arc(cx, cy, 5, 0, Math.PI * 2);
|
|
917
|
-
ctx.fill();
|
|
918
|
-
ctx.stroke();
|
|
911
|
+
this._drawPortShape(cx, cy, p.portType);
|
|
912
|
+
if (p.name) {
|
|
913
|
+
this._drawScreenText(p.name, cx - 10, cy, {
|
|
914
|
+
fontPx: 10,
|
|
915
|
+
color: theme.textMuted,
|
|
916
|
+
baseline: "middle",
|
|
917
|
+
align: "right"
|
|
918
|
+
});
|
|
919
919
|
}
|
|
920
920
|
});
|
|
921
921
|
}
|
|
922
|
-
|
|
922
|
+
_drawPortShape(cx, cy, portType) {
|
|
923
923
|
const { ctx, theme } = this;
|
|
924
|
+
if (portType === "exec") {
|
|
925
|
+
const s = 5 / this.scale;
|
|
926
|
+
ctx.save();
|
|
927
|
+
ctx.fillStyle = theme.portExec;
|
|
928
|
+
ctx.strokeStyle = this._rgba(theme.portExec, 0.4);
|
|
929
|
+
ctx.lineWidth = 2 / this.scale;
|
|
930
|
+
ctx.beginPath();
|
|
931
|
+
ctx.moveTo(cx, cy - s);
|
|
932
|
+
ctx.lineTo(cx + s, cy);
|
|
933
|
+
ctx.lineTo(cx, cy + s);
|
|
934
|
+
ctx.lineTo(cx - s, cy);
|
|
935
|
+
ctx.closePath();
|
|
936
|
+
ctx.fill();
|
|
937
|
+
ctx.stroke();
|
|
938
|
+
ctx.restore();
|
|
939
|
+
} else {
|
|
940
|
+
ctx.save();
|
|
941
|
+
ctx.strokeStyle = this._rgba(theme.port, 0.35);
|
|
942
|
+
ctx.lineWidth = 3 / this.scale;
|
|
943
|
+
ctx.beginPath();
|
|
944
|
+
ctx.arc(cx, cy, 5 / this.scale, 0, Math.PI * 2);
|
|
945
|
+
ctx.stroke();
|
|
946
|
+
ctx.fillStyle = theme.port;
|
|
947
|
+
ctx.beginPath();
|
|
948
|
+
ctx.arc(cx, cy, 3.5 / this.scale, 0, Math.PI * 2);
|
|
949
|
+
ctx.fill();
|
|
950
|
+
ctx.restore();
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
_drawPorts(node) {
|
|
924
954
|
node.inputs.forEach((p, i) => {
|
|
925
955
|
const rct = portRect(node, p, i, "in");
|
|
926
956
|
const cx = rct.x + rct.w / 2;
|
|
927
957
|
const cy = rct.y + rct.h / 2;
|
|
928
|
-
|
|
929
|
-
const portSize = 8;
|
|
930
|
-
ctx.fillStyle = theme.portExec;
|
|
931
|
-
ctx.strokeStyle = "rgba(16, 185, 129, 0.3)";
|
|
932
|
-
ctx.lineWidth = 2 / this.scale;
|
|
933
|
-
ctx.beginPath();
|
|
934
|
-
ctx.roundRect(cx - portSize / 2, cy - portSize / 2, portSize, portSize, 2);
|
|
935
|
-
ctx.fill();
|
|
936
|
-
ctx.stroke();
|
|
937
|
-
} else {
|
|
938
|
-
ctx.fillStyle = theme.port;
|
|
939
|
-
ctx.strokeStyle = "rgba(99, 102, 241, 0.3)";
|
|
940
|
-
ctx.lineWidth = 2 / this.scale;
|
|
941
|
-
ctx.beginPath();
|
|
942
|
-
ctx.arc(cx, cy, 5, 0, Math.PI * 2);
|
|
943
|
-
ctx.fill();
|
|
944
|
-
}
|
|
958
|
+
this._drawPortShape(cx, cy, p.portType);
|
|
945
959
|
});
|
|
946
960
|
node.outputs.forEach((p, i) => {
|
|
947
961
|
const rct = portRect(node, p, i, "out");
|
|
948
962
|
const cx = rct.x + rct.w / 2;
|
|
949
963
|
const cy = rct.y + rct.h / 2;
|
|
950
|
-
|
|
951
|
-
const portSize = 8;
|
|
952
|
-
ctx.fillStyle = theme.portExec;
|
|
953
|
-
ctx.strokeStyle = "rgba(16, 185, 129, 0.3)";
|
|
954
|
-
ctx.lineWidth = 2 / this.scale;
|
|
955
|
-
ctx.beginPath();
|
|
956
|
-
ctx.roundRect(cx - portSize / 2, cy - portSize / 2, portSize, portSize, 2);
|
|
957
|
-
ctx.fill();
|
|
958
|
-
ctx.stroke();
|
|
959
|
-
} else {
|
|
960
|
-
ctx.fillStyle = theme.port;
|
|
961
|
-
ctx.strokeStyle = "rgba(99, 102, 241, 0.3)";
|
|
962
|
-
ctx.lineWidth = 2 / this.scale;
|
|
963
|
-
ctx.beginPath();
|
|
964
|
-
ctx.arc(cx, cy, 5, 0, Math.PI * 2);
|
|
965
|
-
ctx.fill();
|
|
966
|
-
ctx.stroke();
|
|
967
|
-
}
|
|
964
|
+
this._drawPortShape(cx, cy, p.portType);
|
|
968
965
|
});
|
|
969
966
|
}
|
|
967
|
+
/** Selection border for HTML overlay nodes, drawn on the edge canvas */
|
|
968
|
+
_drawHtmlSelectionBorder(node) {
|
|
969
|
+
const { ctx, theme } = this;
|
|
970
|
+
const { x, y, w, h } = node.computed;
|
|
971
|
+
const r = 2;
|
|
972
|
+
const pad = 1.5 / this.scale;
|
|
973
|
+
ctx.save();
|
|
974
|
+
ctx.shadowColor = theme.accentGlow;
|
|
975
|
+
ctx.shadowBlur = 14 / this.scale;
|
|
976
|
+
ctx.strokeStyle = theme.accentBright;
|
|
977
|
+
ctx.lineWidth = 1.5 / this.scale;
|
|
978
|
+
roundRect(ctx, x - pad, y - pad, w + pad * 2, h + pad * 2, r);
|
|
979
|
+
ctx.stroke();
|
|
980
|
+
ctx.restore();
|
|
981
|
+
}
|
|
982
|
+
/** Rotating dashed border drawn on the edge canvas for executing nodes */
|
|
983
|
+
_drawActiveNodeBorder(node, time) {
|
|
984
|
+
const { ctx, theme } = this;
|
|
985
|
+
const { x, y, w, h } = node.computed;
|
|
986
|
+
const r = 2;
|
|
987
|
+
const pad = 2.5 / this.scale;
|
|
988
|
+
const dashLen = 8 / this.scale;
|
|
989
|
+
const gapLen = 6 / this.scale;
|
|
990
|
+
const offset = -(time / 1e3) * (50 / this.scale);
|
|
991
|
+
ctx.save();
|
|
992
|
+
ctx.setLineDash([dashLen, gapLen]);
|
|
993
|
+
ctx.lineDashOffset = offset;
|
|
994
|
+
ctx.strokeStyle = this._rgba(theme.portExec, 0.9);
|
|
995
|
+
ctx.lineWidth = 1.5 / this.scale;
|
|
996
|
+
ctx.shadowColor = theme.portExec;
|
|
997
|
+
ctx.shadowBlur = 4 / this.scale;
|
|
998
|
+
roundRect(ctx, x - pad, y - pad, w + pad * 2, h + pad * 2, r + pad);
|
|
999
|
+
ctx.stroke();
|
|
1000
|
+
ctx.restore();
|
|
1001
|
+
}
|
|
970
1002
|
_drawEdge(graph, e) {
|
|
971
1003
|
const from = graph.nodes.get(e.fromNode);
|
|
972
1004
|
const to = graph.nodes.get(e.toNode);
|
|
@@ -975,7 +1007,8 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
975
1007
|
const iIn = to.inputs.findIndex((p) => p.id === e.toPort);
|
|
976
1008
|
const pr1 = portRect(from, null, iOut, "out");
|
|
977
1009
|
const pr2 = portRect(to, null, iIn, "in");
|
|
978
|
-
const x1 = pr1.x + pr1.w / 2, y1 = pr1.y + pr1.h / 2
|
|
1010
|
+
const x1 = pr1.x + pr1.w / 2, y1 = pr1.y + pr1.h / 2;
|
|
1011
|
+
const x2 = pr2.x + pr2.w / 2, y2 = pr2.y + pr2.h / 2;
|
|
979
1012
|
if (this.edgeStyle === "line") {
|
|
980
1013
|
this._drawLine(x1, y1, x2, y2);
|
|
981
1014
|
} else if (this.edgeStyle === "orthogonal") {
|
|
@@ -984,6 +1017,32 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
984
1017
|
this._drawCurve(x1, y1, x2, y2);
|
|
985
1018
|
}
|
|
986
1019
|
}
|
|
1020
|
+
_getEdgeDotPosition(graph, e, t) {
|
|
1021
|
+
const from = graph.nodes.get(e.fromNode);
|
|
1022
|
+
const to = graph.nodes.get(e.toNode);
|
|
1023
|
+
if (!from || !to) return null;
|
|
1024
|
+
const iOut = from.outputs.findIndex((p) => p.id === e.fromPort);
|
|
1025
|
+
const iIn = to.inputs.findIndex((p) => p.id === e.toPort);
|
|
1026
|
+
const pr1 = portRect(from, null, iOut, "out");
|
|
1027
|
+
const pr2 = portRect(to, null, iIn, "in");
|
|
1028
|
+
const x1 = pr1.x + pr1.w / 2, y1 = pr1.y + pr1.h / 2;
|
|
1029
|
+
const x2 = pr2.x + pr2.w / 2, y2 = pr2.y + pr2.h / 2;
|
|
1030
|
+
if (this.edgeStyle === "bezier") {
|
|
1031
|
+
const dx = Math.max(40, Math.abs(x2 - x1) * 0.4);
|
|
1032
|
+
return cubicBezierPoint(x1, y1, x1 + dx, y1, x2 - dx, y2, x2, y2, t);
|
|
1033
|
+
} else if (this.edgeStyle === "orthogonal") {
|
|
1034
|
+
const midX = (x1 + x2) / 2;
|
|
1035
|
+
const pts = [
|
|
1036
|
+
{ x: x1, y: y1 },
|
|
1037
|
+
{ x: midX, y: y1 },
|
|
1038
|
+
{ x: midX, y: y2 },
|
|
1039
|
+
{ x: x2, y: y2 }
|
|
1040
|
+
];
|
|
1041
|
+
return polylinePoint(pts, t);
|
|
1042
|
+
} else {
|
|
1043
|
+
return { x: x1 + (x2 - x1) * t, y: y1 + (y2 - y1) * t };
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
987
1046
|
_drawLine(x1, y1, x2, y2) {
|
|
988
1047
|
const { ctx } = this;
|
|
989
1048
|
ctx.beginPath();
|
|
@@ -1000,15 +1059,12 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
1000
1059
|
}
|
|
1001
1060
|
_drawOrthogonal(x1, y1, x2, y2) {
|
|
1002
1061
|
const midX = (x1 + x2) / 2;
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
{ x: x2, y: y2 }
|
|
1010
|
-
];
|
|
1011
|
-
}
|
|
1062
|
+
const pts = [
|
|
1063
|
+
{ x: x1, y: y1 },
|
|
1064
|
+
{ x: midX, y: y1 },
|
|
1065
|
+
{ x: midX, y: y2 },
|
|
1066
|
+
{ x: x2, y: y2 }
|
|
1067
|
+
];
|
|
1012
1068
|
const { ctx } = this;
|
|
1013
1069
|
const prevJoin = ctx.lineJoin, prevCap = ctx.lineCap;
|
|
1014
1070
|
ctx.lineJoin = "round";
|
|
@@ -1026,74 +1082,91 @@ const _CanvasRenderer = class _CanvasRenderer {
|
|
|
1026
1082
|
ctx.bezierCurveTo(x1 + dx, y1, x2 - dx, y2, x2, y2);
|
|
1027
1083
|
ctx.stroke();
|
|
1028
1084
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1085
|
+
drawEdgesOnly(graph, {
|
|
1086
|
+
activeEdges = /* @__PURE__ */ new Set(),
|
|
1087
|
+
activeEdgeTimes = /* @__PURE__ */ new Map(),
|
|
1088
|
+
activeNodes = /* @__PURE__ */ new Set(),
|
|
1089
|
+
selection = /* @__PURE__ */ new Set(),
|
|
1090
|
+
time = performance.now(),
|
|
1091
|
+
tempEdge = null
|
|
1092
|
+
} = {}) {
|
|
1093
|
+
var _a, _b;
|
|
1035
1094
|
this._resetTransform();
|
|
1036
1095
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
1037
1096
|
this._applyTransform();
|
|
1038
1097
|
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
1098
|
for (const e of graph.edges.values()) {
|
|
1050
|
-
const isActive = activeEdges
|
|
1051
|
-
if (isActive
|
|
1052
|
-
ctx.
|
|
1053
|
-
ctx.
|
|
1054
|
-
ctx.
|
|
1055
|
-
ctx.
|
|
1099
|
+
const isActive = activeEdges.has(e.id);
|
|
1100
|
+
if (isActive) {
|
|
1101
|
+
ctx.save();
|
|
1102
|
+
ctx.shadowColor = theme.edgeActive;
|
|
1103
|
+
ctx.shadowBlur = 6 / this.scale;
|
|
1104
|
+
ctx.strokeStyle = theme.edgeActive;
|
|
1105
|
+
ctx.lineWidth = 2 / this.scale;
|
|
1106
|
+
ctx.setLineDash([]);
|
|
1107
|
+
this._drawEdge(graph, e);
|
|
1108
|
+
ctx.restore();
|
|
1109
|
+
const activationTime = activeEdgeTimes.get(e.id) ?? time;
|
|
1110
|
+
const dotT = Math.min(1, (time - activationTime) / 620);
|
|
1111
|
+
const dotPos = this._getEdgeDotPosition(graph, e, dotT);
|
|
1112
|
+
if (dotPos) {
|
|
1113
|
+
ctx.save();
|
|
1114
|
+
ctx.fillStyle = this._rgba(theme.edgeActive, 0.9);
|
|
1115
|
+
ctx.shadowColor = theme.edgeActive;
|
|
1116
|
+
ctx.shadowBlur = 8 / this.scale;
|
|
1117
|
+
ctx.beginPath();
|
|
1118
|
+
ctx.arc(dotPos.x, dotPos.y, 2.5 / this.scale, 0, Math.PI * 2);
|
|
1119
|
+
ctx.fill();
|
|
1120
|
+
ctx.restore();
|
|
1121
|
+
}
|
|
1056
1122
|
} else {
|
|
1057
1123
|
ctx.setLineDash([]);
|
|
1058
1124
|
ctx.strokeStyle = theme.edge;
|
|
1059
1125
|
ctx.lineWidth = 1.5 / this.scale;
|
|
1126
|
+
this._drawEdge(graph, e);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
for (const nodeId of selection) {
|
|
1130
|
+
const node = graph.nodes.get(nodeId);
|
|
1131
|
+
if (!node) continue;
|
|
1132
|
+
const def = (_b = (_a = this.registry) == null ? void 0 : _a.types) == null ? void 0 : _b.get(node.type);
|
|
1133
|
+
if (def == null ? void 0 : def.html) this._drawHtmlSelectionBorder(node);
|
|
1134
|
+
}
|
|
1135
|
+
if (activeNodes.size > 0) {
|
|
1136
|
+
for (const nodeId of activeNodes) {
|
|
1137
|
+
const node = graph.nodes.get(nodeId);
|
|
1138
|
+
if (node) this._drawActiveNodeBorder(node, time);
|
|
1060
1139
|
}
|
|
1061
|
-
this._drawEdge(graph, e);
|
|
1062
1140
|
}
|
|
1063
1141
|
if (tempEdge) {
|
|
1064
1142
|
const a = this.screenToWorld(tempEdge.x1, tempEdge.y1);
|
|
1065
1143
|
const b = this.screenToWorld(tempEdge.x2, tempEdge.y2);
|
|
1066
1144
|
const prevDash = this.ctx.getLineDash();
|
|
1067
|
-
this.ctx.setLineDash([
|
|
1145
|
+
this.ctx.setLineDash([5 / this.scale, 5 / this.scale]);
|
|
1146
|
+
this.ctx.strokeStyle = this._rgba(this.theme.accentBright, 0.7);
|
|
1147
|
+
this.ctx.lineWidth = 1.5 / this.scale;
|
|
1068
1148
|
let ptsForArrow = null;
|
|
1069
1149
|
if (this.edgeStyle === "line") {
|
|
1070
1150
|
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
|
-
];
|
|
1151
|
+
ptsForArrow = [{ x: a.x, y: a.y }, { x: b.x, y: b.y }];
|
|
1075
1152
|
} else if (this.edgeStyle === "orthogonal") {
|
|
1076
1153
|
ptsForArrow = this._drawOrthogonal(a.x, a.y, b.x, b.y);
|
|
1077
1154
|
} else {
|
|
1078
1155
|
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
|
-
];
|
|
1156
|
+
ptsForArrow = [{ x: a.x, y: a.y }, { x: b.x, y: b.y }];
|
|
1083
1157
|
}
|
|
1084
1158
|
this.ctx.setLineDash(prevDash);
|
|
1085
1159
|
if (ptsForArrow && ptsForArrow.length >= 2) {
|
|
1086
1160
|
const p1 = ptsForArrow[ptsForArrow.length - 2];
|
|
1087
1161
|
const p2 = ptsForArrow[ptsForArrow.length - 1];
|
|
1088
|
-
this.ctx.fillStyle = this.theme.
|
|
1089
|
-
this.
|
|
1090
|
-
this._drawArrowhead(p1.x, p1.y, p2.x, p2.y, 12);
|
|
1162
|
+
this.ctx.fillStyle = this.theme.accentBright;
|
|
1163
|
+
this._drawArrowhead(p1.x, p1.y, p2.x, p2.y, 10);
|
|
1091
1164
|
}
|
|
1092
1165
|
}
|
|
1093
1166
|
this._resetTransform();
|
|
1094
1167
|
}
|
|
1095
1168
|
};
|
|
1096
|
-
__publicField(_CanvasRenderer, "FONT_SIZE",
|
|
1169
|
+
__publicField(_CanvasRenderer, "FONT_SIZE", 11);
|
|
1097
1170
|
__publicField(_CanvasRenderer, "SELECTED_NODE_COLOR", "#6cf");
|
|
1098
1171
|
let CanvasRenderer = _CanvasRenderer;
|
|
1099
1172
|
function roundRect(ctx, x, y, w, h, r = 6) {
|
|
@@ -1110,6 +1183,38 @@ function roundRect(ctx, x, y, w, h, r = 6) {
|
|
|
1110
1183
|
ctx.quadraticCurveTo(x, y, x + r.tl, y);
|
|
1111
1184
|
ctx.closePath();
|
|
1112
1185
|
}
|
|
1186
|
+
function cubicBezierPoint(x0, y0, x1, y1, x2, y2, x3, y3, t) {
|
|
1187
|
+
const mt = 1 - t;
|
|
1188
|
+
return {
|
|
1189
|
+
x: mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3,
|
|
1190
|
+
y: mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
function polylinePoint(pts, t) {
|
|
1194
|
+
let totalLen = 0;
|
|
1195
|
+
const lens = [];
|
|
1196
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
1197
|
+
const dx = pts[i + 1].x - pts[i].x;
|
|
1198
|
+
const dy = pts[i + 1].y - pts[i].y;
|
|
1199
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
1200
|
+
lens.push(len);
|
|
1201
|
+
totalLen += len;
|
|
1202
|
+
}
|
|
1203
|
+
if (totalLen === 0) return pts[0];
|
|
1204
|
+
let target = t * totalLen;
|
|
1205
|
+
let accum = 0;
|
|
1206
|
+
for (let i = 0; i < lens.length; i++) {
|
|
1207
|
+
if (accum + lens[i] >= target) {
|
|
1208
|
+
const segT = lens[i] > 0 ? (target - accum) / lens[i] : 0;
|
|
1209
|
+
return {
|
|
1210
|
+
x: pts[i].x + (pts[i + 1].x - pts[i].x) * segT,
|
|
1211
|
+
y: pts[i].y + (pts[i + 1].y - pts[i].y) * segT
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
accum += lens[i];
|
|
1215
|
+
}
|
|
1216
|
+
return pts[pts.length - 1];
|
|
1217
|
+
}
|
|
1113
1218
|
function findEdgeId(graph, a, b, c, d) {
|
|
1114
1219
|
for (const [id, e] of graph.edges) {
|
|
1115
1220
|
if (e.fromNode === a && e.fromPort === b && e.toNode === c && e.toPort === d)
|
|
@@ -1232,6 +1337,9 @@ const _Controller = class _Controller {
|
|
|
1232
1337
|
this.gDragging = null;
|
|
1233
1338
|
this.gResizing = null;
|
|
1234
1339
|
this.boxSelecting = null;
|
|
1340
|
+
this.activeEdges = /* @__PURE__ */ new Set();
|
|
1341
|
+
this.activeEdgeTimes = /* @__PURE__ */ new Map();
|
|
1342
|
+
this.activeNodes = /* @__PURE__ */ new Set();
|
|
1235
1343
|
this.snapToGrid = true;
|
|
1236
1344
|
this.gridSize = 20;
|
|
1237
1345
|
this._cursor = "default";
|
|
@@ -1810,11 +1918,12 @@ const _Controller = class _Controller {
|
|
|
1810
1918
|
edgeCtx.clearRect(0, 0, this.edgeRenderer.canvas.width, this.edgeRenderer.canvas.height);
|
|
1811
1919
|
this.edgeRenderer._applyTransform();
|
|
1812
1920
|
this.edgeRenderer.drawEdgesOnly(this.graph, {
|
|
1813
|
-
activeEdges: this.activeEdges
|
|
1814
|
-
|
|
1921
|
+
activeEdges: this.activeEdges,
|
|
1922
|
+
activeEdgeTimes: this.activeEdgeTimes,
|
|
1923
|
+
activeNodes: this.activeNodes,
|
|
1924
|
+
selection: this.selection,
|
|
1815
1925
|
time: performance.now(),
|
|
1816
1926
|
tempEdge: tEdge
|
|
1817
|
-
// Draw temp edge on edge layer
|
|
1818
1927
|
});
|
|
1819
1928
|
this.edgeRenderer._resetTransform();
|
|
1820
1929
|
}
|
|
@@ -2209,11 +2318,9 @@ class Runner {
|
|
|
2209
2318
|
this._last = 0;
|
|
2210
2319
|
this.cyclesPerFrame = Math.max(1, cyclesPerFrame | 0);
|
|
2211
2320
|
}
|
|
2212
|
-
// 외부에서 실행 중인지 확인
|
|
2213
2321
|
isRunning() {
|
|
2214
2322
|
return this.running;
|
|
2215
2323
|
}
|
|
2216
|
-
// 실행 도중에도 CPS 변경 가능
|
|
2217
2324
|
setCyclesPerFrame(n) {
|
|
2218
2325
|
this.cyclesPerFrame = Math.max(1, n | 0);
|
|
2219
2326
|
}
|
|
@@ -2246,29 +2353,22 @@ class Runner {
|
|
|
2246
2353
|
}
|
|
2247
2354
|
}
|
|
2248
2355
|
/**
|
|
2249
|
-
* Execute connected nodes once from a starting node
|
|
2250
|
-
*
|
|
2251
|
-
* @param {string} startNodeId - ID of the node to start from
|
|
2252
|
-
* @param {number} dt - Delta time
|
|
2356
|
+
* Execute connected nodes once from a starting node.
|
|
2357
|
+
* Returns execEdgeOrder: exec edges in the order they were traversed.
|
|
2253
2358
|
*/
|
|
2254
2359
|
runOnce(startNodeId, dt = 0) {
|
|
2255
|
-
console.log("[Runner.runOnce] Starting exec flow from node:", startNodeId);
|
|
2256
|
-
const executedNodes = [];
|
|
2257
2360
|
const allConnectedNodes = /* @__PURE__ */ new Set();
|
|
2258
|
-
const
|
|
2361
|
+
const execEdgeOrder = [];
|
|
2362
|
+
const queue = [{ nodeId: startNodeId, fromEdgeId: null }];
|
|
2259
2363
|
const visited = /* @__PURE__ */ new Set();
|
|
2260
2364
|
while (queue.length > 0) {
|
|
2261
|
-
const currentNodeId = queue.shift();
|
|
2365
|
+
const { nodeId: currentNodeId, fromEdgeId } = queue.shift();
|
|
2262
2366
|
if (visited.has(currentNodeId)) continue;
|
|
2263
2367
|
visited.add(currentNodeId);
|
|
2368
|
+
if (fromEdgeId) execEdgeOrder.push(fromEdgeId);
|
|
2264
2369
|
const node = this.graph.nodes.get(currentNodeId);
|
|
2265
|
-
if (!node)
|
|
2266
|
-
console.warn(`[Runner.runOnce] Node not found: ${currentNodeId}`);
|
|
2267
|
-
continue;
|
|
2268
|
-
}
|
|
2269
|
-
executedNodes.push(currentNodeId);
|
|
2370
|
+
if (!node) continue;
|
|
2270
2371
|
allConnectedNodes.add(currentNodeId);
|
|
2271
|
-
console.log(`[Runner.runOnce] Executing: ${node.title} (${node.type})`);
|
|
2272
2372
|
for (const input of node.inputs) {
|
|
2273
2373
|
if (input.portType === "data") {
|
|
2274
2374
|
for (const edge of this.graph.edges.values()) {
|
|
@@ -2283,25 +2383,23 @@ class Runner {
|
|
|
2283
2383
|
}
|
|
2284
2384
|
}
|
|
2285
2385
|
this.executeNode(currentNodeId, dt);
|
|
2286
|
-
const
|
|
2287
|
-
|
|
2386
|
+
const execOutputs = node.outputs.filter((p) => p.portType === "exec");
|
|
2387
|
+
for (const execOutput of execOutputs) {
|
|
2388
|
+
for (const edge of this.graph.edges.values()) {
|
|
2389
|
+
if (edge.fromNode === currentNodeId && edge.fromPort === execOutput.id) {
|
|
2390
|
+
queue.push({ nodeId: edge.toNode, fromEdgeId: edge.id });
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2288
2394
|
}
|
|
2289
|
-
console.log("[Runner.runOnce] Executed nodes:", executedNodes.length);
|
|
2290
2395
|
const connectedEdges = /* @__PURE__ */ new Set();
|
|
2291
2396
|
for (const edge of this.graph.edges.values()) {
|
|
2292
2397
|
if (allConnectedNodes.has(edge.fromNode) && allConnectedNodes.has(edge.toNode)) {
|
|
2293
2398
|
connectedEdges.add(edge.id);
|
|
2294
2399
|
}
|
|
2295
2400
|
}
|
|
2296
|
-
|
|
2297
|
-
return { connectedNodes: allConnectedNodes, connectedEdges };
|
|
2401
|
+
return { connectedNodes: allConnectedNodes, connectedEdges, execEdgeOrder };
|
|
2298
2402
|
}
|
|
2299
|
-
/**
|
|
2300
|
-
* Find all nodes connected via exec outputs
|
|
2301
|
-
* Supports multiple connections from a single exec output
|
|
2302
|
-
* @param {string} nodeId - Current node ID
|
|
2303
|
-
* @returns {string[]} Array of next node IDs
|
|
2304
|
-
*/
|
|
2305
2403
|
findAllNextExecNodes(nodeId) {
|
|
2306
2404
|
const node = this.graph.nodes.get(nodeId);
|
|
2307
2405
|
if (!node) return [];
|
|
@@ -2317,11 +2415,6 @@ class Runner {
|
|
|
2317
2415
|
}
|
|
2318
2416
|
return nextNodes;
|
|
2319
2417
|
}
|
|
2320
|
-
/**
|
|
2321
|
-
* Execute a single node
|
|
2322
|
-
* @param {string} nodeId - Node ID to execute
|
|
2323
|
-
* @param {number} dt - Delta time
|
|
2324
|
-
*/
|
|
2325
2418
|
executeNode(nodeId, dt) {
|
|
2326
2419
|
var _a, _b;
|
|
2327
2420
|
const node = this.graph.nodes.get(nodeId);
|
|
@@ -2419,7 +2512,7 @@ class HtmlOverlay {
|
|
|
2419
2512
|
const header = document.createElement("div");
|
|
2420
2513
|
header.className = "node-header";
|
|
2421
2514
|
Object.assign(header.style, {
|
|
2422
|
-
height: "
|
|
2515
|
+
height: "26px",
|
|
2423
2516
|
flexShrink: "0",
|
|
2424
2517
|
display: "flex",
|
|
2425
2518
|
alignItems: "center",
|
|
@@ -2635,10 +2728,44 @@ class PropertyPanel {
|
|
|
2635
2728
|
this.hooks = hooks;
|
|
2636
2729
|
this.registry = registry;
|
|
2637
2730
|
this.render = render;
|
|
2731
|
+
this._def = null;
|
|
2638
2732
|
this.panel = null;
|
|
2639
2733
|
this.currentNode = null;
|
|
2640
2734
|
this.isVisible = false;
|
|
2735
|
+
this._selfUpdating = false;
|
|
2641
2736
|
this._createPanel();
|
|
2737
|
+
this._bindHooks();
|
|
2738
|
+
}
|
|
2739
|
+
_bindHooks() {
|
|
2740
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2741
|
+
(_a = this.hooks) == null ? void 0 : _a.on("edge:create", () => {
|
|
2742
|
+
if (this._canRefresh()) this._renderContent();
|
|
2743
|
+
});
|
|
2744
|
+
(_b = this.hooks) == null ? void 0 : _b.on("edge:delete", () => {
|
|
2745
|
+
if (this._canRefresh()) this._renderContent();
|
|
2746
|
+
});
|
|
2747
|
+
(_c = this.hooks) == null ? void 0 : _c.on("node:updated", (node) => {
|
|
2748
|
+
var _a2;
|
|
2749
|
+
if (this._canRefresh() && ((_a2 = this.currentNode) == null ? void 0 : _a2.id) === (node == null ? void 0 : node.id) && !this._selfUpdating) {
|
|
2750
|
+
this._renderContent();
|
|
2751
|
+
}
|
|
2752
|
+
});
|
|
2753
|
+
(_d = this.hooks) == null ? void 0 : _d.on("node:move", (node) => {
|
|
2754
|
+
var _a2;
|
|
2755
|
+
if (this._canRefresh() && ((_a2 = this.currentNode) == null ? void 0 : _a2.id) === (node == null ? void 0 : node.id)) {
|
|
2756
|
+
this._updatePositionFields();
|
|
2757
|
+
}
|
|
2758
|
+
});
|
|
2759
|
+
(_e = this.hooks) == null ? void 0 : _e.on("runner:tick", () => {
|
|
2760
|
+
if (this._canRefresh()) this._updateLiveValues();
|
|
2761
|
+
});
|
|
2762
|
+
(_f = this.hooks) == null ? void 0 : _f.on("runner:stop", () => {
|
|
2763
|
+
if (this._canRefresh()) this._updateLiveValues();
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
_canRefresh() {
|
|
2767
|
+
if (!this.isVisible || !this.currentNode) return false;
|
|
2768
|
+
return !this.panel.querySelector("[data-field]:focus");
|
|
2642
2769
|
}
|
|
2643
2770
|
_createPanel() {
|
|
2644
2771
|
this.panel = document.createElement("div");
|
|
@@ -2668,8 +2795,10 @@ class PropertyPanel {
|
|
|
2668
2795
|
});
|
|
2669
2796
|
}
|
|
2670
2797
|
open(node) {
|
|
2798
|
+
var _a, _b;
|
|
2671
2799
|
if (!node) return;
|
|
2672
2800
|
this.currentNode = node;
|
|
2801
|
+
this._def = ((_b = (_a = this.registry) == null ? void 0 : _a.types) == null ? void 0 : _b.get(node.type)) || null;
|
|
2673
2802
|
this.isVisible = true;
|
|
2674
2803
|
this._renderContent();
|
|
2675
2804
|
this.panel.style.display = "block";
|
|
@@ -2705,9 +2834,9 @@ class PropertyPanel {
|
|
|
2705
2834
|
</div>
|
|
2706
2835
|
</div>
|
|
2707
2836
|
</div>
|
|
2708
|
-
|
|
2837
|
+
|
|
2709
2838
|
<div class="section">
|
|
2710
|
-
<div class="section-title">Position & Size</div>
|
|
2839
|
+
<div class="section-title">Position & Size</div>
|
|
2711
2840
|
<div class="section-body">
|
|
2712
2841
|
<div class="field-row">
|
|
2713
2842
|
<div class="field">
|
|
@@ -2731,16 +2860,92 @@ class PropertyPanel {
|
|
|
2731
2860
|
</div>
|
|
2732
2861
|
</div>
|
|
2733
2862
|
</div>
|
|
2734
|
-
|
|
2863
|
+
|
|
2864
|
+
${this._renderConnections(node)}
|
|
2735
2865
|
${this._renderPorts(node)}
|
|
2866
|
+
${this._renderLiveValues(node)}
|
|
2736
2867
|
${this._renderState(node)}
|
|
2737
|
-
|
|
2868
|
+
|
|
2738
2869
|
<div class="panel-actions">
|
|
2739
2870
|
<button class="btn-secondary panel-close-btn">Close</button>
|
|
2740
2871
|
</div>
|
|
2741
2872
|
`;
|
|
2742
2873
|
this._attachInputListeners();
|
|
2743
2874
|
}
|
|
2875
|
+
_renderConnections(node) {
|
|
2876
|
+
const edges = [...this.graph.edges.values()];
|
|
2877
|
+
const incoming = edges.filter((e) => e.toNode === node.id);
|
|
2878
|
+
const outgoing = edges.filter((e) => e.fromNode === node.id);
|
|
2879
|
+
if (!incoming.length && !outgoing.length) return "";
|
|
2880
|
+
const edgeLabel = (e, dir) => {
|
|
2881
|
+
const otherId = dir === "in" ? e.fromNode : e.toNode;
|
|
2882
|
+
const other = this.graph.nodes.get(otherId);
|
|
2883
|
+
return `<div class="port-item">
|
|
2884
|
+
<span class="port-icon data"></span>
|
|
2885
|
+
<span class="port-name" style="font-size:10px;color:#5a5a78;">${(other == null ? void 0 : other.title) ?? otherId}</span>
|
|
2886
|
+
</div>`;
|
|
2887
|
+
};
|
|
2888
|
+
return `
|
|
2889
|
+
<div class="section">
|
|
2890
|
+
<div class="section-title">Connections</div>
|
|
2891
|
+
<div class="section-body">
|
|
2892
|
+
${incoming.length ? `
|
|
2893
|
+
<div class="port-group">
|
|
2894
|
+
<div class="port-group-title">Incoming (${incoming.length})</div>
|
|
2895
|
+
${incoming.map((e) => edgeLabel(e, "in")).join("")}
|
|
2896
|
+
</div>` : ""}
|
|
2897
|
+
${outgoing.length ? `
|
|
2898
|
+
<div class="port-group">
|
|
2899
|
+
<div class="port-group-title">Outgoing (${outgoing.length})</div>
|
|
2900
|
+
${outgoing.map((e) => edgeLabel(e, "out")).join("")}
|
|
2901
|
+
</div>` : ""}
|
|
2902
|
+
</div>
|
|
2903
|
+
</div>
|
|
2904
|
+
`;
|
|
2905
|
+
}
|
|
2906
|
+
_renderLiveValues(node) {
|
|
2907
|
+
var _a, _b;
|
|
2908
|
+
const cur = (_b = (_a = this.graph) == null ? void 0 : _a._curBuf) == null ? void 0 : _b.call(_a);
|
|
2909
|
+
if (!cur) return "";
|
|
2910
|
+
const lines = [];
|
|
2911
|
+
for (const input of node.inputs) {
|
|
2912
|
+
`${node.id}:${input.id}`;
|
|
2913
|
+
for (const edge of this.graph.edges.values()) {
|
|
2914
|
+
if (edge.toNode === node.id && edge.toPort === input.id) {
|
|
2915
|
+
const upKey = `${edge.fromNode}:${edge.fromPort}`;
|
|
2916
|
+
const val = cur.get(upKey);
|
|
2917
|
+
if (val !== void 0) {
|
|
2918
|
+
lines.push(`<div class="port-item">
|
|
2919
|
+
<span class="port-icon data"></span>
|
|
2920
|
+
<span class="port-name">↳ ${input.name}</span>
|
|
2921
|
+
<span class="port-type" style="color:var(--color-primary);background:rgba(99,102,241,0.1);">${JSON.stringify(val)}</span>
|
|
2922
|
+
</div>`);
|
|
2923
|
+
}
|
|
2924
|
+
break;
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
for (const output of node.outputs) {
|
|
2929
|
+
const key = `${node.id}:${output.id}`;
|
|
2930
|
+
const val = cur.get(key);
|
|
2931
|
+
if (val !== void 0) {
|
|
2932
|
+
lines.push(`<div class="port-item">
|
|
2933
|
+
<span class="port-icon exec" style="background:#10b981;"></span>
|
|
2934
|
+
<span class="port-name">↳ ${output.name}</span>
|
|
2935
|
+
<span class="port-type" style="color:#10b981;background:rgba(16,185,129,0.1);">${JSON.stringify(val)}</span>
|
|
2936
|
+
</div>`);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
if (!lines.length) return "";
|
|
2940
|
+
return `
|
|
2941
|
+
<div class="section">
|
|
2942
|
+
<div class="section-title">Live Values</div>
|
|
2943
|
+
<div class="section-body">
|
|
2944
|
+
${lines.join("")}
|
|
2945
|
+
</div>
|
|
2946
|
+
</div>
|
|
2947
|
+
`;
|
|
2948
|
+
}
|
|
2744
2949
|
_renderPorts(node) {
|
|
2745
2950
|
if (!node.inputs.length && !node.outputs.length) return "";
|
|
2746
2951
|
return `
|
|
@@ -2759,7 +2964,6 @@ class PropertyPanel {
|
|
|
2759
2964
|
`).join("")}
|
|
2760
2965
|
</div>
|
|
2761
2966
|
` : ""}
|
|
2762
|
-
|
|
2763
2967
|
${node.outputs.length ? `
|
|
2764
2968
|
<div class="port-group">
|
|
2765
2969
|
<div class="port-group-title">Outputs (${node.outputs.length})</div>
|
|
@@ -2777,38 +2981,56 @@ class PropertyPanel {
|
|
|
2777
2981
|
`;
|
|
2778
2982
|
}
|
|
2779
2983
|
_renderState(node) {
|
|
2780
|
-
if (!node.state
|
|
2984
|
+
if (!node.state) return "";
|
|
2985
|
+
const entries = Object.entries(node.state).filter(([key, value]) => {
|
|
2986
|
+
if (key.startsWith("_")) return false;
|
|
2987
|
+
const t = typeof value;
|
|
2988
|
+
return t === "string" || t === "number" || t === "boolean";
|
|
2989
|
+
});
|
|
2990
|
+
if (!entries.length) return "";
|
|
2991
|
+
const fieldHtml = ([key, value]) => {
|
|
2992
|
+
if (typeof value === "boolean") {
|
|
2993
|
+
return `
|
|
2994
|
+
<div class="field">
|
|
2995
|
+
<label>${key}</label>
|
|
2996
|
+
<select data-field="state.${key}">
|
|
2997
|
+
<option value="true"${value ? " selected" : ""}>true</option>
|
|
2998
|
+
<option value="false"${!value ? " selected" : ""}>false</option>
|
|
2999
|
+
</select>
|
|
3000
|
+
</div>`;
|
|
3001
|
+
}
|
|
3002
|
+
return `
|
|
3003
|
+
<div class="field">
|
|
3004
|
+
<label>${key}</label>
|
|
3005
|
+
<input type="${typeof value === "number" ? "number" : "text"}"
|
|
3006
|
+
data-field="state.${key}"
|
|
3007
|
+
value="${value}" />
|
|
3008
|
+
</div>`;
|
|
3009
|
+
};
|
|
2781
3010
|
return `
|
|
2782
3011
|
<div class="section">
|
|
2783
3012
|
<div class="section-title">State</div>
|
|
2784
3013
|
<div class="section-body">
|
|
2785
|
-
${
|
|
2786
|
-
<div class="field">
|
|
2787
|
-
<label>${key}</label>
|
|
2788
|
-
<input
|
|
2789
|
-
type="${typeof value === "number" ? "number" : "text"}"
|
|
2790
|
-
data-field="state.${key}"
|
|
2791
|
-
value="${value}"
|
|
2792
|
-
/>
|
|
2793
|
-
</div>
|
|
2794
|
-
`).join("")}
|
|
3014
|
+
${entries.map(fieldHtml).join("")}
|
|
2795
3015
|
</div>
|
|
2796
3016
|
</div>
|
|
2797
3017
|
`;
|
|
2798
3018
|
}
|
|
2799
3019
|
_attachInputListeners() {
|
|
2800
|
-
|
|
2801
|
-
|
|
3020
|
+
var _a;
|
|
3021
|
+
this.panel.querySelectorAll("[data-field]").forEach((input) => {
|
|
2802
3022
|
input.addEventListener("change", () => {
|
|
3023
|
+
this._selfUpdating = true;
|
|
2803
3024
|
this._handleFieldChange(input.dataset.field, input.value);
|
|
3025
|
+
this._selfUpdating = false;
|
|
2804
3026
|
});
|
|
2805
3027
|
});
|
|
2806
|
-
this.panel.querySelector(".panel-close-btn").addEventListener("click", () => {
|
|
3028
|
+
(_a = this.panel.querySelector(".panel-close-btn")) == null ? void 0 : _a.addEventListener("click", () => {
|
|
2807
3029
|
this.close();
|
|
2808
3030
|
});
|
|
2809
3031
|
}
|
|
2810
3032
|
_handleFieldChange(field, value) {
|
|
2811
|
-
var _a;
|
|
3033
|
+
var _a, _b;
|
|
2812
3034
|
const node = this.currentNode;
|
|
2813
3035
|
if (!node) return;
|
|
2814
3036
|
switch (field) {
|
|
@@ -2832,21 +3054,192 @@ class PropertyPanel {
|
|
|
2832
3054
|
default:
|
|
2833
3055
|
if (field.startsWith("state.")) {
|
|
2834
3056
|
const key = field.substring(6);
|
|
2835
|
-
if (node.state) {
|
|
2836
|
-
const
|
|
2837
|
-
|
|
3057
|
+
if (node.state && key in node.state) {
|
|
3058
|
+
const orig = node.state[key];
|
|
3059
|
+
if (typeof orig === "boolean") {
|
|
3060
|
+
node.state[key] = value === "true";
|
|
3061
|
+
} else if (typeof orig === "number") {
|
|
3062
|
+
node.state[key] = parseFloat(value);
|
|
3063
|
+
} else {
|
|
3064
|
+
node.state[key] = value;
|
|
3065
|
+
}
|
|
2838
3066
|
}
|
|
2839
3067
|
}
|
|
2840
3068
|
}
|
|
2841
3069
|
(_a = this.hooks) == null ? void 0 : _a.emit("node:updated", node);
|
|
2842
|
-
|
|
2843
|
-
|
|
3070
|
+
(_b = this.render) == null ? void 0 : _b.call(this);
|
|
3071
|
+
}
|
|
3072
|
+
/** Lightweight update of position fields only (no full re-render) */
|
|
3073
|
+
_updatePositionFields() {
|
|
3074
|
+
const node = this.currentNode;
|
|
3075
|
+
if (!node) return;
|
|
3076
|
+
const xEl = this.panel.querySelector('[data-field="x"]');
|
|
3077
|
+
const yEl = this.panel.querySelector('[data-field="y"]');
|
|
3078
|
+
if (xEl) xEl.value = Math.round(node.computed.x);
|
|
3079
|
+
if (yEl) yEl.value = Math.round(node.computed.y);
|
|
3080
|
+
}
|
|
3081
|
+
/** Lightweight in-place update of the Live Values section */
|
|
3082
|
+
_updateLiveValues() {
|
|
3083
|
+
var _a, _b;
|
|
3084
|
+
const node = this.currentNode;
|
|
3085
|
+
if (!node) return;
|
|
3086
|
+
const cur = (_b = (_a = this.graph) == null ? void 0 : _a._curBuf) == null ? void 0 : _b.call(_a);
|
|
3087
|
+
if (!cur) return;
|
|
3088
|
+
let section = this.panel.querySelector(".live-values-section");
|
|
3089
|
+
const newHtml = this._renderLiveValues(node);
|
|
3090
|
+
if (!newHtml) {
|
|
3091
|
+
if (section) section.remove();
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
const wrapper = document.createElement("div");
|
|
3095
|
+
wrapper.innerHTML = newHtml;
|
|
3096
|
+
const newSection = wrapper.firstElementChild;
|
|
3097
|
+
newSection.classList.add("live-values-section");
|
|
3098
|
+
if (section) {
|
|
3099
|
+
section.replaceWith(newSection);
|
|
3100
|
+
} else {
|
|
3101
|
+
this.panel.querySelectorAll(".section");
|
|
3102
|
+
const actions = this.panel.querySelector(".panel-actions");
|
|
3103
|
+
if (actions) {
|
|
3104
|
+
actions.before(newSection);
|
|
3105
|
+
} else {
|
|
3106
|
+
this.panel.querySelector(".panel-content").appendChild(newSection);
|
|
3107
|
+
}
|
|
2844
3108
|
}
|
|
2845
3109
|
}
|
|
2846
3110
|
destroy() {
|
|
2847
|
-
|
|
2848
|
-
|
|
3111
|
+
var _a;
|
|
3112
|
+
(_a = this.panel) == null ? void 0 : _a.remove();
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
class HelpOverlay {
|
|
3116
|
+
constructor(container, options = {}) {
|
|
3117
|
+
this.container = container;
|
|
3118
|
+
this.options = {
|
|
3119
|
+
shortcuts: options.shortcuts || this._getDefaultShortcuts(),
|
|
3120
|
+
onToggle: options.onToggle || null
|
|
3121
|
+
};
|
|
3122
|
+
this.isVisible = false;
|
|
3123
|
+
this.overlay = null;
|
|
3124
|
+
this.toggleBtn = null;
|
|
3125
|
+
this._createElements();
|
|
3126
|
+
this._bindEvents();
|
|
3127
|
+
}
|
|
3128
|
+
_getDefaultShortcuts() {
|
|
3129
|
+
return [
|
|
3130
|
+
{
|
|
3131
|
+
group: "Selection",
|
|
3132
|
+
items: [
|
|
3133
|
+
{ label: "Select node", key: "Click" },
|
|
3134
|
+
{ label: "Multi-select", key: "Shift+Click" },
|
|
3135
|
+
{ label: "Box select", key: "Ctrl+Drag" }
|
|
3136
|
+
]
|
|
3137
|
+
},
|
|
3138
|
+
{
|
|
3139
|
+
group: "Edit",
|
|
3140
|
+
items: [
|
|
3141
|
+
{ label: "Delete", key: "Del" },
|
|
3142
|
+
{ label: "Undo", key: "Ctrl+Z" },
|
|
3143
|
+
{ label: "Redo", key: "Ctrl+Y" }
|
|
3144
|
+
]
|
|
3145
|
+
},
|
|
3146
|
+
{
|
|
3147
|
+
group: "Group & Align",
|
|
3148
|
+
items: [
|
|
3149
|
+
{ label: "Create group", key: "Ctrl+G" },
|
|
3150
|
+
{ label: "Align horizontal", key: "A" },
|
|
3151
|
+
{ label: "Align vertical", key: "Shift+A" }
|
|
3152
|
+
]
|
|
3153
|
+
},
|
|
3154
|
+
{
|
|
3155
|
+
group: "View",
|
|
3156
|
+
items: [
|
|
3157
|
+
{ label: "Toggle snap", key: "G" },
|
|
3158
|
+
{ label: "Pan", key: "Mid+Drag" },
|
|
3159
|
+
{ label: "Zoom", key: "Scroll" },
|
|
3160
|
+
{ label: "Context menu", key: "RClick" }
|
|
3161
|
+
]
|
|
3162
|
+
}
|
|
3163
|
+
];
|
|
3164
|
+
}
|
|
3165
|
+
_createElements() {
|
|
3166
|
+
this.toggleBtn = document.createElement("div");
|
|
3167
|
+
this.toggleBtn.id = "helpToggle";
|
|
3168
|
+
this.toggleBtn.title = "Keyboard shortcuts (?)";
|
|
3169
|
+
this.toggleBtn.textContent = "?";
|
|
3170
|
+
this.container.appendChild(this.toggleBtn);
|
|
3171
|
+
this.overlay = document.createElement("div");
|
|
3172
|
+
this.overlay.id = "helpOverlay";
|
|
3173
|
+
const sectionsHtml = this.options.shortcuts.map(
|
|
3174
|
+
(group) => `
|
|
3175
|
+
<h4>${group.group}</h4>
|
|
3176
|
+
${group.items.map(
|
|
3177
|
+
(item) => `
|
|
3178
|
+
<div class="shortcut-item">
|
|
3179
|
+
<span>${item.label}</span>
|
|
3180
|
+
<span class="shortcut-key">${item.key}</span>
|
|
3181
|
+
</div>
|
|
3182
|
+
`
|
|
3183
|
+
).join("")}
|
|
3184
|
+
`
|
|
3185
|
+
).join("");
|
|
3186
|
+
this.overlay.innerHTML = `
|
|
3187
|
+
<h3>
|
|
3188
|
+
<span>Keyboard Shortcuts</span>
|
|
3189
|
+
<button class="close-btn" id="helpClose" title="Close">×</button>
|
|
3190
|
+
</h3>
|
|
3191
|
+
${sectionsHtml}
|
|
3192
|
+
`;
|
|
3193
|
+
this.container.appendChild(this.overlay);
|
|
3194
|
+
}
|
|
3195
|
+
_bindEvents() {
|
|
3196
|
+
this.toggleBtn.addEventListener("click", () => this.toggle());
|
|
3197
|
+
const closeBtn = this.overlay.querySelector("#helpClose");
|
|
3198
|
+
if (closeBtn) {
|
|
3199
|
+
closeBtn.addEventListener("click", (e) => {
|
|
3200
|
+
e.stopPropagation();
|
|
3201
|
+
this.close();
|
|
3202
|
+
});
|
|
2849
3203
|
}
|
|
3204
|
+
document.addEventListener("mousedown", (e) => {
|
|
3205
|
+
if (this.isVisible) {
|
|
3206
|
+
if (!this.overlay.contains(e.target) && !this.toggleBtn.contains(e.target)) {
|
|
3207
|
+
this.close();
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
});
|
|
3211
|
+
window.addEventListener("keydown", (e) => {
|
|
3212
|
+
if (e.key === "?" || e.shiftKey && e.key === "/") {
|
|
3213
|
+
if (!["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement.tagName)) {
|
|
3214
|
+
e.preventDefault();
|
|
3215
|
+
this.toggle();
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
if (e.key === "Escape" && this.isVisible) {
|
|
3219
|
+
this.close();
|
|
3220
|
+
}
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
3223
|
+
toggle() {
|
|
3224
|
+
if (this.isVisible) this.close();
|
|
3225
|
+
else this.open();
|
|
3226
|
+
}
|
|
3227
|
+
open() {
|
|
3228
|
+
this.isVisible = true;
|
|
3229
|
+
this.overlay.classList.add("visible");
|
|
3230
|
+
this.toggleBtn.classList.add("active");
|
|
3231
|
+
if (this.options.onToggle) this.options.onToggle(true);
|
|
3232
|
+
}
|
|
3233
|
+
close() {
|
|
3234
|
+
this.isVisible = false;
|
|
3235
|
+
this.overlay.classList.remove("visible");
|
|
3236
|
+
this.toggleBtn.classList.remove("active");
|
|
3237
|
+
if (this.options.onToggle) this.options.onToggle(false);
|
|
3238
|
+
}
|
|
3239
|
+
destroy() {
|
|
3240
|
+
var _a, _b;
|
|
3241
|
+
(_a = this.toggleBtn) == null ? void 0 : _a.remove();
|
|
3242
|
+
(_b = this.overlay) == null ? void 0 : _b.remove();
|
|
2850
3243
|
}
|
|
2851
3244
|
}
|
|
2852
3245
|
function setupDefaultContextMenu(contextMenu, { controller, graph, hooks }) {
|
|
@@ -2925,6 +3318,8 @@ function createGraphEditor(target, {
|
|
|
2925
3318
|
showMinimap = true,
|
|
2926
3319
|
enablePropertyPanel = true,
|
|
2927
3320
|
propertyPanelContainer = null,
|
|
3321
|
+
enableHelp = true,
|
|
3322
|
+
helpShortcuts = null,
|
|
2928
3323
|
setupDefaultContextMenu: setupDefaultContextMenu$1 = true,
|
|
2929
3324
|
setupContextMenu = null,
|
|
2930
3325
|
plugins = []
|
|
@@ -3058,6 +3453,12 @@ function createGraphEditor(target, {
|
|
|
3058
3453
|
propertyPanel.open(node);
|
|
3059
3454
|
});
|
|
3060
3455
|
}
|
|
3456
|
+
let helpOverlay = null;
|
|
3457
|
+
if (enableHelp) {
|
|
3458
|
+
helpOverlay = new HelpOverlay(container, {
|
|
3459
|
+
shortcuts: helpShortcuts
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3061
3462
|
const runner = new Runner({ graph, registry, hooks });
|
|
3062
3463
|
graph.runner = runner;
|
|
3063
3464
|
graph.controller = controller;
|
|
@@ -3095,6 +3496,10 @@ function createGraphEditor(target, {
|
|
|
3095
3496
|
hooks.on("node:updated", () => {
|
|
3096
3497
|
controller.render();
|
|
3097
3498
|
});
|
|
3499
|
+
hooks.on("graph:deserialize", () => {
|
|
3500
|
+
renderer.setTransform({ scale: 1, offsetX: 0, offsetY: 0 });
|
|
3501
|
+
controller.render();
|
|
3502
|
+
});
|
|
3098
3503
|
if (setupDefaultContextMenu$1) {
|
|
3099
3504
|
setupDefaultContextMenu(contextMenu, { controller, graph, hooks });
|
|
3100
3505
|
}
|
|
@@ -3166,6 +3571,7 @@ function createGraphEditor(target, {
|
|
|
3166
3571
|
contextMenu.destroy();
|
|
3167
3572
|
if (propertyPanel) propertyPanel.destroy();
|
|
3168
3573
|
if (minimap) minimap.destroy();
|
|
3574
|
+
if (helpOverlay) helpOverlay.destroy();
|
|
3169
3575
|
}
|
|
3170
3576
|
};
|
|
3171
3577
|
if (autorun) runner.start();
|