@stocksharp/diagram 0.4.0 → 0.5.0
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/README.md +42 -0
- package/dist/esm/canvas-renderer.js +91 -16
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/diagram/context-menu.js +272 -0
- package/dist/esm/diagram/context-menu.js.map +1 -0
- package/dist/esm/diagram/stocksharp-diagram.js +83 -5
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/draw-surface.js +2 -0
- package/dist/esm/draw-surface.js.map +1 -0
- package/dist/esm/i18n.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/svg-surface.js +341 -0
- package/dist/esm/svg-surface.js.map +1 -0
- package/dist/ssdiagram.js +879 -164
- package/dist/ssdiagram.js.map +4 -4
- package/dist/types/canvas-renderer.d.ts +12 -0
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +36 -2
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/context-menu.d.ts +31 -0
- package/dist/types/diagram/context-menu.d.ts.map +1 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts +11 -5
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/draw-surface.d.ts +52 -0
- package/dist/types/draw-surface.d.ts.map +1 -0
- package/dist/types/i18n.d.ts +4 -0
- package/dist/types/i18n.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/svg-surface.d.ts +74 -0
- package/dist/types/svg-surface.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/canvas-renderer.ts +103 -16
- package/src/diagram/api.ts +46 -2
- package/src/diagram/context-menu.ts +298 -0
- package/src/diagram/stocksharp-diagram.ts +93 -6
- package/src/draw-surface.ts +61 -0
- package/src/i18n.ts +7 -0
- package/src/index.ts +8 -0
- package/src/svg-surface.ts +430 -0
package/dist/ssdiagram.js
CHANGED
|
@@ -556,8 +556,8 @@ var SSDiagram = (() => {
|
|
|
556
556
|
this.redoCommands.length = 0;
|
|
557
557
|
this.notify();
|
|
558
558
|
}
|
|
559
|
-
transaction(
|
|
560
|
-
const frame = { label, commands: [] };
|
|
559
|
+
transaction(label2, action) {
|
|
560
|
+
const frame = { label: label2, commands: [] };
|
|
561
561
|
this.transactions.push(frame);
|
|
562
562
|
try {
|
|
563
563
|
const result = action();
|
|
@@ -788,6 +788,326 @@ var SSDiagram = (() => {
|
|
|
788
788
|
return value;
|
|
789
789
|
}
|
|
790
790
|
|
|
791
|
+
// src/svg-surface.ts
|
|
792
|
+
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
793
|
+
function multiply(m, n2) {
|
|
794
|
+
return [
|
|
795
|
+
m[0] * n2[0] + m[2] * n2[1],
|
|
796
|
+
m[1] * n2[0] + m[3] * n2[1],
|
|
797
|
+
m[0] * n2[2] + m[2] * n2[3],
|
|
798
|
+
m[1] * n2[2] + m[3] * n2[3],
|
|
799
|
+
m[0] * n2[4] + m[2] * n2[5] + m[4],
|
|
800
|
+
m[1] * n2[4] + m[3] * n2[5] + m[5]
|
|
801
|
+
];
|
|
802
|
+
}
|
|
803
|
+
function isIdentity(m) {
|
|
804
|
+
return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
|
|
805
|
+
}
|
|
806
|
+
function n(value) {
|
|
807
|
+
if (!Number.isFinite(value)) return "0";
|
|
808
|
+
return (Math.round(value * 1e3) / 1e3).toString();
|
|
809
|
+
}
|
|
810
|
+
function escapeXml(value) {
|
|
811
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
812
|
+
}
|
|
813
|
+
var ANCHOR = {
|
|
814
|
+
left: "start",
|
|
815
|
+
start: "start",
|
|
816
|
+
center: "middle",
|
|
817
|
+
right: "end",
|
|
818
|
+
end: "end"
|
|
819
|
+
};
|
|
820
|
+
var BASELINE = {
|
|
821
|
+
top: "text-before-edge",
|
|
822
|
+
hanging: "hanging",
|
|
823
|
+
middle: "central",
|
|
824
|
+
alphabetic: "alphabetic",
|
|
825
|
+
ideographic: "ideographic",
|
|
826
|
+
bottom: "text-after-edge"
|
|
827
|
+
};
|
|
828
|
+
var SvgSurface = class {
|
|
829
|
+
constructor(options) {
|
|
830
|
+
this.body = [];
|
|
831
|
+
this.defs = [];
|
|
832
|
+
this.stack = [];
|
|
833
|
+
this.state = {
|
|
834
|
+
fillStyle: "#000000",
|
|
835
|
+
strokeStyle: "#000000",
|
|
836
|
+
lineWidth: 1,
|
|
837
|
+
lineJoin: "miter",
|
|
838
|
+
lineCap: "butt",
|
|
839
|
+
globalAlpha: 1,
|
|
840
|
+
font: "10px sans-serif",
|
|
841
|
+
textAlign: "start",
|
|
842
|
+
textBaseline: "alphabetic",
|
|
843
|
+
dash: [],
|
|
844
|
+
matrix: IDENTITY,
|
|
845
|
+
clipId: null
|
|
846
|
+
};
|
|
847
|
+
/** Path being built by moveTo/lineTo/arc/arcTo, in the units the caller passes. */
|
|
848
|
+
this.path = [];
|
|
849
|
+
this.cursor = null;
|
|
850
|
+
this.subpathStart = null;
|
|
851
|
+
this.clipSeq = 0;
|
|
852
|
+
this.width = options.width;
|
|
853
|
+
this.height = options.height;
|
|
854
|
+
this.background = options.background;
|
|
855
|
+
this.metrics = options.metrics ?? null;
|
|
856
|
+
}
|
|
857
|
+
// ===== state =========================================================
|
|
858
|
+
get fillStyle() {
|
|
859
|
+
return this.state.fillStyle;
|
|
860
|
+
}
|
|
861
|
+
set fillStyle(value) {
|
|
862
|
+
if (typeof value === "string") this.state.fillStyle = value;
|
|
863
|
+
}
|
|
864
|
+
get strokeStyle() {
|
|
865
|
+
return this.state.strokeStyle;
|
|
866
|
+
}
|
|
867
|
+
set strokeStyle(value) {
|
|
868
|
+
if (typeof value === "string") this.state.strokeStyle = value;
|
|
869
|
+
}
|
|
870
|
+
get lineWidth() {
|
|
871
|
+
return this.state.lineWidth;
|
|
872
|
+
}
|
|
873
|
+
set lineWidth(value) {
|
|
874
|
+
this.state.lineWidth = value;
|
|
875
|
+
}
|
|
876
|
+
get lineJoin() {
|
|
877
|
+
return this.state.lineJoin;
|
|
878
|
+
}
|
|
879
|
+
set lineJoin(value) {
|
|
880
|
+
this.state.lineJoin = value;
|
|
881
|
+
}
|
|
882
|
+
get lineCap() {
|
|
883
|
+
return this.state.lineCap;
|
|
884
|
+
}
|
|
885
|
+
set lineCap(value) {
|
|
886
|
+
this.state.lineCap = value;
|
|
887
|
+
}
|
|
888
|
+
get globalAlpha() {
|
|
889
|
+
return this.state.globalAlpha;
|
|
890
|
+
}
|
|
891
|
+
set globalAlpha(value) {
|
|
892
|
+
this.state.globalAlpha = value;
|
|
893
|
+
}
|
|
894
|
+
get font() {
|
|
895
|
+
return this.state.font;
|
|
896
|
+
}
|
|
897
|
+
set font(value) {
|
|
898
|
+
this.state.font = value;
|
|
899
|
+
if (this.metrics !== null) this.metrics.font = value;
|
|
900
|
+
}
|
|
901
|
+
get textAlign() {
|
|
902
|
+
return this.state.textAlign;
|
|
903
|
+
}
|
|
904
|
+
set textAlign(value) {
|
|
905
|
+
this.state.textAlign = value;
|
|
906
|
+
}
|
|
907
|
+
get textBaseline() {
|
|
908
|
+
return this.state.textBaseline;
|
|
909
|
+
}
|
|
910
|
+
set textBaseline(value) {
|
|
911
|
+
this.state.textBaseline = value;
|
|
912
|
+
}
|
|
913
|
+
save() {
|
|
914
|
+
this.stack.push({ ...this.state, dash: [...this.state.dash], matrix: [...this.state.matrix] });
|
|
915
|
+
}
|
|
916
|
+
restore() {
|
|
917
|
+
const previous = this.stack.pop();
|
|
918
|
+
if (previous !== void 0) this.state = previous;
|
|
919
|
+
}
|
|
920
|
+
setLineDash(segments) {
|
|
921
|
+
this.state.dash = [...segments];
|
|
922
|
+
}
|
|
923
|
+
// ===== transform =====================================================
|
|
924
|
+
setTransform(a, b, c, d, e, f) {
|
|
925
|
+
this.state.matrix = [a, b, c, d, e, f];
|
|
926
|
+
}
|
|
927
|
+
translate(x, y) {
|
|
928
|
+
this.state.matrix = multiply(this.state.matrix, [1, 0, 0, 1, x, y]);
|
|
929
|
+
}
|
|
930
|
+
rotate(angle) {
|
|
931
|
+
const cos = Math.cos(angle);
|
|
932
|
+
const sin = Math.sin(angle);
|
|
933
|
+
this.state.matrix = multiply(this.state.matrix, [cos, sin, -sin, cos, 0, 0]);
|
|
934
|
+
}
|
|
935
|
+
// ===== paths =========================================================
|
|
936
|
+
beginPath() {
|
|
937
|
+
this.path = [];
|
|
938
|
+
this.cursor = null;
|
|
939
|
+
this.subpathStart = null;
|
|
940
|
+
}
|
|
941
|
+
closePath() {
|
|
942
|
+
if (this.cursor === null) return;
|
|
943
|
+
this.path.push("Z");
|
|
944
|
+
if (this.subpathStart !== null) this.cursor = [...this.subpathStart];
|
|
945
|
+
}
|
|
946
|
+
moveTo(x, y) {
|
|
947
|
+
this.path.push(`M${n(x)} ${n(y)}`);
|
|
948
|
+
this.cursor = [x, y];
|
|
949
|
+
this.subpathStart = [x, y];
|
|
950
|
+
}
|
|
951
|
+
lineTo(x, y) {
|
|
952
|
+
if (this.cursor === null) {
|
|
953
|
+
this.moveTo(x, y);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
this.path.push(`L${n(x)} ${n(y)}`);
|
|
957
|
+
this.cursor = [x, y];
|
|
958
|
+
}
|
|
959
|
+
arc(cx, cy, r, start, end, ccw = false) {
|
|
960
|
+
const sx = cx + r * Math.cos(start);
|
|
961
|
+
const sy = cy + r * Math.sin(start);
|
|
962
|
+
if (this.cursor === null) this.moveTo(sx, sy);
|
|
963
|
+
else this.lineTo(sx, sy);
|
|
964
|
+
let delta = end - start;
|
|
965
|
+
if (ccw) {
|
|
966
|
+
while (delta > 0) delta -= Math.PI * 2;
|
|
967
|
+
if (delta <= -Math.PI * 2) delta = -Math.PI * 2;
|
|
968
|
+
} else {
|
|
969
|
+
while (delta < 0) delta += Math.PI * 2;
|
|
970
|
+
if (delta >= Math.PI * 2) delta = Math.PI * 2;
|
|
971
|
+
}
|
|
972
|
+
const sweep = ccw ? 0 : 1;
|
|
973
|
+
if (Math.abs(delta) >= Math.PI * 2 - 1e-9) {
|
|
974
|
+
const mid = start + delta / 2;
|
|
975
|
+
const mx = cx + r * Math.cos(mid);
|
|
976
|
+
const my = cy + r * Math.sin(mid);
|
|
977
|
+
this.path.push(`A${n(r)} ${n(r)} 0 0 ${sweep} ${n(mx)} ${n(my)}`);
|
|
978
|
+
this.path.push(`A${n(r)} ${n(r)} 0 0 ${sweep} ${n(sx)} ${n(sy)}`);
|
|
979
|
+
this.cursor = [sx, sy];
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const ex = cx + r * Math.cos(start + delta);
|
|
983
|
+
const ey = cy + r * Math.sin(start + delta);
|
|
984
|
+
const large = Math.abs(delta) > Math.PI ? 1 : 0;
|
|
985
|
+
this.path.push(`A${n(r)} ${n(r)} 0 ${large} ${sweep} ${n(ex)} ${n(ey)}`);
|
|
986
|
+
this.cursor = [ex, ey];
|
|
987
|
+
}
|
|
988
|
+
arcTo(x1, y1, x2, y2, r) {
|
|
989
|
+
if (this.cursor === null) {
|
|
990
|
+
this.moveTo(x1, y1);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
const [x0, y0] = this.cursor;
|
|
994
|
+
const a = [x0 - x1, y0 - y1];
|
|
995
|
+
const b = [x2 - x1, y2 - y1];
|
|
996
|
+
const la = Math.hypot(a[0], a[1]);
|
|
997
|
+
const lb = Math.hypot(b[0], b[1]);
|
|
998
|
+
if (la === 0 || lb === 0 || r === 0) {
|
|
999
|
+
this.lineTo(x1, y1);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const ua = [a[0] / la, a[1] / la];
|
|
1003
|
+
const ub = [b[0] / lb, b[1] / lb];
|
|
1004
|
+
const cross = ua[0] * ub[1] - ua[1] * ub[0];
|
|
1005
|
+
if (Math.abs(cross) < 1e-9) {
|
|
1006
|
+
this.lineTo(x1, y1);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const angle = Math.acos(Math.max(-1, Math.min(1, ua[0] * ub[0] + ua[1] * ub[1])));
|
|
1010
|
+
const tangent = r / Math.tan(angle / 2);
|
|
1011
|
+
const t2 = Math.min(tangent, la, lb);
|
|
1012
|
+
const radius = t2 * Math.tan(angle / 2);
|
|
1013
|
+
const t1 = [x1 + ua[0] * t2, y1 + ua[1] * t2];
|
|
1014
|
+
const t22 = [x1 + ub[0] * t2, y1 + ub[1] * t2];
|
|
1015
|
+
this.lineTo(t1[0], t1[1]);
|
|
1016
|
+
const sweep = cross < 0 ? 1 : 0;
|
|
1017
|
+
this.path.push(`A${n(radius)} ${n(radius)} 0 0 ${sweep} ${n(t22[0])} ${n(t22[1])}`);
|
|
1018
|
+
this.cursor = [t22[0], t22[1]];
|
|
1019
|
+
}
|
|
1020
|
+
fill() {
|
|
1021
|
+
if (this.path.length === 0) return;
|
|
1022
|
+
this.emit(`<path d="${this.path.join(" ")}" ${this.fillAttrs()}/>`);
|
|
1023
|
+
}
|
|
1024
|
+
stroke() {
|
|
1025
|
+
if (this.path.length === 0) return;
|
|
1026
|
+
this.emit(`<path d="${this.path.join(" ")}" fill="none" ${this.strokeAttrs()}/>`);
|
|
1027
|
+
}
|
|
1028
|
+
clip() {
|
|
1029
|
+
if (this.path.length === 0) return;
|
|
1030
|
+
const id = `clip${++this.clipSeq}`;
|
|
1031
|
+
this.defs.push(`<clipPath id="${id}"><path d="${this.path.join(" ")}"/></clipPath>`);
|
|
1032
|
+
this.state.clipId = id;
|
|
1033
|
+
}
|
|
1034
|
+
// ===== rectangles ====================================================
|
|
1035
|
+
clearRect(x, y, w, h) {
|
|
1036
|
+
void x;
|
|
1037
|
+
void y;
|
|
1038
|
+
void w;
|
|
1039
|
+
void h;
|
|
1040
|
+
}
|
|
1041
|
+
fillRect(x, y, w, h) {
|
|
1042
|
+
this.emit(`<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" ${this.fillAttrs()}/>`);
|
|
1043
|
+
}
|
|
1044
|
+
strokeRect(x, y, w, h) {
|
|
1045
|
+
this.emit(`<rect x="${n(x)}" y="${n(y)}" width="${n(w)}" height="${n(h)}" fill="none" ${this.strokeAttrs()}/>`);
|
|
1046
|
+
}
|
|
1047
|
+
// ===== text ==========================================================
|
|
1048
|
+
fillText(text, x, y) {
|
|
1049
|
+
if (text === "") return;
|
|
1050
|
+
const anchor = ANCHOR[this.state.textAlign] ?? "start";
|
|
1051
|
+
const baseline = BASELINE[this.state.textBaseline] ?? "alphabetic";
|
|
1052
|
+
this.emit(
|
|
1053
|
+
`<text x="${n(x)}" y="${n(y)}" style="font:${escapeXml(this.state.font)}" text-anchor="${anchor}" dominant-baseline="${baseline}" ${this.fillAttrs()}>${escapeXml(text)}</text>`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
measureText(text) {
|
|
1057
|
+
if (this.metrics !== null) return { width: this.metrics.measureText(text).width };
|
|
1058
|
+
const size = parseFloat(this.state.font) || 10;
|
|
1059
|
+
return { width: text.length * size * 0.55 };
|
|
1060
|
+
}
|
|
1061
|
+
// ===== images ========================================================
|
|
1062
|
+
drawImage(image, dx, dy, dw, dh) {
|
|
1063
|
+
const href = this.imageHref(image);
|
|
1064
|
+
if (href === null) return;
|
|
1065
|
+
this.emit(`<image x="${n(dx)}" y="${n(dy)}" width="${n(dw)}" height="${n(dh)}" href="${escapeXml(href)}"${this.commonAttrs()}/>`);
|
|
1066
|
+
}
|
|
1067
|
+
imageHref(image) {
|
|
1068
|
+
try {
|
|
1069
|
+
if (typeof HTMLCanvasElement !== "undefined" && image instanceof HTMLCanvasElement) return image.toDataURL();
|
|
1070
|
+
if (typeof HTMLImageElement !== "undefined" && image instanceof HTMLImageElement) return image.src;
|
|
1071
|
+
} catch {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
// ===== output ========================================================
|
|
1077
|
+
/** The finished document. Safe to call more than once; it does not consume the recording. */
|
|
1078
|
+
toSvg() {
|
|
1079
|
+
const background = this.background === void 0 ? "" : `<rect width="100%" height="100%" fill="${escapeXml(this.background)}"/>`;
|
|
1080
|
+
const defs = this.defs.length === 0 ? "" : `<defs>${this.defs.join("")}</defs>`;
|
|
1081
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${n(this.width)}" height="${n(this.height)}" viewBox="0 0 ${n(this.width)} ${n(this.height)}">${defs}${background}${this.body.join("")}</svg>`;
|
|
1082
|
+
}
|
|
1083
|
+
// ===== internals =====================================================
|
|
1084
|
+
emit(element) {
|
|
1085
|
+
this.body.push(element);
|
|
1086
|
+
}
|
|
1087
|
+
commonAttrs() {
|
|
1088
|
+
const parts = [];
|
|
1089
|
+
if (!isIdentity(this.state.matrix)) {
|
|
1090
|
+
parts.push(` transform="matrix(${this.state.matrix.map(n).join(" ")})"`);
|
|
1091
|
+
}
|
|
1092
|
+
if (this.state.globalAlpha !== 1) parts.push(` opacity="${n(this.state.globalAlpha)}"`);
|
|
1093
|
+
if (this.state.clipId !== null) parts.push(` clip-path="url(#${this.state.clipId})"`);
|
|
1094
|
+
return parts.join("");
|
|
1095
|
+
}
|
|
1096
|
+
fillAttrs() {
|
|
1097
|
+
return `fill="${escapeXml(this.state.fillStyle)}"${this.commonAttrs()}`;
|
|
1098
|
+
}
|
|
1099
|
+
strokeAttrs() {
|
|
1100
|
+
const parts = [
|
|
1101
|
+
`stroke="${escapeXml(this.state.strokeStyle)}"`,
|
|
1102
|
+
`stroke-width="${n(this.state.lineWidth)}"`
|
|
1103
|
+
];
|
|
1104
|
+
if (this.state.lineJoin !== "miter") parts.push(`stroke-linejoin="${this.state.lineJoin}"`);
|
|
1105
|
+
if (this.state.lineCap !== "butt") parts.push(`stroke-linecap="${this.state.lineCap}"`);
|
|
1106
|
+
if (this.state.dash.length > 0) parts.push(`stroke-dasharray="${this.state.dash.map(n).join(" ")}"`);
|
|
1107
|
+
return parts.join(" ") + this.commonAttrs();
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
791
1111
|
// src/canvas-renderer.ts
|
|
792
1112
|
var SCREEN_RENDER_OPTIONS = {
|
|
793
1113
|
grid: true,
|
|
@@ -1142,7 +1462,7 @@ var SSDiagram = (() => {
|
|
|
1142
1462
|
this.scheduleDraw();
|
|
1143
1463
|
}
|
|
1144
1464
|
removeDiagramNode(id) {
|
|
1145
|
-
const node = this.nodes.find((
|
|
1465
|
+
const node = this.nodes.find((n2) => n2.id === id);
|
|
1146
1466
|
if (node === void 0) return;
|
|
1147
1467
|
const snapshot = node.toInit(false);
|
|
1148
1468
|
const cascaded = this.links.map((link, index) => ({ link, index })).filter(({ link }) => link.from === id || link.to === id).map(({ link, index }) => ({ init: link.toInit(), index }));
|
|
@@ -1178,11 +1498,11 @@ var SSDiagram = (() => {
|
|
|
1178
1498
|
}
|
|
1179
1499
|
}
|
|
1180
1500
|
doRemoveNode(id) {
|
|
1181
|
-
const node = this.nodes.find((
|
|
1501
|
+
const node = this.nodes.find((n2) => n2.id === id);
|
|
1182
1502
|
if (node === void 0) return;
|
|
1183
1503
|
const removedLinks = this.links.filter((l) => l.from === id || l.to === id);
|
|
1184
1504
|
this.links = this.links.filter((l) => l.from !== id && l.to !== id);
|
|
1185
|
-
this.nodes = this.nodes.filter((
|
|
1505
|
+
this.nodes = this.nodes.filter((n2) => n2 !== node);
|
|
1186
1506
|
let selectionChanged = this.selectedNodes.delete(node);
|
|
1187
1507
|
if (this.selectedNode === node) {
|
|
1188
1508
|
const remaining = [...this.selectedNodes];
|
|
@@ -1203,7 +1523,7 @@ var SSDiagram = (() => {
|
|
|
1203
1523
|
this.scheduleDraw();
|
|
1204
1524
|
}
|
|
1205
1525
|
moveNode(id, x, y) {
|
|
1206
|
-
const node = this.nodes.find((
|
|
1526
|
+
const node = this.nodes.find((n2) => n2.id === id);
|
|
1207
1527
|
if (node === void 0) return;
|
|
1208
1528
|
const fromX = node.x, fromY = node.y;
|
|
1209
1529
|
this.doMoveNode(id, x, y);
|
|
@@ -1242,7 +1562,7 @@ var SSDiagram = (() => {
|
|
|
1242
1562
|
return true;
|
|
1243
1563
|
}
|
|
1244
1564
|
doMoveNode(id, x, y) {
|
|
1245
|
-
const node = this.nodes.find((
|
|
1565
|
+
const node = this.nodes.find((n2) => n2.id === id);
|
|
1246
1566
|
if (node === void 0) return;
|
|
1247
1567
|
node.x = x;
|
|
1248
1568
|
node.y = y;
|
|
@@ -1298,8 +1618,8 @@ var SSDiagram = (() => {
|
|
|
1298
1618
|
return ok;
|
|
1299
1619
|
}
|
|
1300
1620
|
doAddLink(init, validate = true, index = this.links.length) {
|
|
1301
|
-
const fn = this.nodes.find((
|
|
1302
|
-
const tn = this.nodes.find((
|
|
1621
|
+
const fn = this.nodes.find((n2) => n2.id === init.from);
|
|
1622
|
+
const tn = this.nodes.find((n2) => n2.id === init.to);
|
|
1303
1623
|
if (fn === void 0 || tn === void 0) return false;
|
|
1304
1624
|
const fp = fn.outPorts.find((p) => p.id === init.fromPort);
|
|
1305
1625
|
const tp = tn.inPorts.find((p) => p.id === init.toPort);
|
|
@@ -1402,8 +1722,8 @@ var SSDiagram = (() => {
|
|
|
1402
1722
|
// Group multiple mutations into one undo step (paste of N nodes,
|
|
1403
1723
|
// bulk delete, multi-drag, etc.). Re-entrant: nested calls flatten
|
|
1404
1724
|
// into the outer batch.
|
|
1405
|
-
withTransaction(
|
|
1406
|
-
return this.history.transaction(
|
|
1725
|
+
withTransaction(label2, fn) {
|
|
1726
|
+
return this.history.transaction(label2, fn);
|
|
1407
1727
|
}
|
|
1408
1728
|
record(action) {
|
|
1409
1729
|
this.history.recordApplied({
|
|
@@ -1416,7 +1736,7 @@ var SSDiagram = (() => {
|
|
|
1416
1736
|
if (!this.permissions.deleteSelection) return;
|
|
1417
1737
|
if (this.selectedNodes.size > 0) {
|
|
1418
1738
|
this.withTransaction("delete selection", () => {
|
|
1419
|
-
for (const id of [...this.selectedNodes].map((
|
|
1739
|
+
for (const id of [...this.selectedNodes].map((n2) => n2.id)) this.removeDiagramNode(id);
|
|
1420
1740
|
});
|
|
1421
1741
|
this.selectedNodes.clear();
|
|
1422
1742
|
this.selectedNode = null;
|
|
@@ -1595,7 +1915,7 @@ var SSDiagram = (() => {
|
|
|
1595
1915
|
this.showNodeMessages = show;
|
|
1596
1916
|
this.scheduleDraw();
|
|
1597
1917
|
}
|
|
1598
|
-
updateNodeState(nodeId,
|
|
1918
|
+
updateNodeState(nodeId, label2, mutate) {
|
|
1599
1919
|
const node = this.findNode(nodeId);
|
|
1600
1920
|
if (node === void 0) return false;
|
|
1601
1921
|
const before = node.toInit(false);
|
|
@@ -1613,7 +1933,7 @@ var SSDiagram = (() => {
|
|
|
1613
1933
|
undo: () => {
|
|
1614
1934
|
this.applyNodeSnapshot(nodeId, before);
|
|
1615
1935
|
},
|
|
1616
|
-
label
|
|
1936
|
+
label: label2
|
|
1617
1937
|
});
|
|
1618
1938
|
return true;
|
|
1619
1939
|
}
|
|
@@ -1763,7 +2083,7 @@ var SSDiagram = (() => {
|
|
|
1763
2083
|
}
|
|
1764
2084
|
// Renderer operations consumed by the high-level component.
|
|
1765
2085
|
findNode(id) {
|
|
1766
|
-
return this.nodes.find((
|
|
2086
|
+
return this.nodes.find((n2) => n2.id === id);
|
|
1767
2087
|
}
|
|
1768
2088
|
requestRedraw() {
|
|
1769
2089
|
this.relayout();
|
|
@@ -1789,17 +2109,8 @@ var SSDiagram = (() => {
|
|
|
1789
2109
|
copyContext.drawImage(this.canvas, 0, 0);
|
|
1790
2110
|
return copy;
|
|
1791
2111
|
}
|
|
1792
|
-
const
|
|
1793
|
-
const
|
|
1794
|
-
const padding = scope === "content" ? this.nonNegativeScreenshotNumber(options.padding ?? 32, "padding") : 0;
|
|
1795
|
-
const bounds = scope === "content" ? this.graphBounds() : null;
|
|
1796
|
-
const width = scope === "content" ? Math.max(1, Math.ceil(((bounds?.maxX ?? 1) - (bounds?.minX ?? 0)) * exportScale + padding * 2)) : this.width;
|
|
1797
|
-
const height = scope === "content" ? Math.max(1, Math.ceil(((bounds?.maxY ?? 1) - (bounds?.minY ?? 0)) * exportScale + padding * 2)) : this.height;
|
|
1798
|
-
const pixelWidth = Math.ceil(width * pixelRatio);
|
|
1799
|
-
const pixelHeight = Math.ceil(height * pixelRatio);
|
|
1800
|
-
if (pixelWidth > 16384 || pixelHeight > 16384 || pixelWidth * pixelHeight > 268435456) {
|
|
1801
|
-
throw new RangeError(`ssdiagram: screenshot is too large (${pixelWidth}x${pixelHeight})`);
|
|
1802
|
-
}
|
|
2112
|
+
const layout = this.exportLayout(scope, options);
|
|
2113
|
+
const { pixelRatio, exportScale, padding, bounds, width, height, pixelWidth, pixelHeight } = layout;
|
|
1803
2114
|
const output = document.createElement("canvas");
|
|
1804
2115
|
output.width = pixelWidth;
|
|
1805
2116
|
output.height = pixelHeight;
|
|
@@ -1849,6 +2160,86 @@ var SSDiagram = (() => {
|
|
|
1849
2160
|
}
|
|
1850
2161
|
return output;
|
|
1851
2162
|
}
|
|
2163
|
+
// Everything an export needs to know about size and placement, for either output. Shared on
|
|
2164
|
+
// purpose: a raster and a vector export of the same scope must frame the picture identically,
|
|
2165
|
+
// and two copies of this arithmetic would eventually disagree about padding or where the origin
|
|
2166
|
+
// sits. A block comment here would be lifted onto the next public member in the .d.ts.
|
|
2167
|
+
exportLayout(scope, options) {
|
|
2168
|
+
const pixelRatio = this.positiveScreenshotNumber(options.pixelRatio ?? this.dpr, "pixelRatio");
|
|
2169
|
+
const exportScale = scope === "content" ? this.positiveScreenshotNumber(options.scale ?? 1, "scale") : this.scale;
|
|
2170
|
+
const padding = scope === "content" ? this.nonNegativeScreenshotNumber(options.padding ?? 32, "padding") : 0;
|
|
2171
|
+
const bounds = scope === "content" ? this.graphBounds() : null;
|
|
2172
|
+
const width = scope === "content" ? Math.max(1, Math.ceil(((bounds?.maxX ?? 1) - (bounds?.minX ?? 0)) * exportScale + padding * 2)) : this.width;
|
|
2173
|
+
const height = scope === "content" ? Math.max(1, Math.ceil(((bounds?.maxY ?? 1) - (bounds?.minY ?? 0)) * exportScale + padding * 2)) : this.height;
|
|
2174
|
+
const pixelWidth = Math.ceil(width * pixelRatio);
|
|
2175
|
+
const pixelHeight = Math.ceil(height * pixelRatio);
|
|
2176
|
+
if (pixelWidth > 16384 || pixelHeight > 16384 || pixelWidth * pixelHeight > 268435456) {
|
|
2177
|
+
throw new RangeError(`ssdiagram: screenshot is too large (${pixelWidth}x${pixelHeight})`);
|
|
2178
|
+
}
|
|
2179
|
+
return { pixelRatio, exportScale, padding, bounds, width, height, pixelWidth, pixelHeight };
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* The same picture takeScreenshot produces, as SVG.
|
|
2183
|
+
*
|
|
2184
|
+
* It is the renderer that draws it -- the surface underneath simply records instead of paints --
|
|
2185
|
+
* so the vector output cannot drift from what the screen and the raster export show. Text keeps
|
|
2186
|
+
* the on-screen metrics by measuring through a real 2D context.
|
|
2187
|
+
*
|
|
2188
|
+
* Scale and pixelRatio still frame the picture (they decide the viewBox), but nothing is
|
|
2189
|
+
* resampled: the result is resolution-independent, which is the point of asking for it.
|
|
2190
|
+
*/
|
|
2191
|
+
takeSvg(options = {}) {
|
|
2192
|
+
if (this.destroyed) throw new Error("ssdiagram: cannot export a destroyed diagram");
|
|
2193
|
+
const scope = options.scope ?? "viewport";
|
|
2194
|
+
if (scope !== "viewport" && scope !== "content") {
|
|
2195
|
+
throw new RangeError(`ssdiagram: unsupported screenshot scope "${String(scope)}"`);
|
|
2196
|
+
}
|
|
2197
|
+
const { exportScale, padding, bounds, width, height } = this.exportLayout(scope, options);
|
|
2198
|
+
const metrics = this.canvas.getContext("2d");
|
|
2199
|
+
const surface = new SvgSurface({
|
|
2200
|
+
width,
|
|
2201
|
+
height,
|
|
2202
|
+
background: options.background ?? this.opts.background ?? "#1b1b1f",
|
|
2203
|
+
metrics: metrics ?? void 0
|
|
2204
|
+
});
|
|
2205
|
+
const previous = {
|
|
2206
|
+
ctx: this.ctx,
|
|
2207
|
+
width: this.width,
|
|
2208
|
+
height: this.height,
|
|
2209
|
+
dpr: this.dpr,
|
|
2210
|
+
scale: this.scale,
|
|
2211
|
+
offX: this.offX,
|
|
2212
|
+
offY: this.offY,
|
|
2213
|
+
overviewVisible: this.overviewVisible
|
|
2214
|
+
};
|
|
2215
|
+
try {
|
|
2216
|
+
this.ctx = surface;
|
|
2217
|
+
this.width = width;
|
|
2218
|
+
this.height = height;
|
|
2219
|
+
this.dpr = 1;
|
|
2220
|
+
this.scale = exportScale;
|
|
2221
|
+
this.offX = scope === "content" && bounds !== null ? padding - bounds.minX * exportScale : previous.offX;
|
|
2222
|
+
this.offY = scope === "content" && bounds !== null ? padding - bounds.minY * exportScale : previous.offY;
|
|
2223
|
+
this.overviewVisible = options.includeOverview ?? (scope === "viewport" && previous.overviewVisible);
|
|
2224
|
+
this.draw({
|
|
2225
|
+
grid: options.includeGrid ?? true,
|
|
2226
|
+
overview: this.overviewVisible,
|
|
2227
|
+
selection: options.includeSelection ?? scope === "viewport",
|
|
2228
|
+
runtime: options.includeRuntimeState ?? true,
|
|
2229
|
+
transient: false
|
|
2230
|
+
});
|
|
2231
|
+
} finally {
|
|
2232
|
+
this.ctx = previous.ctx;
|
|
2233
|
+
this.width = previous.width;
|
|
2234
|
+
this.height = previous.height;
|
|
2235
|
+
this.dpr = previous.dpr;
|
|
2236
|
+
this.scale = previous.scale;
|
|
2237
|
+
this.offX = previous.offX;
|
|
2238
|
+
this.offY = previous.offY;
|
|
2239
|
+
this.overviewVisible = previous.overviewVisible;
|
|
2240
|
+
}
|
|
2241
|
+
return surface.toSvg();
|
|
2242
|
+
}
|
|
1852
2243
|
positiveScreenshotNumber(value, name) {
|
|
1853
2244
|
if (!Number.isFinite(value) || value <= 0) {
|
|
1854
2245
|
throw new RangeError(`ssdiagram: screenshot ${name} must be a positive finite number`);
|
|
@@ -1969,7 +2360,7 @@ var SSDiagram = (() => {
|
|
|
1969
2360
|
return this.toScreen(wx, wy);
|
|
1970
2361
|
}
|
|
1971
2362
|
selectNodeById(id) {
|
|
1972
|
-
const node = id === null ? null : this.nodes.find((
|
|
2363
|
+
const node = id === null ? null : this.nodes.find((n2) => n2.id === id) ?? null;
|
|
1973
2364
|
this.selectNode(node);
|
|
1974
2365
|
}
|
|
1975
2366
|
setZoom(scale) {
|
|
@@ -2017,25 +2408,25 @@ var SSDiagram = (() => {
|
|
|
2017
2408
|
this.opts.typeColors = { ...colors };
|
|
2018
2409
|
this.scheduleDraw();
|
|
2019
2410
|
}
|
|
2020
|
-
setTheme(
|
|
2021
|
-
if (
|
|
2022
|
-
if (
|
|
2023
|
-
if (
|
|
2024
|
-
if (
|
|
2025
|
-
if (
|
|
2026
|
-
if (
|
|
2027
|
-
if (
|
|
2028
|
-
if (
|
|
2411
|
+
setTheme(t2) {
|
|
2412
|
+
if (t2.background !== void 0) this.opts.background = t2.background;
|
|
2413
|
+
if (t2.gridColor !== void 0) this.opts.gridColor = t2.gridColor;
|
|
2414
|
+
if (t2.linkMaxLightness !== void 0) this.opts.linkMaxLightness = t2.linkMaxLightness;
|
|
2415
|
+
if (t2.typedLinkColors !== void 0) this.opts.typedLinkColors = t2.typedLinkColors;
|
|
2416
|
+
if (t2.overviewBackground !== void 0) this.opts.overviewBackground = t2.overviewBackground;
|
|
2417
|
+
if (t2.overviewBorderColor !== void 0) this.opts.overviewBorderColor = t2.overviewBorderColor;
|
|
2418
|
+
if (t2.overviewViewportColor !== void 0) this.opts.overviewViewportColor = t2.overviewViewportColor;
|
|
2419
|
+
if (t2.overviewViewportFill !== void 0) this.opts.overviewViewportFill = t2.overviewViewportFill;
|
|
2029
2420
|
this.scheduleDraw();
|
|
2030
2421
|
}
|
|
2031
2422
|
graphBounds() {
|
|
2032
2423
|
if (this.nodes.length === 0) return null;
|
|
2033
2424
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
2034
|
-
for (const
|
|
2035
|
-
minX = Math.min(minX,
|
|
2036
|
-
minY = Math.min(minY,
|
|
2037
|
-
maxX = Math.max(maxX,
|
|
2038
|
-
maxY = Math.max(maxY,
|
|
2425
|
+
for (const n2 of this.nodes) {
|
|
2426
|
+
minX = Math.min(minX, n2.x);
|
|
2427
|
+
minY = Math.min(minY, n2.y);
|
|
2428
|
+
maxX = Math.max(maxX, n2.x + n2.w);
|
|
2429
|
+
maxY = Math.max(maxY, n2.y + n2.h);
|
|
2039
2430
|
}
|
|
2040
2431
|
return { minX, minY, maxX, maxY };
|
|
2041
2432
|
}
|
|
@@ -2086,26 +2477,26 @@ var SSDiagram = (() => {
|
|
|
2086
2477
|
this.canvas.remove();
|
|
2087
2478
|
}
|
|
2088
2479
|
// ---- geometry ---------------------------------------------------
|
|
2089
|
-
layoutNode(
|
|
2480
|
+
layoutNode(n2) {
|
|
2090
2481
|
const ctx = this.ctx;
|
|
2091
2482
|
ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
|
|
2092
|
-
const titleW = ctx.measureText(
|
|
2093
|
-
const rows = Math.max(
|
|
2094
|
-
|
|
2095
|
-
|
|
2483
|
+
const titleW = ctx.measureText(n2.name).width;
|
|
2484
|
+
const rows = Math.max(n2.inPorts.length, n2.outPorts.length, 1);
|
|
2485
|
+
n2.w = Math.max(170, Math.ceil(titleW) + 28);
|
|
2486
|
+
n2.h = Math.max(48, rows * PORT_ROW_H + 16);
|
|
2096
2487
|
const off = PORT_SQ / 2;
|
|
2097
2488
|
const place = (ports, x) => {
|
|
2098
|
-
const startY =
|
|
2489
|
+
const startY = n2.y + (n2.h - ports.length * PORT_ROW_H) / 2 + PORT_ROW_H / 2;
|
|
2099
2490
|
ports.forEach((p, i) => {
|
|
2100
2491
|
p.cx = x;
|
|
2101
2492
|
p.cy = startY + i * PORT_ROW_H;
|
|
2102
2493
|
});
|
|
2103
2494
|
};
|
|
2104
|
-
place(
|
|
2105
|
-
place(
|
|
2495
|
+
place(n2.inPorts, n2.x - off);
|
|
2496
|
+
place(n2.outPorts, n2.x + n2.w + off);
|
|
2106
2497
|
}
|
|
2107
2498
|
relayout() {
|
|
2108
|
-
for (const
|
|
2499
|
+
for (const n2 of this.nodes) this.layoutNode(n2);
|
|
2109
2500
|
}
|
|
2110
2501
|
toScreen(wx, wy) {
|
|
2111
2502
|
return [wx * this.scale + this.offX, wy * this.scale + this.offY];
|
|
@@ -2134,9 +2525,9 @@ var SSDiagram = (() => {
|
|
|
2134
2525
|
// ---- hit testing (world coords) ---------------------------------
|
|
2135
2526
|
portAt(wx, wy) {
|
|
2136
2527
|
for (let i = this.nodes.length - 1; i >= 0; i -= 1) {
|
|
2137
|
-
const
|
|
2138
|
-
for (const p of [...
|
|
2139
|
-
if ((wx - p.cx) ** 2 + (wy - p.cy) ** 2 <= (PORT_R + 4) ** 2) return { node:
|
|
2528
|
+
const n2 = this.nodes[i];
|
|
2529
|
+
for (const p of [...n2.inPorts, ...n2.outPorts]) {
|
|
2530
|
+
if ((wx - p.cx) ** 2 + (wy - p.cy) ** 2 <= (PORT_R + 4) ** 2) return { node: n2, port: p };
|
|
2140
2531
|
}
|
|
2141
2532
|
}
|
|
2142
2533
|
return null;
|
|
@@ -2157,8 +2548,8 @@ var SSDiagram = (() => {
|
|
|
2157
2548
|
}
|
|
2158
2549
|
nodeAt(wx, wy) {
|
|
2159
2550
|
for (let i = this.nodes.length - 1; i >= 0; i -= 1) {
|
|
2160
|
-
const
|
|
2161
|
-
if (wx >=
|
|
2551
|
+
const n2 = this.nodes[i];
|
|
2552
|
+
if (wx >= n2.x && wx <= n2.x + n2.w && wy >= n2.y && wy <= n2.y + n2.h) return n2;
|
|
2162
2553
|
}
|
|
2163
2554
|
return null;
|
|
2164
2555
|
}
|
|
@@ -2167,9 +2558,9 @@ var SSDiagram = (() => {
|
|
|
2167
2558
|
obstaclesFor(exclude) {
|
|
2168
2559
|
const inf = 12;
|
|
2169
2560
|
const out = [];
|
|
2170
|
-
for (const
|
|
2171
|
-
if (exclude.has(
|
|
2172
|
-
out.push([
|
|
2561
|
+
for (const n2 of this.nodes) {
|
|
2562
|
+
if (exclude.has(n2.id)) continue;
|
|
2563
|
+
out.push([n2.x - inf, n2.y - inf, n2.w + inf * 2, n2.h + inf * 2]);
|
|
2173
2564
|
}
|
|
2174
2565
|
return out;
|
|
2175
2566
|
}
|
|
@@ -2191,7 +2582,7 @@ var SSDiagram = (() => {
|
|
|
2191
2582
|
return null;
|
|
2192
2583
|
}
|
|
2193
2584
|
endpoint(l, end) {
|
|
2194
|
-
const node = this.nodes.find((
|
|
2585
|
+
const node = this.nodes.find((n2) => n2.id === (end === "from" ? l.from : l.to));
|
|
2195
2586
|
if (node === void 0) return null;
|
|
2196
2587
|
const port = end === "from" ? node.outPorts.find((p) => p.id === l.fromPort) : node.inPorts.find((p) => p.id === l.toPort);
|
|
2197
2588
|
if (port === void 0) return null;
|
|
@@ -2289,39 +2680,39 @@ var SSDiagram = (() => {
|
|
|
2289
2680
|
}
|
|
2290
2681
|
let best = null;
|
|
2291
2682
|
let bestD = SNAP_PX;
|
|
2292
|
-
for (const
|
|
2293
|
-
const ports = direction === "in" ?
|
|
2683
|
+
for (const n2 of this.nodes) {
|
|
2684
|
+
const ports = direction === "in" ? n2.inPorts : n2.outPorts;
|
|
2294
2685
|
for (const p of ports) {
|
|
2295
|
-
if (!accepts(
|
|
2686
|
+
if (!accepts(n2, p)) continue;
|
|
2296
2687
|
const [sx, sy] = this.toScreen(p.cx, p.cy);
|
|
2297
2688
|
const dpx = Math.hypot(sx - this.cursor.x, sy - this.cursor.y);
|
|
2298
2689
|
if (dpx <= bestD) {
|
|
2299
2690
|
bestD = dpx;
|
|
2300
|
-
best = { node:
|
|
2691
|
+
best = { node: n2, port: p };
|
|
2301
2692
|
}
|
|
2302
2693
|
}
|
|
2303
2694
|
}
|
|
2304
2695
|
return best;
|
|
2305
2696
|
}
|
|
2306
|
-
selectNode(
|
|
2697
|
+
selectNode(n2) {
|
|
2307
2698
|
this.selectedLink = null;
|
|
2308
2699
|
this.selectedPort = null;
|
|
2309
2700
|
this.selectedNodes.clear();
|
|
2310
|
-
if (
|
|
2311
|
-
this.selectedNode =
|
|
2312
|
-
this.emit("nodeSelected", { node:
|
|
2701
|
+
if (n2 !== null) this.selectedNodes.add(n2);
|
|
2702
|
+
this.selectedNode = n2;
|
|
2703
|
+
this.emit("nodeSelected", { node: n2, selected: n2 !== null });
|
|
2313
2704
|
this.emitSelectionChanged();
|
|
2314
2705
|
this.scheduleDraw();
|
|
2315
2706
|
}
|
|
2316
|
-
toggleSelect(
|
|
2707
|
+
toggleSelect(n2) {
|
|
2317
2708
|
this.selectedLink = null;
|
|
2318
2709
|
this.selectedPort = null;
|
|
2319
|
-
if (this.selectedNodes.has(
|
|
2320
|
-
this.selectedNodes.delete(
|
|
2321
|
-
if (this.selectedNode ===
|
|
2710
|
+
if (this.selectedNodes.has(n2)) {
|
|
2711
|
+
this.selectedNodes.delete(n2);
|
|
2712
|
+
if (this.selectedNode === n2) this.selectedNode = null;
|
|
2322
2713
|
} else {
|
|
2323
|
-
this.selectedNodes.add(
|
|
2324
|
-
this.selectedNode =
|
|
2714
|
+
this.selectedNodes.add(n2);
|
|
2715
|
+
this.selectedNode = n2;
|
|
2325
2716
|
}
|
|
2326
2717
|
this.emit("nodeSelected", { node: this.selectedNode, selected: this.selectedNodes.size > 0 });
|
|
2327
2718
|
this.emitSelectionChanged();
|
|
@@ -2365,10 +2756,10 @@ var SSDiagram = (() => {
|
|
|
2365
2756
|
this.clip = null;
|
|
2366
2757
|
return null;
|
|
2367
2758
|
}
|
|
2368
|
-
const ids = new Set([...this.selectedNodes].map((
|
|
2759
|
+
const ids = new Set([...this.selectedNodes].map((n2) => n2.id));
|
|
2369
2760
|
const all = this.saveDocument();
|
|
2370
2761
|
this.clip = createDiagramDocument({
|
|
2371
|
-
nodes: all.nodes.filter((
|
|
2762
|
+
nodes: all.nodes.filter((n2) => ids.has(n2.id)),
|
|
2372
2763
|
links: all.links.filter((link) => ids.has(link.from.nodeId) && ids.has(link.to.nodeId))
|
|
2373
2764
|
});
|
|
2374
2765
|
return parseDiagramDocument(this.clip);
|
|
@@ -2409,12 +2800,12 @@ var SSDiagram = (() => {
|
|
|
2409
2800
|
}
|
|
2410
2801
|
for (const l of clipboard.links) {
|
|
2411
2802
|
const f = map.get(l.from.nodeId);
|
|
2412
|
-
const
|
|
2413
|
-
if (f !== void 0 &&
|
|
2803
|
+
const t2 = map.get(l.to.nodeId);
|
|
2804
|
+
if (f !== void 0 && t2 !== void 0)
|
|
2414
2805
|
this.addLink({
|
|
2415
2806
|
from: f,
|
|
2416
2807
|
fromPort: l.from.portId,
|
|
2417
|
-
to:
|
|
2808
|
+
to: t2,
|
|
2418
2809
|
toPort: l.to.portId,
|
|
2419
2810
|
metadata: l.metadata
|
|
2420
2811
|
});
|
|
@@ -2718,7 +3109,7 @@ var SSDiagram = (() => {
|
|
|
2718
3109
|
const ry1 = Math.max(this.rubber.y0, this.rubber.y);
|
|
2719
3110
|
this.rubber = null;
|
|
2720
3111
|
if (rx1 - rx0 > 3 || ry1 - ry0 > 3) {
|
|
2721
|
-
const inRect = this.nodes.filter((
|
|
3112
|
+
const inRect = this.nodes.filter((n2) => n2.x < rx1 && n2.x + n2.w > rx0 && n2.y < ry1 && n2.y + n2.h > ry0);
|
|
2722
3113
|
const merge = e.shiftKey ? [...this.selectedNodes, ...inRect] : inRect;
|
|
2723
3114
|
this.setSelection([...new Set(merge)]);
|
|
2724
3115
|
}
|
|
@@ -2951,7 +3342,7 @@ var SSDiagram = (() => {
|
|
|
2951
3342
|
const a = this.endpoint(l, "from");
|
|
2952
3343
|
const b = this.endpoint(l, "to");
|
|
2953
3344
|
if (a === null || b === null) continue;
|
|
2954
|
-
const fp = this.nodes.find((
|
|
3345
|
+
const fp = this.nodes.find((n2) => n2.id === l.from)?.outPorts.find((p) => p.id === l.fromPort);
|
|
2955
3346
|
const baseColor = (this.opts.typedLinkColors ?? false) && fp !== void 0 ? this.portColor(fp.type) : this.portColor("");
|
|
2956
3347
|
const state = options.selection && l === this.selectedLink ? "sel" : options.transient && (l === this.hoveredLink || hoveredNode !== null && (l.from === hoveredNode.id || l.to === hoveredNode.id)) ? "hov" : "norm";
|
|
2957
3348
|
const color = state === "sel" ? this.selectionColor() : state === "hov" ? this.linkHoverColor() : baseColor;
|
|
@@ -2967,7 +3358,7 @@ var SSDiagram = (() => {
|
|
|
2967
3358
|
}
|
|
2968
3359
|
}
|
|
2969
3360
|
if (options.transient && (this.linking !== null || this.relinking !== null)) this.drawPendingLink();
|
|
2970
|
-
for (const
|
|
3361
|
+
for (const n2 of this.nodes) this.drawNode(n2, options.selection && this.selectedNodes.has(n2), options);
|
|
2971
3362
|
if (options.selection && this.permissions.createLinks && this.relinking === null)
|
|
2972
3363
|
this.drawSelectedLinkEndpoints();
|
|
2973
3364
|
if (options.transient && this.rubber !== null) {
|
|
@@ -2985,7 +3376,7 @@ var SSDiagram = (() => {
|
|
|
2985
3376
|
if (options.overview) this.drawOverview();
|
|
2986
3377
|
if (options.runtime) this.drawGlobalError();
|
|
2987
3378
|
if (options.transient) this.drawTooltip();
|
|
2988
|
-
if (options.transient && (this.introStart !== null || this.globalErrorFlashStart !== null || this.nodes.some((
|
|
3379
|
+
if (options.transient && (this.introStart !== null || this.globalErrorFlashStart !== null || this.nodes.some((n2) => n2.errorFlashStart !== null)))
|
|
2989
3380
|
this.scheduleDraw();
|
|
2990
3381
|
}
|
|
2991
3382
|
drawGlobalError() {
|
|
@@ -3049,17 +3440,17 @@ ${runtime.error}`;
|
|
|
3049
3440
|
isError = true;
|
|
3050
3441
|
}
|
|
3051
3442
|
} else if (this.hoverNode !== null) {
|
|
3052
|
-
const
|
|
3053
|
-
const errors = [
|
|
3443
|
+
const n2 = this.hoverNode;
|
|
3444
|
+
const errors = [n2.runtimeError, n2.loadError].filter((value) => value.length > 0);
|
|
3054
3445
|
if (errors.length > 0) {
|
|
3055
3446
|
text = errors.join("\n");
|
|
3056
3447
|
isError = true;
|
|
3057
|
-
} else if (this.showNodeMessages &&
|
|
3058
|
-
text =
|
|
3059
|
-
} else if (
|
|
3060
|
-
text =
|
|
3448
|
+
} else if (this.showNodeMessages && n2.message.length > 0) {
|
|
3449
|
+
text = n2.message;
|
|
3450
|
+
} else if (n2.description.length > 0) {
|
|
3451
|
+
text = n2.description;
|
|
3061
3452
|
} else {
|
|
3062
|
-
text =
|
|
3453
|
+
text = n2.typeId && n2.typeId !== n2.name ? `${n2.name} [${n2.typeId}]` : n2.name;
|
|
3063
3454
|
}
|
|
3064
3455
|
} else {
|
|
3065
3456
|
return;
|
|
@@ -3172,24 +3563,24 @@ ${runtime.error}`;
|
|
|
3172
3563
|
img.src = url;
|
|
3173
3564
|
return null;
|
|
3174
3565
|
}
|
|
3175
|
-
drawNode(
|
|
3566
|
+
drawNode(n2, selected, options) {
|
|
3176
3567
|
const ctx = this.ctx;
|
|
3177
|
-
const hasLoadError = options.runtime &&
|
|
3178
|
-
const hasRuntimeError = options.runtime &&
|
|
3179
|
-
const runtime = options.runtime ? this.runtimeState.nodes[
|
|
3180
|
-
const active = options.runtime && (this.runtimeState.activeNodeId ===
|
|
3181
|
-
roundRect(ctx,
|
|
3182
|
-
ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? "#ffd1dc" :
|
|
3568
|
+
const hasLoadError = options.runtime && n2.loadError.length > 0;
|
|
3569
|
+
const hasRuntimeError = options.runtime && n2.runtimeError.length > 0;
|
|
3570
|
+
const runtime = options.runtime ? this.runtimeState.nodes[n2.id] : void 0;
|
|
3571
|
+
const active = options.runtime && (this.runtimeState.activeNodeId === n2.id || runtime?.active === true);
|
|
3572
|
+
roundRect(ctx, n2.x, n2.y, n2.w, n2.h, 6);
|
|
3573
|
+
ctx.fillStyle = hasLoadError ? ERROR_BACKGROUND : active ? "#ffd1dc" : n2.color;
|
|
3183
3574
|
ctx.fill();
|
|
3184
3575
|
ctx.lineWidth = selected ? 2 : 1.5;
|
|
3185
|
-
ctx.strokeStyle = selected ? this.selectionColor() :
|
|
3576
|
+
ctx.strokeStyle = selected ? this.selectionColor() : n2.border;
|
|
3186
3577
|
ctx.stroke();
|
|
3187
3578
|
if (hasLoadError || hasRuntimeError) {
|
|
3188
3579
|
let alpha = 1;
|
|
3189
|
-
if (options.transient && hasRuntimeError &&
|
|
3190
|
-
const elapsed = performance.now() -
|
|
3580
|
+
if (options.transient && hasRuntimeError && n2.errorFlashStart !== null) {
|
|
3581
|
+
const elapsed = performance.now() - n2.errorFlashStart;
|
|
3191
3582
|
if (elapsed >= ERROR_FLASH_MS) {
|
|
3192
|
-
|
|
3583
|
+
n2.errorFlashStart = null;
|
|
3193
3584
|
} else {
|
|
3194
3585
|
alpha = 0.12 + 0.88 * (0.5 + 0.5 * Math.sin(
|
|
3195
3586
|
elapsed / ERROR_FLASH_MS * Math.PI * 6 - Math.PI / 2
|
|
@@ -3198,30 +3589,30 @@ ${runtime.error}`;
|
|
|
3198
3589
|
}
|
|
3199
3590
|
ctx.save();
|
|
3200
3591
|
ctx.globalAlpha *= alpha;
|
|
3201
|
-
roundRect(ctx,
|
|
3592
|
+
roundRect(ctx, n2.x, n2.y, n2.w, n2.h, 6);
|
|
3202
3593
|
ctx.lineWidth = hasRuntimeError ? 3 : 2;
|
|
3203
3594
|
ctx.strokeStyle = ERROR_RED;
|
|
3204
3595
|
ctx.stroke();
|
|
3205
3596
|
ctx.restore();
|
|
3206
3597
|
}
|
|
3207
3598
|
const iconW = 18;
|
|
3208
|
-
if (
|
|
3209
|
-
const img = this.getIcon(
|
|
3599
|
+
if (n2.icon) {
|
|
3600
|
+
const img = this.getIcon(n2.icon);
|
|
3210
3601
|
if (img) {
|
|
3211
3602
|
try {
|
|
3212
|
-
ctx.drawImage(img,
|
|
3603
|
+
ctx.drawImage(img, n2.x + 8, n2.y + (n2.h - iconW) / 2, iconW, iconW);
|
|
3213
3604
|
} catch {
|
|
3214
3605
|
}
|
|
3215
3606
|
}
|
|
3216
3607
|
}
|
|
3217
|
-
const titleShift =
|
|
3608
|
+
const titleShift = n2.icon ? iconW + 4 : 0;
|
|
3218
3609
|
ctx.fillStyle = hasLoadError ? "#ffffff" : "#1b1b1b";
|
|
3219
3610
|
ctx.font = "600 12px Segoe UI, Tahoma, sans-serif";
|
|
3220
3611
|
ctx.textAlign = "center";
|
|
3221
3612
|
ctx.textBaseline = "middle";
|
|
3222
|
-
ctx.fillText(
|
|
3223
|
-
for (const p of
|
|
3224
|
-
for (const p of
|
|
3613
|
+
ctx.fillText(n2.name, n2.x + titleShift + (n2.w - titleShift) / 2, n2.y + n2.h / 2, n2.w - titleShift - 14);
|
|
3614
|
+
for (const p of n2.inPorts) this.drawPort(n2, p, options);
|
|
3615
|
+
for (const p of n2.outPorts) this.drawPort(n2, p, options);
|
|
3225
3616
|
}
|
|
3226
3617
|
drawPort(node, p, options) {
|
|
3227
3618
|
const ctx = this.ctx;
|
|
@@ -3319,9 +3710,9 @@ ${runtime.error}`;
|
|
|
3319
3710
|
ctx.setLineDash([]);
|
|
3320
3711
|
}
|
|
3321
3712
|
drawArrow(pts, color) {
|
|
3322
|
-
const
|
|
3323
|
-
const [px, py] = pts[
|
|
3324
|
-
const [ex, ey] = pts[
|
|
3713
|
+
const n2 = pts.length;
|
|
3714
|
+
const [px, py] = pts[n2 - 2];
|
|
3715
|
+
const [ex, ey] = pts[n2 - 1];
|
|
3325
3716
|
const ang = Math.atan2(ey - py, ex - px);
|
|
3326
3717
|
const ctx = this.ctx;
|
|
3327
3718
|
ctx.save();
|
|
@@ -3450,12 +3841,12 @@ ${runtime.error}`;
|
|
|
3450
3841
|
ctx.lineWidth = 1;
|
|
3451
3842
|
ctx.stroke();
|
|
3452
3843
|
ctx.clip();
|
|
3453
|
-
for (const
|
|
3454
|
-
ctx.fillStyle =
|
|
3455
|
-
ctx.fillRect(mx(
|
|
3456
|
-
ctx.strokeStyle =
|
|
3844
|
+
for (const n2 of this.nodes) {
|
|
3845
|
+
ctx.fillStyle = n2.color;
|
|
3846
|
+
ctx.fillRect(mx(n2.x), my(n2.y), Math.max(2, n2.w * s), Math.max(2, n2.h * s));
|
|
3847
|
+
ctx.strokeStyle = n2.border;
|
|
3457
3848
|
ctx.lineWidth = 1;
|
|
3458
|
-
ctx.strokeRect(mx(
|
|
3849
|
+
ctx.strokeRect(mx(n2.x), my(n2.y), Math.max(2, n2.w * s), Math.max(2, n2.h * s));
|
|
3459
3850
|
}
|
|
3460
3851
|
const [vx0, vy0] = this.toWorld(0, 0);
|
|
3461
3852
|
const [vx1, vy1] = this.toWorld(this.width, this.height);
|
|
@@ -3614,11 +4005,11 @@ ${runtime.error}`;
|
|
|
3614
4005
|
cols = Math.ceil((maxX - minX) / cell);
|
|
3615
4006
|
rows = Math.ceil((maxY - minY) / cell);
|
|
3616
4007
|
}
|
|
3617
|
-
const
|
|
3618
|
-
if (
|
|
4008
|
+
const n2 = cols * rows;
|
|
4009
|
+
if (n2 <= 0 || n2 > 8e4) return null;
|
|
3619
4010
|
const cx = (c) => minX + (c + 0.5) * cell;
|
|
3620
4011
|
const cy = (r) => minY + (r + 0.5) * cell;
|
|
3621
|
-
const blocked = new Uint8Array(
|
|
4012
|
+
const blocked = new Uint8Array(n2);
|
|
3622
4013
|
for (const r of rects) {
|
|
3623
4014
|
const c0 = Math.max(0, Math.floor((r[0] - minX) / cell));
|
|
3624
4015
|
const c1 = Math.min(cols - 1, Math.floor((r[0] + r[2] - minX) / cell));
|
|
@@ -3635,17 +4026,17 @@ ${runtime.error}`;
|
|
|
3635
4026
|
const gi = gr * cols + gc;
|
|
3636
4027
|
blocked[si] = 0;
|
|
3637
4028
|
blocked[gi] = 0;
|
|
3638
|
-
const g = new Float64Array(
|
|
3639
|
-
const prev = new Int32Array(
|
|
3640
|
-
const dir = new Int8Array(
|
|
4029
|
+
const g = new Float64Array(n2).fill(Infinity);
|
|
4030
|
+
const prev = new Int32Array(n2).fill(-1);
|
|
4031
|
+
const dir = new Int8Array(n2).fill(-1);
|
|
3641
4032
|
g[si] = 0;
|
|
3642
4033
|
const heap = [si];
|
|
3643
|
-
const f = new Float64Array(
|
|
4034
|
+
const f = new Float64Array(n2);
|
|
3644
4035
|
f[si] = (Math.abs(gc - sc) + Math.abs(gr - sr)) * A_STEP;
|
|
3645
4036
|
const swap = (i, j) => {
|
|
3646
|
-
const
|
|
4037
|
+
const t2 = heap[i];
|
|
3647
4038
|
heap[i] = heap[j];
|
|
3648
|
-
heap[j] =
|
|
4039
|
+
heap[j] = t2;
|
|
3649
4040
|
};
|
|
3650
4041
|
const push = (v) => {
|
|
3651
4042
|
heap.push(v);
|
|
@@ -3676,7 +4067,7 @@ ${runtime.error}`;
|
|
|
3676
4067
|
}
|
|
3677
4068
|
return top;
|
|
3678
4069
|
};
|
|
3679
|
-
const seen = new Uint8Array(
|
|
4070
|
+
const seen = new Uint8Array(n2);
|
|
3680
4071
|
while (heap.length > 0) {
|
|
3681
4072
|
const cur = pop();
|
|
3682
4073
|
if (cur === gi) break;
|
|
@@ -3734,13 +4125,267 @@ ${runtime.error}`;
|
|
|
3734
4125
|
const dx = x2 - x1;
|
|
3735
4126
|
const dy = y2 - y1;
|
|
3736
4127
|
const len2 = dx * dx + dy * dy;
|
|
3737
|
-
let
|
|
3738
|
-
|
|
3739
|
-
const cx = x1 +
|
|
3740
|
-
const cy = y1 +
|
|
4128
|
+
let t2 = len2 === 0 ? 0 : ((px - x1) * dx + (py - y1) * dy) / len2;
|
|
4129
|
+
t2 = clamp(t2, 0, 1);
|
|
4130
|
+
const cx = x1 + t2 * dx;
|
|
4131
|
+
const cy = y1 + t2 * dy;
|
|
3741
4132
|
return Math.hypot(px - cx, py - cy);
|
|
3742
4133
|
}
|
|
3743
4134
|
|
|
4135
|
+
// src/i18n.ts
|
|
4136
|
+
var i18n = typeof window === "undefined" ? {} : window.__designerI18n ?? {};
|
|
4137
|
+
function t(key, fallback) {
|
|
4138
|
+
return i18n[key] ?? fallback;
|
|
4139
|
+
}
|
|
4140
|
+
|
|
4141
|
+
// src/diagram/context-menu.ts
|
|
4142
|
+
var ROOT_CLASS = "ssdiagram-context-menu";
|
|
4143
|
+
var ITEM_CLASS = "ssdiagram-context-menu-item";
|
|
4144
|
+
var SUBMENU_CLASS = "ssdiagram-context-menu-submenu";
|
|
4145
|
+
var SEPARATOR_CLASS = "ssdiagram-context-menu-separator";
|
|
4146
|
+
var LABEL_CLASS = "ssdiagram-context-menu-label";
|
|
4147
|
+
var ARROW_CLASS = "ssdiagram-context-menu-arrow";
|
|
4148
|
+
var SEPARATE_AFTER = /* @__PURE__ */ new Set(["redo", "paste", "delete"]);
|
|
4149
|
+
function label(id) {
|
|
4150
|
+
switch (id) {
|
|
4151
|
+
case "undo":
|
|
4152
|
+
return t("undo", "Undo");
|
|
4153
|
+
case "redo":
|
|
4154
|
+
return t("redo", "Redo");
|
|
4155
|
+
case "cut":
|
|
4156
|
+
return t("cut", "Cut");
|
|
4157
|
+
case "copy":
|
|
4158
|
+
return t("copy", "Copy");
|
|
4159
|
+
case "paste":
|
|
4160
|
+
return t("paste", "Paste");
|
|
4161
|
+
case "open":
|
|
4162
|
+
return t("ctxOpen", "Open");
|
|
4163
|
+
case "delete":
|
|
4164
|
+
return t("delete", "Delete");
|
|
4165
|
+
case "export":
|
|
4166
|
+
return t("ctxExportAs", "Export as");
|
|
4167
|
+
case "exportDocument":
|
|
4168
|
+
return t("ctxExportDocument", "Scheme");
|
|
4169
|
+
case "exportPng":
|
|
4170
|
+
return t("ctxExportPng", "PNG image");
|
|
4171
|
+
case "exportSvg":
|
|
4172
|
+
return t("ctxExportSvg", "SVG image");
|
|
4173
|
+
case "properties":
|
|
4174
|
+
return t("properties", "Properties");
|
|
4175
|
+
case "help":
|
|
4176
|
+
return t("ctxHelp", "Help");
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
var PANEL_STYLE = {
|
|
4180
|
+
position: "fixed",
|
|
4181
|
+
zIndex: "2147483000",
|
|
4182
|
+
minWidth: "176px",
|
|
4183
|
+
padding: "4px",
|
|
4184
|
+
border: "1px solid var(--ssdiagram-menu-border, var(--ssdiagram-control-border, var(--t-border, #3a4250)))",
|
|
4185
|
+
borderRadius: "7px",
|
|
4186
|
+
background: "var(--ssdiagram-menu-background, var(--ssdiagram-control-background, var(--t-panel, #181a20)))",
|
|
4187
|
+
color: "var(--ssdiagram-menu-color, var(--ssdiagram-control-color, var(--t-text, #eaecef)))",
|
|
4188
|
+
boxShadow: "0 12px 32px rgba(0, 0, 0, 0.45)",
|
|
4189
|
+
font: 'var(--ssdiagram-menu-font, 12.5px/1.35 system-ui, -apple-system, "Segoe UI", sans-serif)',
|
|
4190
|
+
boxSizing: "border-box",
|
|
4191
|
+
userSelect: "none"
|
|
4192
|
+
};
|
|
4193
|
+
var ITEM_STYLE = {
|
|
4194
|
+
display: "flex",
|
|
4195
|
+
alignItems: "center",
|
|
4196
|
+
gap: "10px",
|
|
4197
|
+
width: "100%",
|
|
4198
|
+
padding: "6px 10px",
|
|
4199
|
+
border: "0",
|
|
4200
|
+
borderRadius: "4px",
|
|
4201
|
+
background: "transparent",
|
|
4202
|
+
color: "inherit",
|
|
4203
|
+
font: "inherit",
|
|
4204
|
+
textAlign: "left",
|
|
4205
|
+
whiteSpace: "nowrap",
|
|
4206
|
+
cursor: "pointer",
|
|
4207
|
+
boxSizing: "border-box"
|
|
4208
|
+
};
|
|
4209
|
+
var HOVER_BACKGROUND = "var(--ssdiagram-menu-hover, var(--t-hover, rgba(255, 255, 255, 0.08)))";
|
|
4210
|
+
var DISABLED_COLOR = "var(--ssdiagram-menu-disabled-color, var(--t-text-dim, #848e9c))";
|
|
4211
|
+
var ContextMenuView = class {
|
|
4212
|
+
constructor(options) {
|
|
4213
|
+
this.root = null;
|
|
4214
|
+
this.submenu = null;
|
|
4215
|
+
this.detach = [];
|
|
4216
|
+
this.container = options.container;
|
|
4217
|
+
this.execute = options.execute;
|
|
4218
|
+
}
|
|
4219
|
+
get isOpen() {
|
|
4220
|
+
return this.root !== null;
|
|
4221
|
+
}
|
|
4222
|
+
show(x, y, items) {
|
|
4223
|
+
this.hide();
|
|
4224
|
+
const owner = this.container.ownerDocument ?? document;
|
|
4225
|
+
const root = this.panel(owner);
|
|
4226
|
+
root.className = ROOT_CLASS;
|
|
4227
|
+
root.setAttribute("role", "menu");
|
|
4228
|
+
items.forEach((item, index) => {
|
|
4229
|
+
if ("group" in item) root.appendChild(this.groupEntry(owner, item));
|
|
4230
|
+
else root.appendChild(this.commandEntry(owner, item.command, item.enabled, true));
|
|
4231
|
+
const id = "group" in item ? item.group : item.command;
|
|
4232
|
+
if (SEPARATE_AFTER.has(id) && index < items.length - 1) root.appendChild(this.separator(owner));
|
|
4233
|
+
});
|
|
4234
|
+
this.container.appendChild(root);
|
|
4235
|
+
this.root = root;
|
|
4236
|
+
this.place(root, x, y);
|
|
4237
|
+
this.watchForDismissal(owner);
|
|
4238
|
+
}
|
|
4239
|
+
hide() {
|
|
4240
|
+
for (const dispose of this.detach.splice(0)) dispose();
|
|
4241
|
+
this.submenu = null;
|
|
4242
|
+
this.root?.remove();
|
|
4243
|
+
this.root = null;
|
|
4244
|
+
}
|
|
4245
|
+
destroy() {
|
|
4246
|
+
this.hide();
|
|
4247
|
+
}
|
|
4248
|
+
panel(owner) {
|
|
4249
|
+
const panel = owner.createElement("div");
|
|
4250
|
+
Object.assign(panel.style, PANEL_STYLE);
|
|
4251
|
+
return panel;
|
|
4252
|
+
}
|
|
4253
|
+
separator(owner) {
|
|
4254
|
+
const rule = owner.createElement("div");
|
|
4255
|
+
rule.className = SEPARATOR_CLASS;
|
|
4256
|
+
Object.assign(rule.style, {
|
|
4257
|
+
height: "1px",
|
|
4258
|
+
margin: "4px 6px",
|
|
4259
|
+
background: "var(--ssdiagram-menu-border, var(--t-border, #3a4250))"
|
|
4260
|
+
});
|
|
4261
|
+
return rule;
|
|
4262
|
+
}
|
|
4263
|
+
item(owner, text, enabled, arrow) {
|
|
4264
|
+
const button = owner.createElement("button");
|
|
4265
|
+
button.type = "button";
|
|
4266
|
+
button.className = ITEM_CLASS;
|
|
4267
|
+
button.setAttribute("role", "menuitem");
|
|
4268
|
+
Object.assign(button.style, ITEM_STYLE);
|
|
4269
|
+
if (!enabled) {
|
|
4270
|
+
button.disabled = true;
|
|
4271
|
+
button.style.color = DISABLED_COLOR;
|
|
4272
|
+
button.style.cursor = "default";
|
|
4273
|
+
}
|
|
4274
|
+
const caption = owner.createElement("span");
|
|
4275
|
+
caption.className = LABEL_CLASS;
|
|
4276
|
+
caption.textContent = text;
|
|
4277
|
+
caption.style.flex = "1";
|
|
4278
|
+
button.appendChild(caption);
|
|
4279
|
+
if (arrow) {
|
|
4280
|
+
const chevron = owner.createElement("span");
|
|
4281
|
+
chevron.className = ARROW_CLASS;
|
|
4282
|
+
chevron.textContent = "\u203A";
|
|
4283
|
+
chevron.setAttribute("aria-hidden", "true");
|
|
4284
|
+
Object.assign(chevron.style, { flex: "none", opacity: "0.7", fontSize: "14px" });
|
|
4285
|
+
button.appendChild(chevron);
|
|
4286
|
+
}
|
|
4287
|
+
return button;
|
|
4288
|
+
}
|
|
4289
|
+
// `closesSubmenu` separates the two places an item can live. A top-level item shuts any
|
|
4290
|
+
// open submenu when the pointer arrives, the way menus everywhere behave. An item inside
|
|
4291
|
+
// the submenu must not: reaching it means the pointer has entered that very panel, and
|
|
4292
|
+
// closing it there would pull it out from under the pointer and drop the click on the
|
|
4293
|
+
// canvas behind.
|
|
4294
|
+
commandEntry(owner, command, enabled, closesSubmenu) {
|
|
4295
|
+
const button = this.item(owner, label(command), enabled, false);
|
|
4296
|
+
this.on(button, "pointerenter", () => {
|
|
4297
|
+
if (closesSubmenu) this.closeSubmenu();
|
|
4298
|
+
if (enabled) button.style.background = HOVER_BACKGROUND;
|
|
4299
|
+
});
|
|
4300
|
+
this.on(button, "pointerleave", () => {
|
|
4301
|
+
button.style.background = "transparent";
|
|
4302
|
+
});
|
|
4303
|
+
this.on(button, "click", () => {
|
|
4304
|
+
if (!enabled) return;
|
|
4305
|
+
this.hide();
|
|
4306
|
+
this.execute(command);
|
|
4307
|
+
});
|
|
4308
|
+
return button;
|
|
4309
|
+
}
|
|
4310
|
+
groupEntry(owner, group) {
|
|
4311
|
+
const host = owner.createElement("div");
|
|
4312
|
+
host.style.position = "relative";
|
|
4313
|
+
const button = this.item(owner, label(group.group), group.enabled, true);
|
|
4314
|
+
host.appendChild(button);
|
|
4315
|
+
const panel = this.panel(owner);
|
|
4316
|
+
panel.className = SUBMENU_CLASS;
|
|
4317
|
+
panel.setAttribute("role", "menu");
|
|
4318
|
+
Object.assign(panel.style, { position: "absolute", left: "100%", top: "-5px", display: "none" });
|
|
4319
|
+
for (const child of group.commands) panel.appendChild(this.commandEntry(owner, child.command, child.enabled, false));
|
|
4320
|
+
host.appendChild(panel);
|
|
4321
|
+
const open = () => {
|
|
4322
|
+
if (!group.enabled) return;
|
|
4323
|
+
button.style.background = HOVER_BACKGROUND;
|
|
4324
|
+
panel.style.display = "block";
|
|
4325
|
+
this.submenu = panel;
|
|
4326
|
+
this.keepOnScreen(panel);
|
|
4327
|
+
};
|
|
4328
|
+
this.on(button, "pointerenter", open);
|
|
4329
|
+
this.on(button, "click", open);
|
|
4330
|
+
this.on(host, "pointerleave", () => {
|
|
4331
|
+
if (this.submenu !== panel) return;
|
|
4332
|
+
button.style.background = "transparent";
|
|
4333
|
+
panel.style.display = "none";
|
|
4334
|
+
this.submenu = null;
|
|
4335
|
+
});
|
|
4336
|
+
return host;
|
|
4337
|
+
}
|
|
4338
|
+
closeSubmenu() {
|
|
4339
|
+
if (this.submenu === null) return;
|
|
4340
|
+
this.submenu.style.display = "none";
|
|
4341
|
+
this.submenu = null;
|
|
4342
|
+
}
|
|
4343
|
+
/** Keeps the panel inside the viewport, flipping it left when it would run off the edge. */
|
|
4344
|
+
keepOnScreen(panel) {
|
|
4345
|
+
if (typeof panel.getBoundingClientRect !== "function" || typeof window === "undefined") return;
|
|
4346
|
+
const box = panel.getBoundingClientRect();
|
|
4347
|
+
if (box.right > window.innerWidth - 4) {
|
|
4348
|
+
panel.style.left = "auto";
|
|
4349
|
+
panel.style.right = "100%";
|
|
4350
|
+
}
|
|
4351
|
+
if (box.bottom > window.innerHeight - 4) {
|
|
4352
|
+
panel.style.top = "auto";
|
|
4353
|
+
panel.style.bottom = "-5px";
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
place(root, x, y) {
|
|
4357
|
+
root.style.left = `${x}px`;
|
|
4358
|
+
root.style.top = `${y}px`;
|
|
4359
|
+
if (typeof root.getBoundingClientRect !== "function" || typeof window === "undefined") return;
|
|
4360
|
+
const box = root.getBoundingClientRect();
|
|
4361
|
+
if (box.right > window.innerWidth - 4) root.style.left = `${Math.max(4, x - box.width)}px`;
|
|
4362
|
+
if (box.bottom > window.innerHeight - 4) root.style.top = `${Math.max(4, y - box.height)}px`;
|
|
4363
|
+
}
|
|
4364
|
+
watchForDismissal(owner) {
|
|
4365
|
+
const dismiss = () => this.hide();
|
|
4366
|
+
const outside = (event) => {
|
|
4367
|
+
const root = this.root;
|
|
4368
|
+
const target = event.target;
|
|
4369
|
+
if (root !== null && target !== null && typeof root.contains === "function" && root.contains(target)) return;
|
|
4370
|
+
this.hide();
|
|
4371
|
+
};
|
|
4372
|
+
this.on(owner, "pointerdown", outside, true);
|
|
4373
|
+
this.on(owner, "keydown", (event) => {
|
|
4374
|
+
if (event.key === "Escape") this.hide();
|
|
4375
|
+
});
|
|
4376
|
+
if (typeof window !== "undefined") {
|
|
4377
|
+
this.on(window, "blur", dismiss);
|
|
4378
|
+
this.on(window, "resize", dismiss);
|
|
4379
|
+
this.on(window, "scroll", dismiss, true);
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
on(target, type, listener, capture = false) {
|
|
4383
|
+
if (typeof target.addEventListener !== "function") return;
|
|
4384
|
+
target.addEventListener(type, listener, capture);
|
|
4385
|
+
this.detach.push(() => target.removeEventListener?.(type, listener, capture));
|
|
4386
|
+
}
|
|
4387
|
+
};
|
|
4388
|
+
|
|
3744
4389
|
// src/diagram/event-emitter.ts
|
|
3745
4390
|
var EventEmitter = class {
|
|
3746
4391
|
constructor() {
|
|
@@ -3895,6 +4540,18 @@ ${runtime.error}`;
|
|
|
3895
4540
|
};
|
|
3896
4541
|
|
|
3897
4542
|
// src/diagram/stocksharp-diagram.ts
|
|
4543
|
+
var CONTEXT_MENU = [
|
|
4544
|
+
"undo",
|
|
4545
|
+
"redo",
|
|
4546
|
+
"cut",
|
|
4547
|
+
"copy",
|
|
4548
|
+
"paste",
|
|
4549
|
+
"open",
|
|
4550
|
+
"delete",
|
|
4551
|
+
{ group: "export", commands: ["exportDocument", "exportPng", "exportSvg"] },
|
|
4552
|
+
"properties",
|
|
4553
|
+
"help"
|
|
4554
|
+
];
|
|
3898
4555
|
var StockSharpDiagram = class extends EventEmitter {
|
|
3899
4556
|
constructor(options) {
|
|
3900
4557
|
super();
|
|
@@ -3909,6 +4566,7 @@ ${runtime.error}`;
|
|
|
3909
4566
|
this.fullscreenButtonVisible = true;
|
|
3910
4567
|
this.fullscreenLabels = { enter: "Enter fullscreen", exit: "Exit fullscreen" };
|
|
3911
4568
|
this.contextActions = new DiagramActionRegistry();
|
|
4569
|
+
this.contextMenu = null;
|
|
3912
4570
|
this.div = options.div;
|
|
3913
4571
|
this.catalog = options.catalog;
|
|
3914
4572
|
this.overviewContainer = options.overviewContainer ?? null;
|
|
@@ -3925,6 +4583,7 @@ ${runtime.error}`;
|
|
|
3925
4583
|
this.prepareFullscreenButtonHost();
|
|
3926
4584
|
this.fullscreenButton = this.createFullscreenButton();
|
|
3927
4585
|
this.updateFullscreenButton();
|
|
4586
|
+
this.setContextMenuEnabled(options.showContextMenu ?? true);
|
|
3928
4587
|
this.registerContextActions();
|
|
3929
4588
|
this.bindCanvasEvents();
|
|
3930
4589
|
this.disposables.push(this.catalog.on("portTypesChanged", () => this.applySocketTheme()));
|
|
@@ -4084,8 +4743,8 @@ ${runtime.error}`;
|
|
|
4084
4743
|
return this.canvas.updateNode(nodeId, { name: value });
|
|
4085
4744
|
}
|
|
4086
4745
|
/** Groups host-driven document edits into one undo/redo operation. */
|
|
4087
|
-
transaction(
|
|
4088
|
-
return this.canvas.withTransaction(
|
|
4746
|
+
transaction(label2, action) {
|
|
4747
|
+
return this.canvas.withTransaction(label2, action);
|
|
4089
4748
|
}
|
|
4090
4749
|
setShowNodeMessages(show) {
|
|
4091
4750
|
this.canvas.setShowNodeMessages(show);
|
|
@@ -4134,6 +4793,10 @@ ${runtime.error}`;
|
|
|
4134
4793
|
takeScreenshot(options = {}) {
|
|
4135
4794
|
return this.canvas.takeScreenshot(options);
|
|
4136
4795
|
}
|
|
4796
|
+
/** The same picture as takeScreenshot, as an SVG document. */
|
|
4797
|
+
takeSvg(options = {}) {
|
|
4798
|
+
return this.canvas.takeSvg(options);
|
|
4799
|
+
}
|
|
4137
4800
|
getSelection() {
|
|
4138
4801
|
return this.canvas.getSelection();
|
|
4139
4802
|
}
|
|
@@ -4266,11 +4929,40 @@ ${runtime.error}`;
|
|
|
4266
4929
|
const fallback = this.canvas.getClipboardDocument();
|
|
4267
4930
|
return fallback !== null && this.canvas.pasteDocument(fallback).length > 0;
|
|
4268
4931
|
}
|
|
4932
|
+
/**
|
|
4933
|
+
* Turns the built-in menu on or off. Off leaves `contextMenuRequested` untouched, so a
|
|
4934
|
+
* host that draws its own is only trading one menu for another, never losing the event.
|
|
4935
|
+
*/
|
|
4936
|
+
setContextMenuEnabled(enabled) {
|
|
4937
|
+
if (enabled === (this.contextMenu !== null)) return;
|
|
4938
|
+
if (!enabled) {
|
|
4939
|
+
this.contextMenu?.destroy();
|
|
4940
|
+
this.contextMenu = null;
|
|
4941
|
+
return;
|
|
4942
|
+
}
|
|
4943
|
+
this.contextMenu = new ContextMenuView({
|
|
4944
|
+
container: this.div,
|
|
4945
|
+
// Through the public entry point, so the built-in menu is exactly as privileged
|
|
4946
|
+
// as a host's own -- same guards, same contextCommand event.
|
|
4947
|
+
execute: (command) => {
|
|
4948
|
+
this.executeContextCommand(command);
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
4951
|
+
}
|
|
4952
|
+
isContextMenuEnabled() {
|
|
4953
|
+
return this.contextMenu !== null;
|
|
4954
|
+
}
|
|
4269
4955
|
getContextCommands() {
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4956
|
+
const context = this.contextActionContext();
|
|
4957
|
+
const state = (command) => ({
|
|
4958
|
+
command,
|
|
4959
|
+
enabled: this.contextActions.canExecute(command, context)
|
|
4960
|
+
});
|
|
4961
|
+
return CONTEXT_MENU.map((entry) => {
|
|
4962
|
+
if (typeof entry === "string") return state(entry);
|
|
4963
|
+
const commands = entry.commands.map(state);
|
|
4964
|
+
return { group: entry.group, enabled: commands.some(({ enabled }) => enabled), commands };
|
|
4965
|
+
});
|
|
4274
4966
|
}
|
|
4275
4967
|
executeContextCommand(command) {
|
|
4276
4968
|
const context = this.contextActionContext();
|
|
@@ -4338,6 +5030,8 @@ ${runtime.error}`;
|
|
|
4338
5030
|
destroy() {
|
|
4339
5031
|
if (this.destroyed) return;
|
|
4340
5032
|
this.destroyed = true;
|
|
5033
|
+
this.contextMenu?.destroy();
|
|
5034
|
+
this.contextMenu = null;
|
|
4341
5035
|
for (const dispose of this.disposables.splice(0).reverse()) dispose();
|
|
4342
5036
|
this.canvas.destroy();
|
|
4343
5037
|
}
|
|
@@ -4390,11 +5084,11 @@ ${runtime.error}`;
|
|
|
4390
5084
|
}
|
|
4391
5085
|
updateFullscreenButton() {
|
|
4392
5086
|
const fullscreen = this.fullscreen;
|
|
4393
|
-
const
|
|
5087
|
+
const label2 = fullscreen ? this.fullscreenLabels.exit : this.fullscreenLabels.enter;
|
|
4394
5088
|
const path = fullscreen ? "M4 9h5V4M20 9h-5V4M4 15h5v5M20 15h-5v5" : "M9 4H4v5M15 4h5v5M9 20H4v-5M15 20h5v-5";
|
|
4395
5089
|
this.fullscreenButton.className = fullscreen ? "ssdiagram-fullscreen-button is-active" : "ssdiagram-fullscreen-button";
|
|
4396
|
-
this.fullscreenButton.title =
|
|
4397
|
-
this.fullscreenButton.setAttribute("aria-label",
|
|
5090
|
+
this.fullscreenButton.title = label2;
|
|
5091
|
+
this.fullscreenButton.setAttribute("aria-label", label2);
|
|
4398
5092
|
this.fullscreenButton.setAttribute("aria-pressed", String(fullscreen));
|
|
4399
5093
|
this.fullscreenButton.innerHTML = `<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="${path}"/></svg>`;
|
|
4400
5094
|
}
|
|
@@ -4490,6 +5184,7 @@ ${runtime.error}`;
|
|
|
4490
5184
|
}),
|
|
4491
5185
|
this.canvas.on("undoStackChanged", (state) => this.emit("undoStackChanged", state)),
|
|
4492
5186
|
this.canvas.on("contextMenu", ({ x, y, node, link, port }) => {
|
|
5187
|
+
const commands = this.getContextCommands();
|
|
4493
5188
|
this.emit("contextMenuRequested", {
|
|
4494
5189
|
x,
|
|
4495
5190
|
y,
|
|
@@ -4497,8 +5192,9 @@ ${runtime.error}`;
|
|
|
4497
5192
|
link: link === null ? null : this.fromCanvasLink(link),
|
|
4498
5193
|
port: port === null ? null : this.fromCanvasPort(port.port),
|
|
4499
5194
|
portDirection: port?.port.direction ?? null,
|
|
4500
|
-
commands
|
|
5195
|
+
commands
|
|
4501
5196
|
});
|
|
5197
|
+
this.contextMenu?.show(x, y, commands);
|
|
4502
5198
|
})
|
|
4503
5199
|
);
|
|
4504
5200
|
}
|
|
@@ -4533,6 +5229,24 @@ ${runtime.error}`;
|
|
|
4533
5229
|
canExecute: ({ selection }) => permissions().deleteSelection && (selection.nodeIds.length > 0 || selection.linkIds.length > 0),
|
|
4534
5230
|
execute: () => this.canvas.deleteSelection()
|
|
4535
5231
|
},
|
|
5232
|
+
// Export asks rather than does. Where the result goes -- a download, an upload,
|
|
5233
|
+
// a clipboard, a print preview -- is the host's decision, and so is which options
|
|
5234
|
+
// to render with, so the control reports the request and stops there.
|
|
5235
|
+
// Availability follows the diagram, not the selection: all three export the whole
|
|
5236
|
+
// picture, and an empty diagram has none to give. They stay on in read-only mode,
|
|
5237
|
+
// which forbids changing the diagram, not looking at it.
|
|
5238
|
+
exportDocument: {
|
|
5239
|
+
canExecute: ({ document: document2 }) => document2.nodes.length > 0,
|
|
5240
|
+
execute: () => this.emit("exportRequested", { format: "document" })
|
|
5241
|
+
},
|
|
5242
|
+
exportPng: {
|
|
5243
|
+
canExecute: ({ document: document2 }) => document2.nodes.length > 0,
|
|
5244
|
+
execute: () => this.emit("exportRequested", { format: "png" })
|
|
5245
|
+
},
|
|
5246
|
+
exportSvg: {
|
|
5247
|
+
canExecute: ({ document: document2 }) => document2.nodes.length > 0,
|
|
5248
|
+
execute: () => this.emit("exportRequested", { format: "svg" })
|
|
5249
|
+
},
|
|
4536
5250
|
properties: {
|
|
4537
5251
|
canExecute: ({ nodes }) => nodes.length > 0,
|
|
4538
5252
|
execute: ({ nodes }) => this.emit("nodeProperties", { nodes })
|
|
@@ -4550,6 +5264,7 @@ ${runtime.error}`;
|
|
|
4550
5264
|
const linkIds = new Set(selection.linkIds);
|
|
4551
5265
|
return {
|
|
4552
5266
|
selection,
|
|
5267
|
+
document: document2,
|
|
4553
5268
|
nodes: document2.nodes.filter((node) => nodeIds.has(node.id)).map((node) => this.fromCanvasInit(node)),
|
|
4554
5269
|
links: document2.links.filter((link) => linkIds.has(link.id)).map((link) => new Link({
|
|
4555
5270
|
id: link.id,
|
|
@@ -4708,8 +5423,8 @@ ${runtime.error}`;
|
|
|
4708
5423
|
// imported node ("Element type is missing from the palette"). Node.id
|
|
4709
5424
|
// keeps its original casing so save/load round-trips unchanged.
|
|
4710
5425
|
addNodeType(node) {
|
|
4711
|
-
const
|
|
4712
|
-
this.nodeTypes.set(
|
|
5426
|
+
const n2 = (node instanceof Node ? node : new Node(node)).clone();
|
|
5427
|
+
this.nodeTypes.set(n2.id.toLowerCase(), n2);
|
|
4713
5428
|
this.emitNodeTypesChanged();
|
|
4714
5429
|
}
|
|
4715
5430
|
removeNodeType(id) {
|
|
@@ -4853,11 +5568,11 @@ ${runtime.error}`;
|
|
|
4853
5568
|
const chev = document.createElement("span");
|
|
4854
5569
|
chev.className = "d-palette-chev";
|
|
4855
5570
|
chev.setAttribute("aria-hidden", "true");
|
|
4856
|
-
const
|
|
4857
|
-
|
|
4858
|
-
|
|
5571
|
+
const label2 = document.createElement("span");
|
|
5572
|
+
label2.className = "d-palette-group-name";
|
|
5573
|
+
label2.textContent = name;
|
|
4859
5574
|
head.appendChild(chev);
|
|
4860
|
-
head.appendChild(
|
|
5575
|
+
head.appendChild(label2);
|
|
4861
5576
|
head.addEventListener("click", () => {
|
|
4862
5577
|
if (this.collapsed.has(name)) this.collapsed.delete(name);
|
|
4863
5578
|
else this.collapsed.add(name);
|
|
@@ -4893,10 +5608,10 @@ ${runtime.error}`;
|
|
|
4893
5608
|
placeholder.setAttribute("aria-hidden", "true");
|
|
4894
5609
|
li.appendChild(placeholder);
|
|
4895
5610
|
}
|
|
4896
|
-
const
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
li.appendChild(
|
|
5611
|
+
const label2 = document.createElement("span");
|
|
5612
|
+
label2.className = "d-palette-label";
|
|
5613
|
+
label2.textContent = item.name;
|
|
5614
|
+
li.appendChild(label2);
|
|
4900
5615
|
li.addEventListener("click", () => this.selectNodeType(item.id));
|
|
4901
5616
|
li.addEventListener("dblclick", (event) => {
|
|
4902
5617
|
event.preventDefault();
|
|
@@ -4976,8 +5691,8 @@ ${runtime.error}`;
|
|
|
4976
5691
|
const m = hex.match(/^#?([0-9a-fA-F]{6})$/);
|
|
4977
5692
|
if (m === null)
|
|
4978
5693
|
return 0;
|
|
4979
|
-
const
|
|
4980
|
-
return 0.299 * (
|
|
5694
|
+
const n2 = parseInt(m[1], 16);
|
|
5695
|
+
return 0.299 * (n2 >> 16 & 255) + 0.587 * (n2 >> 8 & 255) + 0.114 * (n2 & 255);
|
|
4981
5696
|
}
|
|
4982
5697
|
function makePort(p) {
|
|
4983
5698
|
return new Port({
|
|
@@ -5018,23 +5733,23 @@ ${runtime.error}`;
|
|
|
5018
5733
|
add(usedOut, l.from, l.fromPort);
|
|
5019
5734
|
add(usedIn, l.to, l.toPort);
|
|
5020
5735
|
}
|
|
5021
|
-
const nodes = scheme.nodes.map((
|
|
5022
|
-
const
|
|
5023
|
-
const inPorts =
|
|
5024
|
-
const outPorts =
|
|
5025
|
-
const anchorType =
|
|
5026
|
-
for (const key of usedIn.get(
|
|
5736
|
+
const nodes = scheme.nodes.map((n2) => {
|
|
5737
|
+
const t2 = catalog.getNodeType(n2.typeId);
|
|
5738
|
+
const inPorts = t2 ? t2.inPorts.map(portOf) : [];
|
|
5739
|
+
const outPorts = t2 ? t2.outPorts.map(portOf) : [];
|
|
5740
|
+
const anchorType = t2?.inPorts.find((p) => p.isDynamic)?.type ?? "Any";
|
|
5741
|
+
for (const key of usedIn.get(n2.id) ?? [])
|
|
5027
5742
|
if (!inPorts.some((p) => p.id === key))
|
|
5028
5743
|
inPorts.push({ id: key, name: key, type: anchorType, maxLinks: 0 });
|
|
5029
|
-
for (const key of usedOut.get(
|
|
5744
|
+
for (const key of usedOut.get(n2.id) ?? [])
|
|
5030
5745
|
if (!outPorts.some((p) => p.id === key))
|
|
5031
5746
|
outPorts.push({ id: key, name: key, type: "Any", maxLinks: 0 });
|
|
5032
|
-
const data = new DiagramNode({ id:
|
|
5033
|
-
if (
|
|
5034
|
-
data.icon =
|
|
5035
|
-
if (!
|
|
5747
|
+
const data = new DiagramNode({ id: n2.id, typeId: n2.typeId, name: n2.name, x: n2.x, y: n2.y, inPorts, outPorts });
|
|
5748
|
+
if (t2?.icon)
|
|
5749
|
+
data.icon = t2.icon;
|
|
5750
|
+
if (!t2) {
|
|
5036
5751
|
data.isPlaceholder = true;
|
|
5037
|
-
nodeErrors[
|
|
5752
|
+
nodeErrors[n2.id] = missingTypeMessage(n2.typeId);
|
|
5038
5753
|
}
|
|
5039
5754
|
return data;
|
|
5040
5755
|
});
|