@ngroznykh/papirus 0.5.9 → 0.6.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/dist/core/DiagramRenderer.d.ts +1 -0
- package/dist/core/DiagramRenderer.d.ts.map +1 -1
- package/dist/core/InteractionManager.d.ts +3 -0
- package/dist/core/InteractionManager.d.ts.map +1 -1
- package/dist/elements/Edge.d.ts +29 -2
- package/dist/elements/Edge.d.ts.map +1 -1
- package/dist/elements/NodeImage.d.ts.map +1 -1
- package/dist/elements/composite/CComponent.d.ts +109 -0
- package/dist/elements/composite/CComponent.d.ts.map +1 -0
- package/dist/elements/composite/CContainer.d.ts +58 -0
- package/dist/elements/composite/CContainer.d.ts.map +1 -0
- package/dist/elements/composite/CDivider.d.ts +37 -0
- package/dist/elements/composite/CDivider.d.ts.map +1 -0
- package/dist/elements/composite/CIcon.d.ts +58 -0
- package/dist/elements/composite/CIcon.d.ts.map +1 -0
- package/dist/elements/composite/CShape.d.ts +51 -0
- package/dist/elements/composite/CShape.d.ts.map +1 -0
- package/dist/elements/composite/CText.d.ts +82 -0
- package/dist/elements/composite/CText.d.ts.map +1 -0
- package/dist/elements/composite/CompositeNode.d.ts +60 -0
- package/dist/elements/composite/CompositeNode.d.ts.map +1 -0
- package/dist/elements/composite/FlexLayout.d.ts +49 -0
- package/dist/elements/composite/FlexLayout.d.ts.map +1 -0
- package/dist/elements/composite/deserialize.d.ts +6 -0
- package/dist/elements/composite/deserialize.d.ts.map +1 -0
- package/dist/elements/composite/index.d.ts +26 -0
- package/dist/elements/composite/index.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/papirus.js +1908 -274
- package/dist/papirus.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/Serializer.d.ts.map +1 -1
- package/dist/utils/SvgExporter.d.ts.map +1 -1
- package/dist/utils/svgTint.d.ts +20 -0
- package/dist/utils/svgTint.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/papirus.js
CHANGED
|
@@ -2903,15 +2903,15 @@ class TextLabel {
|
|
|
2903
2903
|
this.applyStyle(ctx);
|
|
2904
2904
|
const lineHeight = (this._style.fontSize ?? 14) * 1.2;
|
|
2905
2905
|
const effectiveMaxWidth = this._maxWidth ?? this._autoMaxWidth;
|
|
2906
|
-
const
|
|
2906
|
+
const text2 = this._text ?? "";
|
|
2907
2907
|
if (effectiveMaxWidth !== void 0) {
|
|
2908
2908
|
const maxWidth = Math.max(
|
|
2909
2909
|
0,
|
|
2910
2910
|
effectiveMaxWidth - this._inset.left - this._inset.right
|
|
2911
2911
|
);
|
|
2912
|
-
this._lines = this.wrapText(ctx,
|
|
2912
|
+
this._lines = this.wrapText(ctx, text2, maxWidth);
|
|
2913
2913
|
} else {
|
|
2914
|
-
this._lines =
|
|
2914
|
+
this._lines = text2.split("\n");
|
|
2915
2915
|
}
|
|
2916
2916
|
let maxLineWidth = 0;
|
|
2917
2917
|
for (const line of this._lines) {
|
|
@@ -3005,9 +3005,9 @@ class TextLabel {
|
|
|
3005
3005
|
ctx.textAlign = this._style.align ?? "center";
|
|
3006
3006
|
ctx.textBaseline = this._style.baseline ?? "middle";
|
|
3007
3007
|
}
|
|
3008
|
-
wrapText(ctx,
|
|
3008
|
+
wrapText(ctx, text2, maxWidth) {
|
|
3009
3009
|
const lines = [];
|
|
3010
|
-
const paragraphs =
|
|
3010
|
+
const paragraphs = text2.split("\n");
|
|
3011
3011
|
for (const paragraph of paragraphs) {
|
|
3012
3012
|
const words = paragraph.split(" ");
|
|
3013
3013
|
let currentLine = "";
|
|
@@ -4239,6 +4239,8 @@ class Edge extends Element {
|
|
|
4239
4239
|
this._pathStrategy = this.getPathStrategy(this._type);
|
|
4240
4240
|
this._lockAnchors = options.lockAnchors ?? true;
|
|
4241
4241
|
this._labelOffset = options.labelOffset ?? 0;
|
|
4242
|
+
this._labelPosition = options.labelPosition ?? 0.5;
|
|
4243
|
+
this._labelFollowPath = options.labelFollowPath ?? false;
|
|
4242
4244
|
this._labelBackground = options.labelBackground;
|
|
4243
4245
|
this._labelLineGap = options.labelLineGap ?? false;
|
|
4244
4246
|
if (options.label !== void 0) {
|
|
@@ -4332,6 +4334,31 @@ class Edge extends Element {
|
|
|
4332
4334
|
this.markDirty();
|
|
4333
4335
|
}
|
|
4334
4336
|
}
|
|
4337
|
+
/**
|
|
4338
|
+
* Position along path (0 = source, 0.5 = midpoint, 1 = target)
|
|
4339
|
+
*/
|
|
4340
|
+
get labelPosition() {
|
|
4341
|
+
return this._labelPosition;
|
|
4342
|
+
}
|
|
4343
|
+
set labelPosition(value) {
|
|
4344
|
+
const clamped = Math.max(0, Math.min(1, value));
|
|
4345
|
+
if (this._labelPosition !== clamped) {
|
|
4346
|
+
this._labelPosition = clamped;
|
|
4347
|
+
this.markDirty();
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
/**
|
|
4351
|
+
* Whether label text rotates to follow the path tangent
|
|
4352
|
+
*/
|
|
4353
|
+
get labelFollowPath() {
|
|
4354
|
+
return this._labelFollowPath;
|
|
4355
|
+
}
|
|
4356
|
+
set labelFollowPath(value) {
|
|
4357
|
+
if (this._labelFollowPath !== value) {
|
|
4358
|
+
this._labelFollowPath = value;
|
|
4359
|
+
this.markDirty();
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4335
4362
|
/**
|
|
4336
4363
|
* Label background configuration
|
|
4337
4364
|
*/
|
|
@@ -4767,11 +4794,20 @@ class Edge extends Element {
|
|
|
4767
4794
|
}
|
|
4768
4795
|
const labelWidth = this._label.measuredWidth;
|
|
4769
4796
|
const labelHeight = this._label.measuredHeight;
|
|
4797
|
+
const rot = this.getLabelRotation();
|
|
4798
|
+
let effectiveWidth = labelWidth;
|
|
4799
|
+
let effectiveHeight = labelHeight;
|
|
4800
|
+
if (rot !== 0) {
|
|
4801
|
+
const cosR = Math.abs(Math.cos(rot));
|
|
4802
|
+
const sinR = Math.abs(Math.sin(rot));
|
|
4803
|
+
effectiveWidth = labelWidth * cosR + labelHeight * sinR;
|
|
4804
|
+
effectiveHeight = labelWidth * sinR + labelHeight * cosR;
|
|
4805
|
+
}
|
|
4770
4806
|
const labelRect = {
|
|
4771
|
-
x: labelCenter.x -
|
|
4772
|
-
y: labelCenter.y -
|
|
4773
|
-
width:
|
|
4774
|
-
height:
|
|
4807
|
+
x: labelCenter.x - effectiveWidth / 2,
|
|
4808
|
+
y: labelCenter.y - effectiveHeight / 2,
|
|
4809
|
+
width: effectiveWidth,
|
|
4810
|
+
height: effectiveHeight
|
|
4775
4811
|
};
|
|
4776
4812
|
const segmentIntersections = [];
|
|
4777
4813
|
for (let i = 0; i < polyline.length - 1; i++) {
|
|
@@ -5045,10 +5081,12 @@ class Edge extends Element {
|
|
|
5045
5081
|
if (this._label === void 0 || this._path.length < 2) {
|
|
5046
5082
|
return;
|
|
5047
5083
|
}
|
|
5048
|
-
const
|
|
5049
|
-
const
|
|
5050
|
-
|
|
5051
|
-
|
|
5084
|
+
const { point, angle: pathAngle } = this.getPathPointAt(this._labelPosition);
|
|
5085
|
+
const rotation = this.getLabelRotation();
|
|
5086
|
+
const perpAngle = this._labelFollowPath ? pathAngle + Math.PI / 2 : Math.PI / 2;
|
|
5087
|
+
const labelCenter = {
|
|
5088
|
+
x: point.x + this._labelOffset * Math.cos(perpAngle),
|
|
5089
|
+
y: point.y + this._labelOffset * Math.sin(perpAngle)
|
|
5052
5090
|
};
|
|
5053
5091
|
const labelOpacity = this._label.style.opacity ?? 1;
|
|
5054
5092
|
this._label.measure(ctx);
|
|
@@ -5057,29 +5095,39 @@ class Edge extends Element {
|
|
|
5057
5095
|
const bgColor = this._labelBackground?.color ?? "#ffffff";
|
|
5058
5096
|
const bgOpacity = this._labelBackground?.opacity ?? 1;
|
|
5059
5097
|
const bgRadius = this._labelBackground?.borderRadius ?? EDGE_LABEL_BACKGROUND_RADIUS;
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5098
|
+
ctx.save();
|
|
5099
|
+
if (rotation !== 0) {
|
|
5100
|
+
ctx.translate(labelCenter.x, labelCenter.y);
|
|
5101
|
+
ctx.rotate(rotation);
|
|
5102
|
+
ctx.translate(-labelCenter.x, -labelCenter.y);
|
|
5103
|
+
}
|
|
5104
|
+
const bgX = labelCenter.x - labelWidth / 2;
|
|
5105
|
+
const bgY = labelCenter.y - labelHeight / 2;
|
|
5064
5106
|
ctx.fillStyle = bgColor;
|
|
5065
5107
|
ctx.globalAlpha = bgOpacity;
|
|
5066
5108
|
if (bgRadius > 0) {
|
|
5067
|
-
this.drawRoundedRect(ctx, bgX, bgY,
|
|
5109
|
+
this.drawRoundedRect(ctx, bgX, bgY, labelWidth, labelHeight, bgRadius);
|
|
5068
5110
|
ctx.fill();
|
|
5069
5111
|
} else {
|
|
5070
|
-
ctx.fillRect(bgX, bgY,
|
|
5112
|
+
ctx.fillRect(bgX, bgY, labelWidth, labelHeight);
|
|
5071
5113
|
}
|
|
5072
5114
|
ctx.globalAlpha = labelOpacity;
|
|
5073
|
-
this._label.renderAt(ctx,
|
|
5115
|
+
this._label.renderAt(ctx, labelCenter);
|
|
5074
5116
|
ctx.globalAlpha = 1;
|
|
5117
|
+
ctx.restore();
|
|
5075
5118
|
}
|
|
5076
5119
|
drawRoundedRect(ctx, x, y, width, height, radius) {
|
|
5077
5120
|
drawRoundedRectPath(ctx, x, y, width, height, radius);
|
|
5078
5121
|
}
|
|
5079
|
-
|
|
5122
|
+
/**
|
|
5123
|
+
* Get a point along the sampled path at parameter t (0..1).
|
|
5124
|
+
* Also returns the tangent angle in radians at that point.
|
|
5125
|
+
*/
|
|
5126
|
+
getPathPointAt(t) {
|
|
5080
5127
|
const path = this._path;
|
|
5128
|
+
let samples;
|
|
5081
5129
|
if (this._type === "bezier" && path.length >= 4) {
|
|
5082
|
-
|
|
5130
|
+
samples = [];
|
|
5083
5131
|
const steps = 20;
|
|
5084
5132
|
for (let i = 1; i + 2 < path.length; i += 3) {
|
|
5085
5133
|
const p0 = path[i - 1];
|
|
@@ -5087,16 +5135,15 @@ class Edge extends Element {
|
|
|
5087
5135
|
const p2 = path[i + 1];
|
|
5088
5136
|
const p3 = path[i + 2];
|
|
5089
5137
|
for (let s = 0; s <= steps; s++) {
|
|
5090
|
-
const
|
|
5091
|
-
if (samples.length > 0 &&
|
|
5092
|
-
|
|
5093
|
-
}
|
|
5094
|
-
samples.push(bezierPoint(p0, p1, p2, p3, t));
|
|
5138
|
+
const st = s / steps;
|
|
5139
|
+
if (samples.length > 0 && st === 0) continue;
|
|
5140
|
+
samples.push(bezierPoint(p0, p1, p2, p3, st));
|
|
5095
5141
|
}
|
|
5096
5142
|
}
|
|
5097
|
-
|
|
5143
|
+
} else {
|
|
5144
|
+
samples = path;
|
|
5098
5145
|
}
|
|
5099
|
-
return this.
|
|
5146
|
+
return this.getPointAlongPolyline(samples, t);
|
|
5100
5147
|
}
|
|
5101
5148
|
/**
|
|
5102
5149
|
* Get world position of label center along path.
|
|
@@ -5105,18 +5152,33 @@ class Edge extends Element {
|
|
|
5105
5152
|
if (this._path.length < 2) {
|
|
5106
5153
|
return null;
|
|
5107
5154
|
}
|
|
5108
|
-
const
|
|
5155
|
+
const { point, angle: pathAngle } = this.getPathPointAt(this._labelPosition);
|
|
5156
|
+
const perpAngle = this._labelFollowPath ? pathAngle + Math.PI / 2 : Math.PI / 2;
|
|
5109
5157
|
return {
|
|
5110
|
-
x:
|
|
5111
|
-
y:
|
|
5158
|
+
x: point.x + this._labelOffset * Math.cos(perpAngle),
|
|
5159
|
+
y: point.y + this._labelOffset * Math.sin(perpAngle)
|
|
5112
5160
|
};
|
|
5113
5161
|
}
|
|
5114
|
-
|
|
5162
|
+
/**
|
|
5163
|
+
* Get label rotation angle in radians (if labelFollowPath is true).
|
|
5164
|
+
*/
|
|
5165
|
+
getLabelRotation() {
|
|
5166
|
+
if (!this._labelFollowPath || this._path.length < 2) return 0;
|
|
5167
|
+
const { angle: angle2 } = this.getPathPointAt(this._labelPosition);
|
|
5168
|
+
let a = angle2;
|
|
5169
|
+
if (a > Math.PI / 2) a -= Math.PI;
|
|
5170
|
+
if (a < -Math.PI / 2) a += Math.PI;
|
|
5171
|
+
return a;
|
|
5172
|
+
}
|
|
5173
|
+
/**
|
|
5174
|
+
* Get point and tangent angle at parameter t along a polyline.
|
|
5175
|
+
*/
|
|
5176
|
+
getPointAlongPolyline(path, t) {
|
|
5115
5177
|
if (path.length === 0) {
|
|
5116
|
-
return { x: 0, y: 0 };
|
|
5178
|
+
return { point: { x: 0, y: 0 }, angle: 0 };
|
|
5117
5179
|
}
|
|
5118
5180
|
if (path.length === 1) {
|
|
5119
|
-
return path[0];
|
|
5181
|
+
return { point: path[0], angle: 0 };
|
|
5120
5182
|
}
|
|
5121
5183
|
let totalLength = 0;
|
|
5122
5184
|
const segments = [];
|
|
@@ -5127,19 +5189,28 @@ class Edge extends Element {
|
|
|
5127
5189
|
segments.push({ start, end, length });
|
|
5128
5190
|
totalLength += length;
|
|
5129
5191
|
}
|
|
5130
|
-
const
|
|
5192
|
+
const targetLength = totalLength * Math.max(0, Math.min(1, t));
|
|
5131
5193
|
let accumulated = 0;
|
|
5132
5194
|
for (const seg of segments) {
|
|
5133
|
-
if (accumulated + seg.length >=
|
|
5134
|
-
const
|
|
5135
|
-
|
|
5136
|
-
x: seg.start.x +
|
|
5137
|
-
y: seg.start.y +
|
|
5195
|
+
if (accumulated + seg.length >= targetLength) {
|
|
5196
|
+
const segT = seg.length > 0 ? (targetLength - accumulated) / seg.length : 0;
|
|
5197
|
+
const point = {
|
|
5198
|
+
x: seg.start.x + segT * (seg.end.x - seg.start.x),
|
|
5199
|
+
y: seg.start.y + segT * (seg.end.y - seg.start.y)
|
|
5138
5200
|
};
|
|
5201
|
+
const angle2 = Math.atan2(seg.end.y - seg.start.y, seg.end.x - seg.start.x);
|
|
5202
|
+
return { point, angle: angle2 };
|
|
5139
5203
|
}
|
|
5140
5204
|
accumulated += seg.length;
|
|
5141
5205
|
}
|
|
5142
|
-
|
|
5206
|
+
const lastSeg = segments[segments.length - 1];
|
|
5207
|
+
return {
|
|
5208
|
+
point: lastSeg.end,
|
|
5209
|
+
angle: Math.atan2(
|
|
5210
|
+
lastSeg.end.y - lastSeg.start.y,
|
|
5211
|
+
lastSeg.end.x - lastSeg.start.x
|
|
5212
|
+
)
|
|
5213
|
+
};
|
|
5143
5214
|
}
|
|
5144
5215
|
getPathStrategy(type) {
|
|
5145
5216
|
switch (type) {
|
|
@@ -5187,10 +5258,10 @@ class LabelEditor {
|
|
|
5187
5258
|
/**
|
|
5188
5259
|
* Start editing a label
|
|
5189
5260
|
*/
|
|
5190
|
-
start(kind, id,
|
|
5261
|
+
start(kind, id, text2, worldPosition, worldToScreen, onCommit) {
|
|
5191
5262
|
this.finish(true);
|
|
5192
5263
|
const screenPoint = worldToScreen(worldPosition.x, worldPosition.y);
|
|
5193
|
-
const textarea = this.createTextarea(
|
|
5264
|
+
const textarea = this.createTextarea(text2, screenPoint);
|
|
5194
5265
|
const { cleanup } = this.setupEventHandlers(textarea, onCommit);
|
|
5195
5266
|
document.body.appendChild(textarea);
|
|
5196
5267
|
textarea.focus();
|
|
@@ -5214,9 +5285,9 @@ class LabelEditor {
|
|
|
5214
5285
|
onCommit(kind, id, value);
|
|
5215
5286
|
}
|
|
5216
5287
|
}
|
|
5217
|
-
createTextarea(
|
|
5288
|
+
createTextarea(text2, screenPoint) {
|
|
5218
5289
|
const textarea = document.createElement("textarea");
|
|
5219
|
-
textarea.value =
|
|
5290
|
+
textarea.value = text2;
|
|
5220
5291
|
textarea.rows = 1;
|
|
5221
5292
|
textarea.spellcheck = false;
|
|
5222
5293
|
textarea.setAttribute("aria-label", "Edit label");
|
|
@@ -5715,6 +5786,24 @@ class InteractionManager {
|
|
|
5715
5786
|
return;
|
|
5716
5787
|
}
|
|
5717
5788
|
this.selectionManager.handleClick(event);
|
|
5789
|
+
this.emitComponentClick(event);
|
|
5790
|
+
}
|
|
5791
|
+
emitComponentClick(event) {
|
|
5792
|
+
const point = { x: event.worldX, y: event.worldY };
|
|
5793
|
+
const hitElement = this.renderer.getInteractableElementAtPoint(point, event.screenX, event.screenY);
|
|
5794
|
+
if (!hitElement || !("typeName" in hitElement)) return;
|
|
5795
|
+
const node = hitElement;
|
|
5796
|
+
if (node.typeName !== "composite") return;
|
|
5797
|
+
const compositeNode = node;
|
|
5798
|
+
const component = compositeNode.getComponentAtPoint(point);
|
|
5799
|
+
if (!component) return;
|
|
5800
|
+
if (component.id !== void 0) {
|
|
5801
|
+
this.renderer.emit("componentClick", node.id, component, point);
|
|
5802
|
+
}
|
|
5803
|
+
const asAny = component;
|
|
5804
|
+
if (typeof asAny["onClick"] === "function") {
|
|
5805
|
+
asAny["onClick"](component);
|
|
5806
|
+
}
|
|
5718
5807
|
}
|
|
5719
5808
|
handleDoubleClick(event) {
|
|
5720
5809
|
if (this.renderer.blocksDiagramPointerAtScreen(event.screenX, event.screenY)) {
|
|
@@ -5742,6 +5831,10 @@ class InteractionManager {
|
|
|
5742
5831
|
return;
|
|
5743
5832
|
}
|
|
5744
5833
|
if ("typeName" in hitElement) {
|
|
5834
|
+
if (hitElement.typeName === "composite") {
|
|
5835
|
+
this.startCompositeNameEdit(hitElement);
|
|
5836
|
+
return;
|
|
5837
|
+
}
|
|
5745
5838
|
const editText = hitElement.label?.editableText ?? hitElement.label?.text ?? "";
|
|
5746
5839
|
this.startLabelEdit("node", hitElement.id, editText, hitElement.getLabelPosition());
|
|
5747
5840
|
return;
|
|
@@ -5851,16 +5944,66 @@ class InteractionManager {
|
|
|
5851
5944
|
return false;
|
|
5852
5945
|
}
|
|
5853
5946
|
}
|
|
5854
|
-
startLabelEdit(kind, id,
|
|
5947
|
+
startLabelEdit(kind, id, text2, worldPosition) {
|
|
5855
5948
|
this.labelEditor.start(
|
|
5856
5949
|
kind,
|
|
5857
5950
|
id,
|
|
5858
|
-
|
|
5951
|
+
text2,
|
|
5859
5952
|
worldPosition,
|
|
5860
5953
|
(x, y) => this.renderer.worldToScreen(x, y),
|
|
5861
5954
|
(k, i, value) => this.handleLabelCommit(k, i, value)
|
|
5862
5955
|
);
|
|
5863
5956
|
}
|
|
5957
|
+
startCompositeNameEdit(node) {
|
|
5958
|
+
const nameText = this.findNameComponent(node.content);
|
|
5959
|
+
if (!nameText) return;
|
|
5960
|
+
const currentText = nameText.text;
|
|
5961
|
+
const worldPos = node.getLabelPosition();
|
|
5962
|
+
this.labelEditor.start(
|
|
5963
|
+
"node",
|
|
5964
|
+
node.id,
|
|
5965
|
+
currentText,
|
|
5966
|
+
worldPos,
|
|
5967
|
+
(x, y) => this.renderer.worldToScreen(x, y),
|
|
5968
|
+
(_kind, _id, value) => {
|
|
5969
|
+
const trimmed = value.trim();
|
|
5970
|
+
if (trimmed.length > 0 && trimmed !== currentText) {
|
|
5971
|
+
const before = currentText;
|
|
5972
|
+
nameText.text = trimmed;
|
|
5973
|
+
this.historyManager.execute({
|
|
5974
|
+
execute: () => {
|
|
5975
|
+
nameText.text = trimmed;
|
|
5976
|
+
},
|
|
5977
|
+
undo: () => {
|
|
5978
|
+
nameText.text = before;
|
|
5979
|
+
}
|
|
5980
|
+
});
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
);
|
|
5984
|
+
}
|
|
5985
|
+
findNameComponent(container2) {
|
|
5986
|
+
for (const child of container2.children) {
|
|
5987
|
+
if (child.type === "text") {
|
|
5988
|
+
const ctext = child;
|
|
5989
|
+
if (ctext.role === "name") return ctext;
|
|
5990
|
+
}
|
|
5991
|
+
if (child.type === "container") {
|
|
5992
|
+
const found = this.findNameComponent(
|
|
5993
|
+
child
|
|
5994
|
+
);
|
|
5995
|
+
if (found) return found;
|
|
5996
|
+
}
|
|
5997
|
+
if (child.type === "shape") {
|
|
5998
|
+
const content = child.content;
|
|
5999
|
+
if (content) {
|
|
6000
|
+
const found = this.findNameComponent(content);
|
|
6001
|
+
if (found) return found;
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
return null;
|
|
6006
|
+
}
|
|
5864
6007
|
handleLabelCommit(kind, id, nextValue) {
|
|
5865
6008
|
if (kind === "node") {
|
|
5866
6009
|
this.changeNodeProperties(id, (node) => {
|
|
@@ -6395,20 +6538,20 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6395
6538
|
}
|
|
6396
6539
|
}
|
|
6397
6540
|
buildMenu(items, target) {
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6541
|
+
const container2 = document.createElement("div");
|
|
6542
|
+
container2.style.position = "fixed";
|
|
6543
|
+
container2.style.zIndex = "10000";
|
|
6544
|
+
container2.style.background = "#ffffff";
|
|
6545
|
+
container2.style.border = "1px solid #e5e7eb";
|
|
6546
|
+
container2.style.borderRadius = "8px";
|
|
6547
|
+
container2.style.padding = "6px";
|
|
6548
|
+
container2.style.boxShadow = "0 8px 20px rgba(15, 23, 42, 0.18)";
|
|
6549
|
+
container2.style.fontFamily = "system-ui, -apple-system, Segoe UI, sans-serif";
|
|
6550
|
+
container2.style.fontSize = "14px";
|
|
6551
|
+
container2.style.color = "#0f172a";
|
|
6409
6552
|
const list = this.buildList(items, target);
|
|
6410
|
-
|
|
6411
|
-
return
|
|
6553
|
+
container2.appendChild(list);
|
|
6554
|
+
return container2;
|
|
6412
6555
|
}
|
|
6413
6556
|
buildList(items, target) {
|
|
6414
6557
|
const list = document.createElement("ul");
|
|
@@ -6510,7 +6653,7 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6510
6653
|
}
|
|
6511
6654
|
return list;
|
|
6512
6655
|
}
|
|
6513
|
-
createIconElement(
|
|
6656
|
+
createIconElement(icon2) {
|
|
6514
6657
|
const iconEl = document.createElement("span");
|
|
6515
6658
|
iconEl.style.width = "16px";
|
|
6516
6659
|
iconEl.style.height = "16px";
|
|
@@ -6519,16 +6662,16 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6519
6662
|
iconEl.style.justifyContent = "center";
|
|
6520
6663
|
iconEl.style.textAlign = "center";
|
|
6521
6664
|
iconEl.style.flexShrink = "0";
|
|
6522
|
-
if (!
|
|
6665
|
+
if (!icon2) {
|
|
6523
6666
|
iconEl.textContent = "";
|
|
6524
6667
|
return iconEl;
|
|
6525
6668
|
}
|
|
6526
|
-
if (typeof
|
|
6527
|
-
if (this.isSvgString(
|
|
6528
|
-
iconEl.innerHTML =
|
|
6669
|
+
if (typeof icon2 === "string") {
|
|
6670
|
+
if (this.isSvgString(icon2)) {
|
|
6671
|
+
iconEl.innerHTML = icon2;
|
|
6529
6672
|
} else if (this.options.iconToUrl) {
|
|
6530
6673
|
const img = document.createElement("img");
|
|
6531
|
-
img.src = this.options.iconToUrl(
|
|
6674
|
+
img.src = this.options.iconToUrl(icon2);
|
|
6532
6675
|
img.alt = "";
|
|
6533
6676
|
img.style.width = "16px";
|
|
6534
6677
|
img.style.height = "16px";
|
|
@@ -6537,15 +6680,15 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6537
6680
|
} else {
|
|
6538
6681
|
iconEl.classList.add("material-symbols-outlined");
|
|
6539
6682
|
iconEl.style.fontSize = "16px";
|
|
6540
|
-
iconEl.textContent =
|
|
6683
|
+
iconEl.textContent = icon2;
|
|
6541
6684
|
}
|
|
6542
6685
|
return iconEl;
|
|
6543
6686
|
}
|
|
6544
|
-
if (
|
|
6545
|
-
iconEl.innerHTML =
|
|
6687
|
+
if (icon2.type === "svg" || icon2.type === "html") {
|
|
6688
|
+
iconEl.innerHTML = icon2.value;
|
|
6546
6689
|
} else if (this.options.iconToUrl) {
|
|
6547
6690
|
const img = document.createElement("img");
|
|
6548
|
-
img.src = this.options.iconToUrl(
|
|
6691
|
+
img.src = this.options.iconToUrl(icon2.value);
|
|
6549
6692
|
img.alt = "";
|
|
6550
6693
|
img.style.width = "16px";
|
|
6551
6694
|
img.style.height = "16px";
|
|
@@ -6554,7 +6697,7 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6554
6697
|
} else {
|
|
6555
6698
|
iconEl.classList.add("material-symbols-outlined");
|
|
6556
6699
|
iconEl.style.fontSize = "16px";
|
|
6557
|
-
iconEl.textContent =
|
|
6700
|
+
iconEl.textContent = icon2.value;
|
|
6558
6701
|
}
|
|
6559
6702
|
return iconEl;
|
|
6560
6703
|
}
|
|
@@ -8435,10 +8578,6 @@ class LRUCache {
|
|
|
8435
8578
|
return this.maxSize;
|
|
8436
8579
|
}
|
|
8437
8580
|
}
|
|
8438
|
-
function isCornerPlacement(placement) {
|
|
8439
|
-
return placement === "top-left" || placement === "top-right" || placement === "bottom-left" || placement === "bottom-right";
|
|
8440
|
-
}
|
|
8441
|
-
const svgTextCache = new LRUCache(100);
|
|
8442
8581
|
function styleSetColor(style, key, color) {
|
|
8443
8582
|
const hasKey = new RegExp(`${key}\\s*:`).test(style);
|
|
8444
8583
|
if (hasKey) {
|
|
@@ -8489,6 +8628,18 @@ function svgToDataUrl(svg) {
|
|
|
8489
8628
|
const encoded = encodeURIComponent(svg).replace(/%0A/g, "").replace(/%0D/g, "").replace(/%09/g, " ").replace(/%20/g, " ");
|
|
8490
8629
|
return `data:image/svg+xml;utf8,${encoded}`;
|
|
8491
8630
|
}
|
|
8631
|
+
function isSvgUrl(url) {
|
|
8632
|
+
try {
|
|
8633
|
+
const path = new URL(url, "http://localhost").pathname;
|
|
8634
|
+
return path.endsWith(".svg");
|
|
8635
|
+
} catch {
|
|
8636
|
+
return url.endsWith(".svg");
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
8639
|
+
function isCornerPlacement(placement) {
|
|
8640
|
+
return placement === "top-left" || placement === "top-right" || placement === "bottom-left" || placement === "bottom-right";
|
|
8641
|
+
}
|
|
8642
|
+
const svgTextCache = new LRUCache(100);
|
|
8492
8643
|
class NodeImage {
|
|
8493
8644
|
constructor(options, onChange) {
|
|
8494
8645
|
this._loaded = false;
|
|
@@ -10349,181 +10500,1603 @@ const ShapeFactories = {
|
|
|
10349
10500
|
}
|
|
10350
10501
|
}
|
|
10351
10502
|
};
|
|
10352
|
-
const
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
}
|
|
10385
|
-
},
|
|
10386
|
-
edge: {
|
|
10387
|
-
default: {
|
|
10388
|
-
strokeColor: "#666666",
|
|
10389
|
-
strokeWidth: 2,
|
|
10390
|
-
opacity: 1
|
|
10391
|
-
},
|
|
10392
|
-
hover: {
|
|
10393
|
-
strokeColor: "#6366f1",
|
|
10394
|
-
strokeWidth: 2,
|
|
10395
|
-
opacity: 1
|
|
10396
|
-
},
|
|
10397
|
-
selected: {
|
|
10398
|
-
strokeColor: "#3b82f6",
|
|
10399
|
-
strokeWidth: 3,
|
|
10400
|
-
opacity: 1
|
|
10401
|
-
}
|
|
10402
|
-
},
|
|
10403
|
-
text: {
|
|
10404
|
-
font: "14px sans-serif",
|
|
10405
|
-
fontSize: 14,
|
|
10406
|
-
fontFamily: "sans-serif",
|
|
10407
|
-
fontWeight: "normal",
|
|
10408
|
-
color: "#333333",
|
|
10409
|
-
align: "center",
|
|
10410
|
-
baseline: "middle"
|
|
10411
|
-
},
|
|
10412
|
-
port: {
|
|
10413
|
-
default: { color: "#666666", radius: 6 },
|
|
10414
|
-
hover: { color: "#3b82f6", radius: 7 }
|
|
10415
|
-
},
|
|
10416
|
-
group: {
|
|
10417
|
-
default: {
|
|
10418
|
-
fillColor: "rgba(200, 200, 200, 0.2)",
|
|
10419
|
-
strokeColor: "#999999",
|
|
10420
|
-
strokeWidth: 1,
|
|
10421
|
-
opacity: 1
|
|
10422
|
-
},
|
|
10423
|
-
selected: {
|
|
10424
|
-
fillColor: "rgba(59, 130, 246, 0.1)",
|
|
10425
|
-
strokeColor: "#3b82f6",
|
|
10426
|
-
strokeWidth: 2,
|
|
10427
|
-
opacity: 1
|
|
10503
|
+
const DEFAULT_FONT_FAMILY = "sans-serif";
|
|
10504
|
+
const DEFAULT_FONT_SIZE = 14;
|
|
10505
|
+
const DEFAULT_LINE_HEIGHT = 1.2;
|
|
10506
|
+
const DEFAULT_COLOR$1 = "#000000";
|
|
10507
|
+
class CText {
|
|
10508
|
+
constructor(options) {
|
|
10509
|
+
this.type = "text";
|
|
10510
|
+
this._cachedLines = null;
|
|
10511
|
+
this.id = options.id;
|
|
10512
|
+
this._text = options.text;
|
|
10513
|
+
this._fontFamily = options.fontFamily ?? DEFAULT_FONT_FAMILY;
|
|
10514
|
+
this._fontWeight = options.fontWeight ?? "normal";
|
|
10515
|
+
this._fontStyle = options.fontStyle ?? "normal";
|
|
10516
|
+
this._fontSize = options.fontSize ?? DEFAULT_FONT_SIZE;
|
|
10517
|
+
this._color = options.color ?? DEFAULT_COLOR$1;
|
|
10518
|
+
this._align = options.align ?? "center";
|
|
10519
|
+
this._verticalAlign = options.verticalAlign ?? "middle";
|
|
10520
|
+
this._maxLines = options.maxLines;
|
|
10521
|
+
this._lineHeight = options.lineHeight ?? DEFAULT_LINE_HEIGHT;
|
|
10522
|
+
this._role = options.role;
|
|
10523
|
+
this._rotation = options.rotation ?? 0;
|
|
10524
|
+
this.style = options.style ?? {};
|
|
10525
|
+
}
|
|
10526
|
+
// --- Property accessors with dirty notification ---
|
|
10527
|
+
get text() {
|
|
10528
|
+
return this._text;
|
|
10529
|
+
}
|
|
10530
|
+
set text(value) {
|
|
10531
|
+
if (this._text !== value) {
|
|
10532
|
+
this._text = value;
|
|
10533
|
+
this._cachedLines = null;
|
|
10534
|
+
this._onChange?.();
|
|
10428
10535
|
}
|
|
10429
10536
|
}
|
|
10430
|
-
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
connectionPreview: "#6366f1"
|
|
10438
|
-
},
|
|
10439
|
-
node: {
|
|
10440
|
-
default: {
|
|
10441
|
-
fillColor: "#2d2d2d",
|
|
10442
|
-
strokeColor: "#555555",
|
|
10443
|
-
strokeWidth: 2,
|
|
10444
|
-
opacity: 1,
|
|
10445
|
-
cornerRadius: 4
|
|
10446
|
-
},
|
|
10447
|
-
hover: {
|
|
10448
|
-
fillColor: "#3d3d3d",
|
|
10449
|
-
strokeColor: "#6366f1",
|
|
10450
|
-
strokeWidth: 2,
|
|
10451
|
-
opacity: 1,
|
|
10452
|
-
cornerRadius: 4
|
|
10453
|
-
},
|
|
10454
|
-
selected: {
|
|
10455
|
-
fillColor: "#2d2d2d",
|
|
10456
|
-
strokeColor: "#555555",
|
|
10457
|
-
strokeWidth: 2,
|
|
10458
|
-
opacity: 1,
|
|
10459
|
-
cornerRadius: 4
|
|
10460
|
-
},
|
|
10461
|
-
dragging: {
|
|
10462
|
-
fillColor: "#404040",
|
|
10463
|
-
strokeColor: "#555555",
|
|
10464
|
-
strokeWidth: 2,
|
|
10465
|
-
opacity: 0.8,
|
|
10466
|
-
cornerRadius: 4
|
|
10537
|
+
get fontFamily() {
|
|
10538
|
+
return this._fontFamily;
|
|
10539
|
+
}
|
|
10540
|
+
set fontFamily(value) {
|
|
10541
|
+
if (this._fontFamily !== value) {
|
|
10542
|
+
this._fontFamily = value;
|
|
10543
|
+
this._onChange?.();
|
|
10467
10544
|
}
|
|
10468
|
-
}
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
strokeColor: "#6366f1",
|
|
10477
|
-
strokeWidth: 2,
|
|
10478
|
-
opacity: 1
|
|
10479
|
-
},
|
|
10480
|
-
selected: {
|
|
10481
|
-
strokeColor: "#818cf8",
|
|
10482
|
-
strokeWidth: 3,
|
|
10483
|
-
opacity: 1
|
|
10545
|
+
}
|
|
10546
|
+
get fontWeight() {
|
|
10547
|
+
return this._fontWeight;
|
|
10548
|
+
}
|
|
10549
|
+
set fontWeight(value) {
|
|
10550
|
+
if (this._fontWeight !== value) {
|
|
10551
|
+
this._fontWeight = value;
|
|
10552
|
+
this._onChange?.();
|
|
10484
10553
|
}
|
|
10485
|
-
}
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
baseline: "middle"
|
|
10494
|
-
},
|
|
10495
|
-
port: {
|
|
10496
|
-
default: { color: "#888888", radius: 6 },
|
|
10497
|
-
hover: { color: "#6366f1", radius: 7 }
|
|
10498
|
-
},
|
|
10499
|
-
group: {
|
|
10500
|
-
default: {
|
|
10501
|
-
fillColor: "rgba(100, 100, 100, 0.2)",
|
|
10502
|
-
strokeColor: "#666666",
|
|
10503
|
-
strokeWidth: 1,
|
|
10504
|
-
opacity: 1
|
|
10505
|
-
},
|
|
10506
|
-
selected: {
|
|
10507
|
-
fillColor: "rgba(99, 102, 241, 0.2)",
|
|
10508
|
-
strokeColor: "#6366f1",
|
|
10509
|
-
strokeWidth: 2,
|
|
10510
|
-
opacity: 1
|
|
10554
|
+
}
|
|
10555
|
+
get fontStyle() {
|
|
10556
|
+
return this._fontStyle;
|
|
10557
|
+
}
|
|
10558
|
+
set fontStyle(value) {
|
|
10559
|
+
if (this._fontStyle !== value) {
|
|
10560
|
+
this._fontStyle = value;
|
|
10561
|
+
this._onChange?.();
|
|
10511
10562
|
}
|
|
10512
10563
|
}
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
this.
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10564
|
+
get fontSize() {
|
|
10565
|
+
return this._fontSize;
|
|
10566
|
+
}
|
|
10567
|
+
set fontSize(value) {
|
|
10568
|
+
if (this._fontSize !== value) {
|
|
10569
|
+
this._fontSize = value;
|
|
10570
|
+
this._onChange?.();
|
|
10571
|
+
}
|
|
10572
|
+
}
|
|
10573
|
+
get color() {
|
|
10574
|
+
return this._color;
|
|
10575
|
+
}
|
|
10576
|
+
set color(value) {
|
|
10577
|
+
if (this._color !== value) {
|
|
10578
|
+
this._color = value;
|
|
10579
|
+
this._onChange?.();
|
|
10580
|
+
}
|
|
10581
|
+
}
|
|
10582
|
+
get align() {
|
|
10583
|
+
return this._align;
|
|
10584
|
+
}
|
|
10585
|
+
set align(value) {
|
|
10586
|
+
if (this._align !== value) {
|
|
10587
|
+
this._align = value;
|
|
10588
|
+
this._onChange?.();
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10591
|
+
get verticalAlign() {
|
|
10592
|
+
return this._verticalAlign;
|
|
10593
|
+
}
|
|
10594
|
+
set verticalAlign(value) {
|
|
10595
|
+
if (this._verticalAlign !== value) {
|
|
10596
|
+
this._verticalAlign = value;
|
|
10597
|
+
this._onChange?.();
|
|
10598
|
+
}
|
|
10599
|
+
}
|
|
10600
|
+
get maxLines() {
|
|
10601
|
+
return this._maxLines;
|
|
10602
|
+
}
|
|
10603
|
+
set maxLines(value) {
|
|
10604
|
+
if (this._maxLines !== value) {
|
|
10605
|
+
this._maxLines = value;
|
|
10606
|
+
this._onChange?.();
|
|
10607
|
+
}
|
|
10608
|
+
}
|
|
10609
|
+
get lineHeight() {
|
|
10610
|
+
return this._lineHeight;
|
|
10611
|
+
}
|
|
10612
|
+
get role() {
|
|
10613
|
+
return this._role;
|
|
10614
|
+
}
|
|
10615
|
+
get rotation() {
|
|
10616
|
+
return this._rotation;
|
|
10617
|
+
}
|
|
10618
|
+
set rotation(value) {
|
|
10619
|
+
if (this._rotation !== value) {
|
|
10620
|
+
this._rotation = value;
|
|
10621
|
+
this._onChange?.();
|
|
10622
|
+
}
|
|
10623
|
+
}
|
|
10624
|
+
setOnChange(cb) {
|
|
10625
|
+
this._onChange = cb;
|
|
10626
|
+
}
|
|
10627
|
+
getFont() {
|
|
10628
|
+
return `${this._fontStyle} ${this._fontWeight} ${this._fontSize}px ${this._fontFamily}`;
|
|
10629
|
+
}
|
|
10630
|
+
getLineHeightPx() {
|
|
10631
|
+
return this._fontSize * this._lineHeight;
|
|
10632
|
+
}
|
|
10633
|
+
/**
|
|
10634
|
+
* Word-wrap text to fit within maxWidth.
|
|
10635
|
+
*/
|
|
10636
|
+
wrapText(ctx, maxWidth) {
|
|
10637
|
+
ctx.font = this.getFont();
|
|
10638
|
+
const words = this._text.split(" ");
|
|
10639
|
+
const lines = [];
|
|
10640
|
+
let currentLine = "";
|
|
10641
|
+
for (const word of words) {
|
|
10642
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
10643
|
+
const metrics = ctx.measureText(testLine);
|
|
10644
|
+
if (metrics.width > maxWidth && currentLine) {
|
|
10645
|
+
lines.push(currentLine);
|
|
10646
|
+
currentLine = word;
|
|
10647
|
+
} else {
|
|
10648
|
+
currentLine = testLine;
|
|
10649
|
+
}
|
|
10650
|
+
}
|
|
10651
|
+
if (currentLine) {
|
|
10652
|
+
lines.push(currentLine);
|
|
10653
|
+
}
|
|
10654
|
+
if (this._maxLines !== void 0 && lines.length > this._maxLines) {
|
|
10655
|
+
const truncated = lines.slice(0, this._maxLines);
|
|
10656
|
+
const lastLine = truncated[truncated.length - 1];
|
|
10657
|
+
if (lastLine !== void 0) {
|
|
10658
|
+
truncated[truncated.length - 1] = lastLine + "…";
|
|
10659
|
+
}
|
|
10660
|
+
return truncated;
|
|
10661
|
+
}
|
|
10662
|
+
return lines;
|
|
10663
|
+
}
|
|
10664
|
+
measure(ctx) {
|
|
10665
|
+
ctx.font = this.getFont();
|
|
10666
|
+
const lineHeightPx = this.getLineHeightPx();
|
|
10667
|
+
const rawLines = this._text.split("\n");
|
|
10668
|
+
let maxWidth = 0;
|
|
10669
|
+
for (const line of rawLines) {
|
|
10670
|
+
const metrics = ctx.measureText(line);
|
|
10671
|
+
if (metrics.width > maxWidth) {
|
|
10672
|
+
maxWidth = metrics.width;
|
|
10673
|
+
}
|
|
10674
|
+
}
|
|
10675
|
+
let lineCount = rawLines.length;
|
|
10676
|
+
if (this._maxLines !== void 0 && lineCount > this._maxLines) {
|
|
10677
|
+
lineCount = this._maxLines;
|
|
10678
|
+
}
|
|
10679
|
+
const width = maxWidth;
|
|
10680
|
+
const height = lineCount * lineHeightPx;
|
|
10681
|
+
if (this._rotation === 90 || this._rotation === -90) {
|
|
10682
|
+
return { width: height, height: width };
|
|
10683
|
+
}
|
|
10684
|
+
return { width, height };
|
|
10685
|
+
}
|
|
10686
|
+
render(ctx, bounds) {
|
|
10687
|
+
if (this.style.visible === false) return;
|
|
10688
|
+
const opacity = this.style.opacity ?? 1;
|
|
10689
|
+
if (opacity <= 0) return;
|
|
10690
|
+
ctx.save();
|
|
10691
|
+
ctx.globalAlpha *= opacity;
|
|
10692
|
+
ctx.font = this.getFont();
|
|
10693
|
+
ctx.fillStyle = this._color;
|
|
10694
|
+
const isRotated = this._rotation === 90 || this._rotation === -90;
|
|
10695
|
+
const textBounds = isRotated ? { x: bounds.x, y: bounds.y, width: bounds.height, height: bounds.width } : bounds;
|
|
10696
|
+
const lines = this.wrapText(ctx, textBounds.width);
|
|
10697
|
+
this._cachedLines = lines;
|
|
10698
|
+
const lineHeightPx = this.getLineHeightPx();
|
|
10699
|
+
const totalTextHeight = lines.length * lineHeightPx;
|
|
10700
|
+
let textAlign;
|
|
10701
|
+
let xBase;
|
|
10702
|
+
switch (this._align) {
|
|
10703
|
+
case "left":
|
|
10704
|
+
textAlign = "left";
|
|
10705
|
+
xBase = textBounds.x;
|
|
10706
|
+
break;
|
|
10707
|
+
case "right":
|
|
10708
|
+
textAlign = "right";
|
|
10709
|
+
xBase = textBounds.x + textBounds.width;
|
|
10710
|
+
break;
|
|
10711
|
+
default:
|
|
10712
|
+
textAlign = "center";
|
|
10713
|
+
xBase = textBounds.x + textBounds.width / 2;
|
|
10714
|
+
break;
|
|
10715
|
+
}
|
|
10716
|
+
let yStart;
|
|
10717
|
+
switch (this._verticalAlign) {
|
|
10718
|
+
case "top":
|
|
10719
|
+
yStart = textBounds.y + lineHeightPx / 2;
|
|
10720
|
+
break;
|
|
10721
|
+
case "bottom":
|
|
10722
|
+
yStart = textBounds.y + textBounds.height - totalTextHeight + lineHeightPx / 2;
|
|
10723
|
+
break;
|
|
10724
|
+
default:
|
|
10725
|
+
yStart = textBounds.y + (textBounds.height - totalTextHeight) / 2 + lineHeightPx / 2;
|
|
10726
|
+
break;
|
|
10727
|
+
}
|
|
10728
|
+
ctx.textAlign = textAlign;
|
|
10729
|
+
ctx.textBaseline = "middle";
|
|
10730
|
+
if (isRotated) {
|
|
10731
|
+
const cx = bounds.x + bounds.width / 2;
|
|
10732
|
+
const cy = bounds.y + bounds.height / 2;
|
|
10733
|
+
ctx.translate(cx, cy);
|
|
10734
|
+
ctx.rotate(this._rotation * Math.PI / 180);
|
|
10735
|
+
const offsetX = xBase - (textBounds.x + textBounds.width / 2);
|
|
10736
|
+
const offsetY = yStart - (textBounds.y + textBounds.height / 2);
|
|
10737
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10738
|
+
ctx.fillText(lines[i], offsetX, offsetY + i * lineHeightPx);
|
|
10739
|
+
}
|
|
10740
|
+
} else {
|
|
10741
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10742
|
+
ctx.fillText(lines[i], xBase, yStart + i * lineHeightPx);
|
|
10743
|
+
}
|
|
10744
|
+
}
|
|
10745
|
+
ctx.restore();
|
|
10746
|
+
}
|
|
10747
|
+
hitTest(point, bounds) {
|
|
10748
|
+
if (this.style.visible === false) return null;
|
|
10749
|
+
if (point.x >= bounds.x && point.x <= bounds.x + bounds.width && point.y >= bounds.y && point.y <= bounds.y + bounds.height) {
|
|
10750
|
+
return this;
|
|
10751
|
+
}
|
|
10752
|
+
return null;
|
|
10753
|
+
}
|
|
10754
|
+
serialize() {
|
|
10755
|
+
const data = {
|
|
10756
|
+
type: "text",
|
|
10757
|
+
text: this._text
|
|
10758
|
+
};
|
|
10759
|
+
if (this.id !== void 0) data.id = this.id;
|
|
10760
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
10761
|
+
if (this._fontFamily !== DEFAULT_FONT_FAMILY) data.fontFamily = this._fontFamily;
|
|
10762
|
+
if (this._fontWeight !== "normal") data.fontWeight = this._fontWeight;
|
|
10763
|
+
if (this._fontStyle !== "normal") data.fontStyle = this._fontStyle;
|
|
10764
|
+
if (this._fontSize !== DEFAULT_FONT_SIZE) data.fontSize = this._fontSize;
|
|
10765
|
+
if (this._color !== DEFAULT_COLOR$1) data.color = this._color;
|
|
10766
|
+
if (this._align !== "center") data.align = this._align;
|
|
10767
|
+
if (this._verticalAlign !== "middle") data.verticalAlign = this._verticalAlign;
|
|
10768
|
+
if (this._maxLines !== void 0) data.maxLines = this._maxLines;
|
|
10769
|
+
if (this._lineHeight !== DEFAULT_LINE_HEIGHT) data.lineHeight = this._lineHeight;
|
|
10770
|
+
if (this._role !== void 0) data.role = this._role;
|
|
10771
|
+
if (this._rotation !== 0) data.rotation = this._rotation;
|
|
10772
|
+
return data;
|
|
10773
|
+
}
|
|
10774
|
+
toSVG(bounds) {
|
|
10775
|
+
if (this.style.visible === false) return "";
|
|
10776
|
+
const lineHeightPx = this.getLineHeightPx();
|
|
10777
|
+
const displayLines = this._cachedLines ?? this._text.split("\n");
|
|
10778
|
+
const totalTextHeight = displayLines.length * lineHeightPx;
|
|
10779
|
+
let anchor;
|
|
10780
|
+
let xBase;
|
|
10781
|
+
switch (this._align) {
|
|
10782
|
+
case "left":
|
|
10783
|
+
anchor = "start";
|
|
10784
|
+
xBase = bounds.x;
|
|
10785
|
+
break;
|
|
10786
|
+
case "right":
|
|
10787
|
+
anchor = "end";
|
|
10788
|
+
xBase = bounds.x + bounds.width;
|
|
10789
|
+
break;
|
|
10790
|
+
default:
|
|
10791
|
+
anchor = "middle";
|
|
10792
|
+
xBase = bounds.x + bounds.width / 2;
|
|
10793
|
+
break;
|
|
10794
|
+
}
|
|
10795
|
+
let yStart;
|
|
10796
|
+
switch (this._verticalAlign) {
|
|
10797
|
+
case "top":
|
|
10798
|
+
yStart = bounds.y + lineHeightPx * 0.75;
|
|
10799
|
+
break;
|
|
10800
|
+
case "bottom":
|
|
10801
|
+
yStart = bounds.y + bounds.height - totalTextHeight + lineHeightPx * 0.75;
|
|
10802
|
+
break;
|
|
10803
|
+
default:
|
|
10804
|
+
yStart = bounds.y + (bounds.height - totalTextHeight) / 2 + lineHeightPx * 0.75;
|
|
10805
|
+
break;
|
|
10806
|
+
}
|
|
10807
|
+
const fontAttrs = [
|
|
10808
|
+
`font-family="${this._fontFamily}"`,
|
|
10809
|
+
`font-size="${this._fontSize}"`,
|
|
10810
|
+
this._fontWeight !== "normal" ? `font-weight="${this._fontWeight}"` : "",
|
|
10811
|
+
this._fontStyle !== "normal" ? `font-style="${this._fontStyle}"` : ""
|
|
10812
|
+
].filter(Boolean).join(" ");
|
|
10813
|
+
const transform = this._rotation !== 0 ? ` transform="rotate(${this._rotation}, ${bounds.x + bounds.width / 2}, ${bounds.y + bounds.height / 2})"` : "";
|
|
10814
|
+
const tspans = displayLines.map(
|
|
10815
|
+
(line, i) => `<tspan x="${xBase}" dy="${i === 0 ? 0 : lineHeightPx}">${escapeXml(line)}</tspan>`
|
|
10816
|
+
).join("");
|
|
10817
|
+
return `<text x="${xBase}" y="${yStart}" ${fontAttrs} fill="${this._color}" text-anchor="${anchor}"${transform}>${tspans}</text>`;
|
|
10818
|
+
}
|
|
10819
|
+
}
|
|
10820
|
+
function escapeXml(str) {
|
|
10821
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
10822
|
+
}
|
|
10823
|
+
const DEFAULT_ICON_SIZE = 24;
|
|
10824
|
+
const svgFetchCache = new LRUCache(100);
|
|
10825
|
+
class CIcon {
|
|
10826
|
+
constructor(options) {
|
|
10827
|
+
this.type = "icon";
|
|
10828
|
+
this._loaded = false;
|
|
10829
|
+
this.id = options.id;
|
|
10830
|
+
this._source = options.source;
|
|
10831
|
+
this._width = options.width ?? DEFAULT_ICON_SIZE;
|
|
10832
|
+
this._height = options.height ?? DEFAULT_ICON_SIZE;
|
|
10833
|
+
this._backgroundColor = options.backgroundColor;
|
|
10834
|
+
this._fillColor = options.fillColor;
|
|
10835
|
+
this._bindsNotationIcon = options.bindsNotationIcon ?? false;
|
|
10836
|
+
this.onClick = options.onClick;
|
|
10837
|
+
this.style = options.style ?? {};
|
|
10838
|
+
if (options.visible === false) {
|
|
10839
|
+
this.style.visible = false;
|
|
10840
|
+
}
|
|
10841
|
+
this._image = new Image();
|
|
10842
|
+
this._image.onload = () => {
|
|
10843
|
+
this._loaded = true;
|
|
10844
|
+
this._onChange?.();
|
|
10845
|
+
};
|
|
10846
|
+
this._image.onerror = () => {
|
|
10847
|
+
this._onChange?.();
|
|
10848
|
+
};
|
|
10849
|
+
void this.loadSource(this._source);
|
|
10850
|
+
}
|
|
10851
|
+
get source() {
|
|
10852
|
+
return this._source;
|
|
10853
|
+
}
|
|
10854
|
+
set source(value) {
|
|
10855
|
+
if (this._source !== value) {
|
|
10856
|
+
this._source = value;
|
|
10857
|
+
this._loaded = false;
|
|
10858
|
+
void this.loadSource(value);
|
|
10859
|
+
this._onChange?.();
|
|
10860
|
+
}
|
|
10861
|
+
}
|
|
10862
|
+
get width() {
|
|
10863
|
+
return this._width;
|
|
10864
|
+
}
|
|
10865
|
+
set width(value) {
|
|
10866
|
+
if (this._width !== value) {
|
|
10867
|
+
this._width = value;
|
|
10868
|
+
this._onChange?.();
|
|
10869
|
+
}
|
|
10870
|
+
}
|
|
10871
|
+
get height() {
|
|
10872
|
+
return this._height;
|
|
10873
|
+
}
|
|
10874
|
+
set height(value) {
|
|
10875
|
+
if (this._height !== value) {
|
|
10876
|
+
this._height = value;
|
|
10877
|
+
this._onChange?.();
|
|
10878
|
+
}
|
|
10879
|
+
}
|
|
10880
|
+
get backgroundColor() {
|
|
10881
|
+
return this._backgroundColor;
|
|
10882
|
+
}
|
|
10883
|
+
set backgroundColor(value) {
|
|
10884
|
+
if (this._backgroundColor !== value) {
|
|
10885
|
+
this._backgroundColor = value;
|
|
10886
|
+
this._onChange?.();
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10889
|
+
get fillColor() {
|
|
10890
|
+
return this._fillColor;
|
|
10891
|
+
}
|
|
10892
|
+
set fillColor(value) {
|
|
10893
|
+
if (this._fillColor !== value) {
|
|
10894
|
+
this._fillColor = value;
|
|
10895
|
+
void this.loadSource(this._source);
|
|
10896
|
+
this._onChange?.();
|
|
10897
|
+
}
|
|
10898
|
+
}
|
|
10899
|
+
get loaded() {
|
|
10900
|
+
return this._loaded;
|
|
10901
|
+
}
|
|
10902
|
+
get bindsNotationIcon() {
|
|
10903
|
+
return this._bindsNotationIcon;
|
|
10904
|
+
}
|
|
10905
|
+
set bindsNotationIcon(value) {
|
|
10906
|
+
if (this._bindsNotationIcon !== value) {
|
|
10907
|
+
this._bindsNotationIcon = value;
|
|
10908
|
+
this._onChange?.();
|
|
10909
|
+
}
|
|
10910
|
+
}
|
|
10911
|
+
setOnChange(cb) {
|
|
10912
|
+
this._onChange = cb;
|
|
10913
|
+
}
|
|
10914
|
+
async loadSource(source) {
|
|
10915
|
+
if (isSvgMarkup(source)) {
|
|
10916
|
+
const tinted = this._fillColor ? tintSvg(source, void 0, this._fillColor) : source;
|
|
10917
|
+
this._image.src = svgToDataUrl(tinted);
|
|
10918
|
+
} else if (isSvgUrl(source) && this._fillColor) {
|
|
10919
|
+
try {
|
|
10920
|
+
const svgText = await this.fetchSvg(source);
|
|
10921
|
+
const tinted = tintSvg(svgText, void 0, this._fillColor);
|
|
10922
|
+
this._image.src = svgToDataUrl(tinted);
|
|
10923
|
+
} catch {
|
|
10924
|
+
this._image.src = source;
|
|
10925
|
+
}
|
|
10926
|
+
} else {
|
|
10927
|
+
this._image.src = source;
|
|
10928
|
+
}
|
|
10929
|
+
}
|
|
10930
|
+
async fetchSvg(url) {
|
|
10931
|
+
let promise = svgFetchCache.get(url);
|
|
10932
|
+
if (!promise) {
|
|
10933
|
+
promise = fetch(url).then((r) => r.text());
|
|
10934
|
+
svgFetchCache.set(url, promise);
|
|
10935
|
+
}
|
|
10936
|
+
return promise;
|
|
10937
|
+
}
|
|
10938
|
+
measure(_ctx) {
|
|
10939
|
+
return { width: this._width, height: this._height };
|
|
10940
|
+
}
|
|
10941
|
+
render(ctx, bounds) {
|
|
10942
|
+
if (this.style.visible === false) return;
|
|
10943
|
+
const opacity = this.style.opacity ?? 1;
|
|
10944
|
+
if (opacity <= 0) return;
|
|
10945
|
+
ctx.save();
|
|
10946
|
+
ctx.globalAlpha *= opacity;
|
|
10947
|
+
if (this._backgroundColor) {
|
|
10948
|
+
ctx.fillStyle = this._backgroundColor;
|
|
10949
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
10950
|
+
}
|
|
10951
|
+
if (this._loaded) {
|
|
10952
|
+
const drawWidth = Math.min(this._width, bounds.width);
|
|
10953
|
+
const drawHeight = Math.min(this._height, bounds.height);
|
|
10954
|
+
const dx = bounds.x + (bounds.width - drawWidth) / 2;
|
|
10955
|
+
const dy = bounds.y + (bounds.height - drawHeight) / 2;
|
|
10956
|
+
ctx.drawImage(this._image, dx, dy, drawWidth, drawHeight);
|
|
10957
|
+
}
|
|
10958
|
+
ctx.restore();
|
|
10959
|
+
}
|
|
10960
|
+
hitTest(point, bounds) {
|
|
10961
|
+
if (this.style.visible === false) return null;
|
|
10962
|
+
if (point.x >= bounds.x && point.x <= bounds.x + bounds.width && point.y >= bounds.y && point.y <= bounds.y + bounds.height) {
|
|
10963
|
+
return this;
|
|
10964
|
+
}
|
|
10965
|
+
return null;
|
|
10966
|
+
}
|
|
10967
|
+
serialize() {
|
|
10968
|
+
const data = {
|
|
10969
|
+
type: "icon",
|
|
10970
|
+
source: this._source,
|
|
10971
|
+
width: this._width,
|
|
10972
|
+
height: this._height
|
|
10973
|
+
};
|
|
10974
|
+
if (this.id !== void 0) data.id = this.id;
|
|
10975
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
10976
|
+
if (this._backgroundColor) data.backgroundColor = this._backgroundColor;
|
|
10977
|
+
if (this._fillColor) data.fillColor = this._fillColor;
|
|
10978
|
+
if (this._bindsNotationIcon) data.bindsNotationIcon = true;
|
|
10979
|
+
return data;
|
|
10980
|
+
}
|
|
10981
|
+
toSVG(bounds) {
|
|
10982
|
+
if (this.style.visible === false) return "";
|
|
10983
|
+
let svg = "";
|
|
10984
|
+
if (this._backgroundColor) {
|
|
10985
|
+
svg += `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}" fill="${this._backgroundColor}" />`;
|
|
10986
|
+
}
|
|
10987
|
+
const drawWidth = Math.min(this._width, bounds.width);
|
|
10988
|
+
const drawHeight = Math.min(this._height, bounds.height);
|
|
10989
|
+
const dx = bounds.x + (bounds.width - drawWidth) / 2;
|
|
10990
|
+
const dy = bounds.y + (bounds.height - drawHeight) / 2;
|
|
10991
|
+
svg += `<image href="${escapeXmlAttr(this._source)}" x="${dx}" y="${dy}" width="${drawWidth}" height="${drawHeight}" />`;
|
|
10992
|
+
return svg;
|
|
10993
|
+
}
|
|
10994
|
+
}
|
|
10995
|
+
function escapeXmlAttr(str) {
|
|
10996
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
10997
|
+
}
|
|
10998
|
+
const DEFAULT_COLOR = "#cccccc";
|
|
10999
|
+
const DEFAULT_THICKNESS = 1;
|
|
11000
|
+
class CDivider {
|
|
11001
|
+
constructor(options = {}) {
|
|
11002
|
+
this.type = "divider";
|
|
11003
|
+
this.id = options.id;
|
|
11004
|
+
this._color = options.color ?? DEFAULT_COLOR;
|
|
11005
|
+
this._thickness = options.thickness ?? DEFAULT_THICKNESS;
|
|
11006
|
+
this.style = options.style ?? {};
|
|
11007
|
+
}
|
|
11008
|
+
get color() {
|
|
11009
|
+
return this._color;
|
|
11010
|
+
}
|
|
11011
|
+
set color(value) {
|
|
11012
|
+
if (this._color !== value) {
|
|
11013
|
+
this._color = value;
|
|
11014
|
+
this._onChange?.();
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
get thickness() {
|
|
11018
|
+
return this._thickness;
|
|
11019
|
+
}
|
|
11020
|
+
set thickness(value) {
|
|
11021
|
+
if (this._thickness !== value) {
|
|
11022
|
+
this._thickness = value;
|
|
11023
|
+
this._onChange?.();
|
|
11024
|
+
}
|
|
11025
|
+
}
|
|
11026
|
+
setOnChange(cb) {
|
|
11027
|
+
this._onChange = cb;
|
|
11028
|
+
}
|
|
11029
|
+
measure(_ctx) {
|
|
11030
|
+
return { width: this._thickness, height: this._thickness };
|
|
11031
|
+
}
|
|
11032
|
+
render(ctx, bounds) {
|
|
11033
|
+
if (this.style.visible === false) return;
|
|
11034
|
+
ctx.save();
|
|
11035
|
+
ctx.strokeStyle = this._color;
|
|
11036
|
+
ctx.lineWidth = this._thickness;
|
|
11037
|
+
ctx.globalAlpha *= this.style.opacity ?? 1;
|
|
11038
|
+
ctx.beginPath();
|
|
11039
|
+
if (bounds.width >= bounds.height) {
|
|
11040
|
+
const y = bounds.y + bounds.height / 2;
|
|
11041
|
+
ctx.moveTo(bounds.x, y);
|
|
11042
|
+
ctx.lineTo(bounds.x + bounds.width, y);
|
|
11043
|
+
} else {
|
|
11044
|
+
const x = bounds.x + bounds.width / 2;
|
|
11045
|
+
ctx.moveTo(x, bounds.y);
|
|
11046
|
+
ctx.lineTo(x, bounds.y + bounds.height);
|
|
11047
|
+
}
|
|
11048
|
+
ctx.stroke();
|
|
11049
|
+
ctx.restore();
|
|
11050
|
+
}
|
|
11051
|
+
hitTest(_point, _bounds) {
|
|
11052
|
+
return null;
|
|
11053
|
+
}
|
|
11054
|
+
serialize() {
|
|
11055
|
+
const data = { type: "divider" };
|
|
11056
|
+
if (this.id !== void 0) data.id = this.id;
|
|
11057
|
+
if (this._color !== DEFAULT_COLOR) data.color = this._color;
|
|
11058
|
+
if (this._thickness !== DEFAULT_THICKNESS) data.thickness = this._thickness;
|
|
11059
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
11060
|
+
return data;
|
|
11061
|
+
}
|
|
11062
|
+
toSVG(bounds) {
|
|
11063
|
+
if (this.style.visible === false) return "";
|
|
11064
|
+
if (bounds.width >= bounds.height) {
|
|
11065
|
+
const y = bounds.y + bounds.height / 2;
|
|
11066
|
+
return `<line x1="${bounds.x}" y1="${y}" x2="${bounds.x + bounds.width}" y2="${y}" stroke="${this._color}" stroke-width="${this._thickness}" />`;
|
|
11067
|
+
} else {
|
|
11068
|
+
const x = bounds.x + bounds.width / 2;
|
|
11069
|
+
return `<line x1="${x}" y1="${bounds.y}" x2="${x}" y2="${bounds.y + bounds.height}" stroke="${this._color}" stroke-width="${this._thickness}" />`;
|
|
11070
|
+
}
|
|
11071
|
+
}
|
|
11072
|
+
}
|
|
11073
|
+
function normalizeSides(value, fallback = 0) {
|
|
11074
|
+
if (value === void 0) {
|
|
11075
|
+
return { top: fallback, right: fallback, bottom: fallback, left: fallback };
|
|
11076
|
+
}
|
|
11077
|
+
if (typeof value === "number") {
|
|
11078
|
+
return { top: value, right: value, bottom: value, left: value };
|
|
11079
|
+
}
|
|
11080
|
+
return {
|
|
11081
|
+
top: value.top ?? fallback,
|
|
11082
|
+
right: value.right ?? fallback,
|
|
11083
|
+
bottom: value.bottom ?? fallback,
|
|
11084
|
+
left: value.left ?? fallback
|
|
11085
|
+
};
|
|
11086
|
+
}
|
|
11087
|
+
function flexLayout(container2, config, children) {
|
|
11088
|
+
const pad = normalizeSides(config.padding);
|
|
11089
|
+
const isRow = config.direction === "row";
|
|
11090
|
+
const innerWidth = Math.max(0, container2.width - pad.left - pad.right);
|
|
11091
|
+
const innerHeight = Math.max(0, container2.height - pad.top - pad.bottom);
|
|
11092
|
+
const availableMain = isRow ? innerWidth : innerHeight;
|
|
11093
|
+
const availableCross = isRow ? innerHeight : innerWidth;
|
|
11094
|
+
if (children.length === 0) {
|
|
11095
|
+
return {
|
|
11096
|
+
childBounds: [],
|
|
11097
|
+
contentSize: { width: pad.left + pad.right, height: pad.top + pad.bottom }
|
|
11098
|
+
};
|
|
11099
|
+
}
|
|
11100
|
+
const baseSizes = [];
|
|
11101
|
+
const mainMargins = [];
|
|
11102
|
+
const crossMargins = [];
|
|
11103
|
+
for (const child of children) {
|
|
11104
|
+
const m = child.margin;
|
|
11105
|
+
if (isRow) {
|
|
11106
|
+
mainMargins.push({ before: m.left, after: m.right });
|
|
11107
|
+
crossMargins.push({ before: m.top, after: m.bottom });
|
|
11108
|
+
} else {
|
|
11109
|
+
mainMargins.push({ before: m.top, after: m.bottom });
|
|
11110
|
+
crossMargins.push({ before: m.left, after: m.right });
|
|
11111
|
+
}
|
|
11112
|
+
const measured = isRow ? child.measure.width : child.measure.height;
|
|
11113
|
+
const minMain = isRow ? child.minSize.width : child.minSize.height;
|
|
11114
|
+
const basis = child.flexBasis === "auto" ? measured : child.flexBasis;
|
|
11115
|
+
baseSizes.push(Math.max(basis, minMain));
|
|
11116
|
+
}
|
|
11117
|
+
const totalGaps = config.gap * Math.max(0, children.length - 1);
|
|
11118
|
+
let totalMargins = 0;
|
|
11119
|
+
for (const mm of mainMargins) {
|
|
11120
|
+
totalMargins += mm.before + mm.after;
|
|
11121
|
+
}
|
|
11122
|
+
let totalBase = 0;
|
|
11123
|
+
for (const s of baseSizes) {
|
|
11124
|
+
totalBase += s;
|
|
11125
|
+
}
|
|
11126
|
+
const totalUsed = totalBase + totalGaps + totalMargins;
|
|
11127
|
+
const finalSizes = [...baseSizes];
|
|
11128
|
+
const remaining = availableMain - totalUsed;
|
|
11129
|
+
if (remaining > 0) {
|
|
11130
|
+
let totalGrow = 0;
|
|
11131
|
+
for (const child of children) {
|
|
11132
|
+
totalGrow += child.flexGrow;
|
|
11133
|
+
}
|
|
11134
|
+
if (totalGrow > 0) {
|
|
11135
|
+
for (let i = 0; i < children.length; i++) {
|
|
11136
|
+
const child = children[i];
|
|
11137
|
+
finalSizes[i] = finalSizes[i] + remaining * child.flexGrow / totalGrow;
|
|
11138
|
+
}
|
|
11139
|
+
}
|
|
11140
|
+
} else if (remaining < 0) {
|
|
11141
|
+
let totalShrink = 0;
|
|
11142
|
+
for (let i = 0; i < children.length; i++) {
|
|
11143
|
+
totalShrink += children[i].flexShrink * baseSizes[i];
|
|
11144
|
+
}
|
|
11145
|
+
if (totalShrink > 0) {
|
|
11146
|
+
const deficit = -remaining;
|
|
11147
|
+
for (let i = 0; i < children.length; i++) {
|
|
11148
|
+
const child = children[i];
|
|
11149
|
+
const shrinkRatio = child.flexShrink * baseSizes[i] / totalShrink;
|
|
11150
|
+
const minMain = isRow ? child.minSize.width : child.minSize.height;
|
|
11151
|
+
finalSizes[i] = Math.max(minMain, finalSizes[i] - deficit * shrinkRatio);
|
|
11152
|
+
}
|
|
11153
|
+
}
|
|
11154
|
+
}
|
|
11155
|
+
let actualTotal = totalGaps + totalMargins;
|
|
11156
|
+
for (const s of finalSizes) {
|
|
11157
|
+
actualTotal += s;
|
|
11158
|
+
}
|
|
11159
|
+
const freeSpace = Math.max(0, availableMain - actualTotal);
|
|
11160
|
+
let mainOffset;
|
|
11161
|
+
let betweenExtra;
|
|
11162
|
+
switch (config.justifyContent) {
|
|
11163
|
+
case "center":
|
|
11164
|
+
mainOffset = freeSpace / 2;
|
|
11165
|
+
betweenExtra = 0;
|
|
11166
|
+
break;
|
|
11167
|
+
case "end":
|
|
11168
|
+
mainOffset = freeSpace;
|
|
11169
|
+
betweenExtra = 0;
|
|
11170
|
+
break;
|
|
11171
|
+
case "space-between":
|
|
11172
|
+
mainOffset = 0;
|
|
11173
|
+
betweenExtra = children.length > 1 ? freeSpace / (children.length - 1) : 0;
|
|
11174
|
+
break;
|
|
11175
|
+
case "space-around":
|
|
11176
|
+
betweenExtra = freeSpace / children.length;
|
|
11177
|
+
mainOffset = betweenExtra / 2;
|
|
11178
|
+
break;
|
|
11179
|
+
default:
|
|
11180
|
+
mainOffset = 0;
|
|
11181
|
+
betweenExtra = 0;
|
|
11182
|
+
break;
|
|
11183
|
+
}
|
|
11184
|
+
const childBounds = [];
|
|
11185
|
+
let cursor = mainOffset;
|
|
11186
|
+
let maxCrossContent = 0;
|
|
11187
|
+
for (let i = 0; i < children.length; i++) {
|
|
11188
|
+
const child = children[i];
|
|
11189
|
+
const mainSize = finalSizes[i];
|
|
11190
|
+
const mm = mainMargins[i];
|
|
11191
|
+
const cm = crossMargins[i];
|
|
11192
|
+
cursor += mm.before;
|
|
11193
|
+
const crossAvailable = availableCross - cm.before - cm.after;
|
|
11194
|
+
const measuredCross = isRow ? child.measure.height : child.measure.width;
|
|
11195
|
+
const align = child.alignSelf === "auto" ? config.alignItems : child.alignSelf;
|
|
11196
|
+
let crossSize;
|
|
11197
|
+
let crossOffset;
|
|
11198
|
+
if (align === "stretch") {
|
|
11199
|
+
crossSize = crossAvailable;
|
|
11200
|
+
crossOffset = cm.before;
|
|
11201
|
+
} else {
|
|
11202
|
+
crossSize = Math.min(measuredCross, crossAvailable);
|
|
11203
|
+
switch (align) {
|
|
11204
|
+
case "center":
|
|
11205
|
+
crossOffset = cm.before + (crossAvailable - crossSize) / 2;
|
|
11206
|
+
break;
|
|
11207
|
+
case "end":
|
|
11208
|
+
crossOffset = cm.before + crossAvailable - crossSize;
|
|
11209
|
+
break;
|
|
11210
|
+
default:
|
|
11211
|
+
crossOffset = cm.before;
|
|
11212
|
+
break;
|
|
11213
|
+
}
|
|
11214
|
+
}
|
|
11215
|
+
maxCrossContent = Math.max(maxCrossContent, crossOffset + crossSize + cm.after);
|
|
11216
|
+
if (isRow) {
|
|
11217
|
+
childBounds.push({
|
|
11218
|
+
x: pad.left + cursor,
|
|
11219
|
+
y: pad.top + crossOffset,
|
|
11220
|
+
width: mainSize,
|
|
11221
|
+
height: crossSize
|
|
11222
|
+
});
|
|
11223
|
+
} else {
|
|
11224
|
+
childBounds.push({
|
|
11225
|
+
x: pad.left + crossOffset,
|
|
11226
|
+
y: pad.top + cursor,
|
|
11227
|
+
width: crossSize,
|
|
11228
|
+
height: mainSize
|
|
11229
|
+
});
|
|
11230
|
+
}
|
|
11231
|
+
cursor += mainSize + mm.after;
|
|
11232
|
+
if (i < children.length - 1) {
|
|
11233
|
+
cursor += config.gap + betweenExtra;
|
|
11234
|
+
}
|
|
11235
|
+
}
|
|
11236
|
+
const mainContent = cursor;
|
|
11237
|
+
const contentWidth = isRow ? pad.left + mainContent + pad.right : pad.left + maxCrossContent + pad.right;
|
|
11238
|
+
const contentHeight = isRow ? pad.top + maxCrossContent + pad.bottom : pad.top + mainContent + pad.bottom;
|
|
11239
|
+
return {
|
|
11240
|
+
childBounds,
|
|
11241
|
+
contentSize: { width: contentWidth, height: contentHeight }
|
|
11242
|
+
};
|
|
11243
|
+
}
|
|
11244
|
+
class CContainer {
|
|
11245
|
+
constructor(options = {}) {
|
|
11246
|
+
this.type = "container";
|
|
11247
|
+
this._cachedBounds = null;
|
|
11248
|
+
this._cachedContainerBounds = null;
|
|
11249
|
+
this.id = options.id;
|
|
11250
|
+
this._direction = options.direction ?? "column";
|
|
11251
|
+
this._justifyContent = options.justifyContent ?? "start";
|
|
11252
|
+
this._alignItems = options.alignItems ?? "stretch";
|
|
11253
|
+
this._gap = options.gap ?? 0;
|
|
11254
|
+
this._padding = options.padding ?? 0;
|
|
11255
|
+
this._children = options.children ?? [];
|
|
11256
|
+
this.style = options.style ?? {};
|
|
11257
|
+
for (const child of this._children) {
|
|
11258
|
+
child.setOnChange(() => this.handleChildChange());
|
|
11259
|
+
}
|
|
11260
|
+
}
|
|
11261
|
+
get direction() {
|
|
11262
|
+
return this._direction;
|
|
11263
|
+
}
|
|
11264
|
+
get justifyContent() {
|
|
11265
|
+
return this._justifyContent;
|
|
11266
|
+
}
|
|
11267
|
+
get alignItems() {
|
|
11268
|
+
return this._alignItems;
|
|
11269
|
+
}
|
|
11270
|
+
get gap() {
|
|
11271
|
+
return this._gap;
|
|
11272
|
+
}
|
|
11273
|
+
get padding() {
|
|
11274
|
+
return this._padding;
|
|
11275
|
+
}
|
|
11276
|
+
get children() {
|
|
11277
|
+
return this._children;
|
|
11278
|
+
}
|
|
11279
|
+
addChild(child) {
|
|
11280
|
+
child.setOnChange(() => this.handleChildChange());
|
|
11281
|
+
this._children.push(child);
|
|
11282
|
+
this.handleChildChange();
|
|
11283
|
+
}
|
|
11284
|
+
removeChild(index) {
|
|
11285
|
+
const removed = this._children.splice(index, 1)[0];
|
|
11286
|
+
if (removed) {
|
|
11287
|
+
removed.setOnChange(void 0);
|
|
11288
|
+
this.handleChildChange();
|
|
11289
|
+
}
|
|
11290
|
+
return removed;
|
|
11291
|
+
}
|
|
11292
|
+
insertChild(index, child) {
|
|
11293
|
+
child.setOnChange(() => this.handleChildChange());
|
|
11294
|
+
this._children.splice(index, 0, child);
|
|
11295
|
+
this.handleChildChange();
|
|
11296
|
+
}
|
|
11297
|
+
setOnChange(cb) {
|
|
11298
|
+
this._onChange = cb;
|
|
11299
|
+
}
|
|
11300
|
+
handleChildChange() {
|
|
11301
|
+
this._cachedBounds = null;
|
|
11302
|
+
this._onChange?.();
|
|
11303
|
+
}
|
|
11304
|
+
getFlexConfig() {
|
|
11305
|
+
return {
|
|
11306
|
+
direction: this._direction,
|
|
11307
|
+
justifyContent: this._justifyContent,
|
|
11308
|
+
alignItems: this._alignItems,
|
|
11309
|
+
gap: this._gap,
|
|
11310
|
+
padding: this._padding
|
|
11311
|
+
};
|
|
11312
|
+
}
|
|
11313
|
+
buildFlexChildren(ctx) {
|
|
11314
|
+
return this._children.map((child) => {
|
|
11315
|
+
const s = child.style;
|
|
11316
|
+
const measured = child.measure(ctx);
|
|
11317
|
+
return {
|
|
11318
|
+
measure: measured,
|
|
11319
|
+
minSize: { width: 0, height: 0 },
|
|
11320
|
+
flexGrow: s.flexGrow ?? 0,
|
|
11321
|
+
flexShrink: s.flexShrink ?? 1,
|
|
11322
|
+
flexBasis: s.flexBasis ?? "auto",
|
|
11323
|
+
alignSelf: s.alignSelf ?? "auto",
|
|
11324
|
+
margin: normalizeSides(s.margin)
|
|
11325
|
+
};
|
|
11326
|
+
});
|
|
11327
|
+
}
|
|
11328
|
+
measure(ctx) {
|
|
11329
|
+
const flexChildren = this.buildFlexChildren(ctx);
|
|
11330
|
+
const measureConfig = {
|
|
11331
|
+
...this.getFlexConfig(),
|
|
11332
|
+
justifyContent: "start",
|
|
11333
|
+
alignItems: "start"
|
|
11334
|
+
};
|
|
11335
|
+
const measureChildren = flexChildren.map((c) => ({
|
|
11336
|
+
...c,
|
|
11337
|
+
flexGrow: 0,
|
|
11338
|
+
alignSelf: "start"
|
|
11339
|
+
}));
|
|
11340
|
+
const result = flexLayout(
|
|
11341
|
+
{ width: 1e5, height: 1e5 },
|
|
11342
|
+
measureConfig,
|
|
11343
|
+
measureChildren
|
|
11344
|
+
);
|
|
11345
|
+
return result.contentSize;
|
|
11346
|
+
}
|
|
11347
|
+
render(ctx, bounds) {
|
|
11348
|
+
if (this.style.visible === false) return;
|
|
11349
|
+
const flexChildren = this.buildFlexChildren(ctx);
|
|
11350
|
+
const result = flexLayout(
|
|
11351
|
+
{ width: bounds.width, height: bounds.height },
|
|
11352
|
+
this.getFlexConfig(),
|
|
11353
|
+
flexChildren
|
|
11354
|
+
);
|
|
11355
|
+
this._cachedBounds = result.childBounds;
|
|
11356
|
+
this._cachedContainerBounds = bounds;
|
|
11357
|
+
const opacity = this.style.opacity ?? 1;
|
|
11358
|
+
if (opacity < 1) {
|
|
11359
|
+
ctx.save();
|
|
11360
|
+
ctx.globalAlpha *= opacity;
|
|
11361
|
+
}
|
|
11362
|
+
for (let i = 0; i < this._children.length; i++) {
|
|
11363
|
+
const child = this._children[i];
|
|
11364
|
+
if (child.style.visible === false) continue;
|
|
11365
|
+
const cb = result.childBounds[i];
|
|
11366
|
+
child.render(ctx, {
|
|
11367
|
+
x: bounds.x + cb.x,
|
|
11368
|
+
y: bounds.y + cb.y,
|
|
11369
|
+
width: cb.width,
|
|
11370
|
+
height: cb.height
|
|
11371
|
+
});
|
|
11372
|
+
}
|
|
11373
|
+
if (opacity < 1) {
|
|
11374
|
+
ctx.restore();
|
|
11375
|
+
}
|
|
11376
|
+
}
|
|
11377
|
+
hitTest(point, bounds) {
|
|
11378
|
+
if (this.style.visible === false) return null;
|
|
11379
|
+
if (!this._cachedBounds) return null;
|
|
11380
|
+
for (let i = this._children.length - 1; i >= 0; i--) {
|
|
11381
|
+
const child = this._children[i];
|
|
11382
|
+
if (child.style.visible === false) continue;
|
|
11383
|
+
const cb = this._cachedBounds[i];
|
|
11384
|
+
const absBounds = {
|
|
11385
|
+
x: bounds.x + cb.x,
|
|
11386
|
+
y: bounds.y + cb.y,
|
|
11387
|
+
width: cb.width,
|
|
11388
|
+
height: cb.height
|
|
11389
|
+
};
|
|
11390
|
+
const hit = child.hitTest(point, absBounds);
|
|
11391
|
+
if (hit) return hit;
|
|
11392
|
+
}
|
|
11393
|
+
return null;
|
|
11394
|
+
}
|
|
11395
|
+
/**
|
|
11396
|
+
* Find a component by id recursively.
|
|
11397
|
+
*/
|
|
11398
|
+
findById(id) {
|
|
11399
|
+
for (const child of this._children) {
|
|
11400
|
+
if (child.id === id) return child;
|
|
11401
|
+
if (child.type === "container") {
|
|
11402
|
+
const found = child.findById(id);
|
|
11403
|
+
if (found) return found;
|
|
11404
|
+
}
|
|
11405
|
+
if (child.type === "shape") {
|
|
11406
|
+
const content = child.content;
|
|
11407
|
+
if (content) {
|
|
11408
|
+
const found = content.findById(id);
|
|
11409
|
+
if (found) return found;
|
|
11410
|
+
}
|
|
11411
|
+
}
|
|
11412
|
+
}
|
|
11413
|
+
return void 0;
|
|
11414
|
+
}
|
|
11415
|
+
serialize() {
|
|
11416
|
+
const data = {
|
|
11417
|
+
type: "container",
|
|
11418
|
+
direction: this._direction,
|
|
11419
|
+
children: this._children.map((c) => c.serialize())
|
|
11420
|
+
};
|
|
11421
|
+
if (this.id !== void 0) data.id = this.id;
|
|
11422
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
11423
|
+
if (this._justifyContent !== "start") data.justifyContent = this._justifyContent;
|
|
11424
|
+
if (this._alignItems !== "stretch") data.alignItems = this._alignItems;
|
|
11425
|
+
if (this._gap !== 0) data.gap = this._gap;
|
|
11426
|
+
if (typeof this._padding === "number" && this._padding !== 0 || typeof this._padding === "object" && Object.values(this._padding).some((v) => v !== void 0 && v !== 0)) {
|
|
11427
|
+
data.padding = this._padding;
|
|
11428
|
+
}
|
|
11429
|
+
return data;
|
|
11430
|
+
}
|
|
11431
|
+
toSVG(bounds) {
|
|
11432
|
+
if (this.style.visible === false) return "";
|
|
11433
|
+
const cachedBounds = this._cachedBounds;
|
|
11434
|
+
const cachedContainer = this._cachedContainerBounds;
|
|
11435
|
+
const childSvg = this._children.map((child, i) => {
|
|
11436
|
+
if (child.style.visible === false) return "";
|
|
11437
|
+
if (cachedBounds && cachedContainer) {
|
|
11438
|
+
const cb = cachedBounds[i];
|
|
11439
|
+
const dx = bounds.x - cachedContainer.x;
|
|
11440
|
+
const dy = bounds.y - cachedContainer.y;
|
|
11441
|
+
return child.toSVG({
|
|
11442
|
+
x: cachedContainer.x + cb.x + dx,
|
|
11443
|
+
y: cachedContainer.y + cb.y + dy,
|
|
11444
|
+
width: cb.width,
|
|
11445
|
+
height: cb.height
|
|
11446
|
+
});
|
|
11447
|
+
}
|
|
11448
|
+
const h = bounds.height / Math.max(1, this._children.length);
|
|
11449
|
+
return child.toSVG({
|
|
11450
|
+
x: bounds.x,
|
|
11451
|
+
y: bounds.y + i * h,
|
|
11452
|
+
width: bounds.width,
|
|
11453
|
+
height: h
|
|
11454
|
+
});
|
|
11455
|
+
}).join("");
|
|
11456
|
+
return `<g>${childSvg}</g>`;
|
|
11457
|
+
}
|
|
11458
|
+
}
|
|
11459
|
+
class CShape {
|
|
11460
|
+
constructor(options = {}) {
|
|
11461
|
+
this.type = "shape";
|
|
11462
|
+
this.id = options.id;
|
|
11463
|
+
this._borderColor = options.borderColor;
|
|
11464
|
+
this._borderWidth = options.borderWidth ?? 0;
|
|
11465
|
+
this._backgroundColor = options.backgroundColor;
|
|
11466
|
+
this._cornerRadius = options.cornerRadius ?? 0;
|
|
11467
|
+
this._padding = options.padding ?? 0;
|
|
11468
|
+
this.onClick = options.onClick;
|
|
11469
|
+
this._content = options.content;
|
|
11470
|
+
this.style = options.style ?? {};
|
|
11471
|
+
if (this._content) {
|
|
11472
|
+
this._content.setOnChange(() => this._onChange?.());
|
|
11473
|
+
}
|
|
11474
|
+
}
|
|
11475
|
+
get borderColor() {
|
|
11476
|
+
return this._borderColor;
|
|
11477
|
+
}
|
|
11478
|
+
set borderColor(value) {
|
|
11479
|
+
if (this._borderColor !== value) {
|
|
11480
|
+
this._borderColor = value;
|
|
11481
|
+
this._onChange?.();
|
|
11482
|
+
}
|
|
11483
|
+
}
|
|
11484
|
+
get borderWidth() {
|
|
11485
|
+
return this._borderWidth;
|
|
11486
|
+
}
|
|
11487
|
+
set borderWidth(value) {
|
|
11488
|
+
if (this._borderWidth !== value) {
|
|
11489
|
+
this._borderWidth = value;
|
|
11490
|
+
this._onChange?.();
|
|
11491
|
+
}
|
|
11492
|
+
}
|
|
11493
|
+
get backgroundColor() {
|
|
11494
|
+
return this._backgroundColor;
|
|
11495
|
+
}
|
|
11496
|
+
set backgroundColor(value) {
|
|
11497
|
+
if (this._backgroundColor !== value) {
|
|
11498
|
+
this._backgroundColor = value;
|
|
11499
|
+
this._onChange?.();
|
|
11500
|
+
}
|
|
11501
|
+
}
|
|
11502
|
+
get cornerRadius() {
|
|
11503
|
+
return this._cornerRadius;
|
|
11504
|
+
}
|
|
11505
|
+
get padding() {
|
|
11506
|
+
return this._padding;
|
|
11507
|
+
}
|
|
11508
|
+
get content() {
|
|
11509
|
+
return this._content;
|
|
11510
|
+
}
|
|
11511
|
+
setOnChange(cb) {
|
|
11512
|
+
this._onChange = cb;
|
|
11513
|
+
}
|
|
11514
|
+
measure(ctx) {
|
|
11515
|
+
const pad = normalizeSides(this._padding);
|
|
11516
|
+
const bw = this._borderWidth;
|
|
11517
|
+
if (this._content) {
|
|
11518
|
+
const contentSize = this._content.measure(ctx);
|
|
11519
|
+
return {
|
|
11520
|
+
width: contentSize.width + pad.left + pad.right + bw * 2,
|
|
11521
|
+
height: contentSize.height + pad.top + pad.bottom + bw * 2
|
|
11522
|
+
};
|
|
11523
|
+
}
|
|
11524
|
+
return {
|
|
11525
|
+
width: pad.left + pad.right + bw * 2,
|
|
11526
|
+
height: pad.top + pad.bottom + bw * 2
|
|
11527
|
+
};
|
|
11528
|
+
}
|
|
11529
|
+
render(ctx, bounds) {
|
|
11530
|
+
if (this.style.visible === false) return;
|
|
11531
|
+
ctx.save();
|
|
11532
|
+
ctx.globalAlpha *= this.style.opacity ?? 1;
|
|
11533
|
+
const { x, y, width, height } = bounds;
|
|
11534
|
+
if (this._backgroundColor) {
|
|
11535
|
+
ctx.fillStyle = this._backgroundColor;
|
|
11536
|
+
if (this._cornerRadius > 0 && ctx.roundRect) {
|
|
11537
|
+
ctx.beginPath();
|
|
11538
|
+
ctx.roundRect(x, y, width, height, this._cornerRadius);
|
|
11539
|
+
ctx.fill();
|
|
11540
|
+
} else {
|
|
11541
|
+
ctx.fillRect(x, y, width, height);
|
|
11542
|
+
}
|
|
11543
|
+
}
|
|
11544
|
+
if (this._borderColor && this._borderWidth > 0) {
|
|
11545
|
+
ctx.strokeStyle = this._borderColor;
|
|
11546
|
+
ctx.lineWidth = this._borderWidth;
|
|
11547
|
+
if (this._cornerRadius > 0 && ctx.roundRect) {
|
|
11548
|
+
ctx.beginPath();
|
|
11549
|
+
ctx.roundRect(x, y, width, height, this._cornerRadius);
|
|
11550
|
+
ctx.stroke();
|
|
11551
|
+
} else {
|
|
11552
|
+
ctx.strokeRect(x, y, width, height);
|
|
11553
|
+
}
|
|
11554
|
+
}
|
|
11555
|
+
if (this._content) {
|
|
11556
|
+
const pad = normalizeSides(this._padding);
|
|
11557
|
+
const bw = this._borderWidth;
|
|
11558
|
+
this._content.render(ctx, {
|
|
11559
|
+
x: x + pad.left + bw,
|
|
11560
|
+
y: y + pad.top + bw,
|
|
11561
|
+
width: Math.max(0, width - pad.left - pad.right - bw * 2),
|
|
11562
|
+
height: Math.max(0, height - pad.top - pad.bottom - bw * 2)
|
|
11563
|
+
});
|
|
11564
|
+
}
|
|
11565
|
+
ctx.restore();
|
|
11566
|
+
}
|
|
11567
|
+
hitTest(point, bounds) {
|
|
11568
|
+
if (this.style.visible === false) return null;
|
|
11569
|
+
if (point.x < bounds.x || point.x > bounds.x + bounds.width || point.y < bounds.y || point.y > bounds.y + bounds.height) {
|
|
11570
|
+
return null;
|
|
11571
|
+
}
|
|
11572
|
+
if (this._content) {
|
|
11573
|
+
const pad = normalizeSides(this._padding);
|
|
11574
|
+
const bw = this._borderWidth;
|
|
11575
|
+
const contentBounds = {
|
|
11576
|
+
x: bounds.x + pad.left + bw,
|
|
11577
|
+
y: bounds.y + pad.top + bw,
|
|
11578
|
+
width: Math.max(0, bounds.width - pad.left - pad.right - bw * 2),
|
|
11579
|
+
height: Math.max(0, bounds.height - pad.top - pad.bottom - bw * 2)
|
|
11580
|
+
};
|
|
11581
|
+
const hit = this._content.hitTest(point, contentBounds);
|
|
11582
|
+
if (hit) return hit;
|
|
11583
|
+
}
|
|
11584
|
+
return this;
|
|
11585
|
+
}
|
|
11586
|
+
serialize() {
|
|
11587
|
+
const data = { type: "shape" };
|
|
11588
|
+
if (this.id !== void 0) data.id = this.id;
|
|
11589
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
11590
|
+
if (this._borderColor) data.borderColor = this._borderColor;
|
|
11591
|
+
if (this._borderWidth !== 0) data.borderWidth = this._borderWidth;
|
|
11592
|
+
if (this._backgroundColor) data.backgroundColor = this._backgroundColor;
|
|
11593
|
+
if (this._cornerRadius !== 0) data.cornerRadius = this._cornerRadius;
|
|
11594
|
+
if (typeof this._padding === "number" && this._padding !== 0 || typeof this._padding === "object" && Object.values(this._padding).some((v) => v !== void 0 && v !== 0)) {
|
|
11595
|
+
data.padding = this._padding;
|
|
11596
|
+
}
|
|
11597
|
+
if (this._content) {
|
|
11598
|
+
data.content = this._content.serialize();
|
|
11599
|
+
}
|
|
11600
|
+
return data;
|
|
11601
|
+
}
|
|
11602
|
+
toSVG(bounds) {
|
|
11603
|
+
if (this.style.visible === false) return "";
|
|
11604
|
+
let svg = "";
|
|
11605
|
+
const { x, y, width, height } = bounds;
|
|
11606
|
+
const hasFill = !!this._backgroundColor;
|
|
11607
|
+
const hasStroke = !!this._borderColor && this._borderWidth > 0;
|
|
11608
|
+
if (hasFill || hasStroke) {
|
|
11609
|
+
const fill = hasFill ? `fill="${this._backgroundColor}"` : 'fill="none"';
|
|
11610
|
+
const stroke = hasStroke ? `stroke="${this._borderColor}" stroke-width="${this._borderWidth}"` : "";
|
|
11611
|
+
const rx = this._cornerRadius > 0 ? ` rx="${this._cornerRadius}"` : "";
|
|
11612
|
+
svg += `<rect x="${x}" y="${y}" width="${width}" height="${height}" ${fill} ${stroke}${rx} />`;
|
|
11613
|
+
}
|
|
11614
|
+
if (this._content) {
|
|
11615
|
+
const pad = normalizeSides(this._padding);
|
|
11616
|
+
const bw = this._borderWidth;
|
|
11617
|
+
svg += this._content.toSVG({
|
|
11618
|
+
x: x + pad.left + bw,
|
|
11619
|
+
y: y + pad.top + bw,
|
|
11620
|
+
width: Math.max(0, width - pad.left - pad.right - bw * 2),
|
|
11621
|
+
height: Math.max(0, height - pad.top - pad.bottom - bw * 2)
|
|
11622
|
+
});
|
|
11623
|
+
}
|
|
11624
|
+
return svg;
|
|
11625
|
+
}
|
|
11626
|
+
}
|
|
11627
|
+
function deserializeCComponent(data) {
|
|
11628
|
+
switch (data.type) {
|
|
11629
|
+
case "text":
|
|
11630
|
+
return new CText({
|
|
11631
|
+
id: data.id,
|
|
11632
|
+
text: data.text ?? "",
|
|
11633
|
+
fontFamily: data.fontFamily,
|
|
11634
|
+
fontWeight: data.fontWeight,
|
|
11635
|
+
fontStyle: data.fontStyle,
|
|
11636
|
+
fontSize: data.fontSize,
|
|
11637
|
+
color: data.color,
|
|
11638
|
+
align: data.align,
|
|
11639
|
+
verticalAlign: data.verticalAlign,
|
|
11640
|
+
maxLines: data.maxLines,
|
|
11641
|
+
lineHeight: data.lineHeight,
|
|
11642
|
+
role: data.role,
|
|
11643
|
+
rotation: data.rotation,
|
|
11644
|
+
style: data.style
|
|
11645
|
+
});
|
|
11646
|
+
case "icon":
|
|
11647
|
+
return new CIcon({
|
|
11648
|
+
id: data.id,
|
|
11649
|
+
source: data.source ?? "",
|
|
11650
|
+
width: data.width,
|
|
11651
|
+
height: data.height,
|
|
11652
|
+
backgroundColor: data.backgroundColor,
|
|
11653
|
+
fillColor: data.fillColor,
|
|
11654
|
+
bindsNotationIcon: data.bindsNotationIcon === true,
|
|
11655
|
+
style: data.style
|
|
11656
|
+
});
|
|
11657
|
+
case "divider":
|
|
11658
|
+
return new CDivider({
|
|
11659
|
+
id: data.id,
|
|
11660
|
+
color: data.color,
|
|
11661
|
+
thickness: data.thickness,
|
|
11662
|
+
style: data.style
|
|
11663
|
+
});
|
|
11664
|
+
case "container":
|
|
11665
|
+
return new CContainer({
|
|
11666
|
+
id: data.id,
|
|
11667
|
+
direction: data.direction,
|
|
11668
|
+
justifyContent: data.justifyContent,
|
|
11669
|
+
alignItems: data.alignItems,
|
|
11670
|
+
gap: data.gap,
|
|
11671
|
+
padding: data.padding,
|
|
11672
|
+
children: data.children?.map(deserializeCComponent),
|
|
11673
|
+
style: data.style
|
|
11674
|
+
});
|
|
11675
|
+
case "shape":
|
|
11676
|
+
return new CShape({
|
|
11677
|
+
id: data.id,
|
|
11678
|
+
borderColor: data.borderColor,
|
|
11679
|
+
borderWidth: data.borderWidth,
|
|
11680
|
+
backgroundColor: data.backgroundColor,
|
|
11681
|
+
cornerRadius: data.cornerRadius,
|
|
11682
|
+
padding: data.padding,
|
|
11683
|
+
content: data.content ? deserializeCComponent(data.content) : void 0,
|
|
11684
|
+
style: data.style
|
|
11685
|
+
});
|
|
11686
|
+
default:
|
|
11687
|
+
throw new Error(`Unknown component type: ${String(data.type)}`);
|
|
11688
|
+
}
|
|
11689
|
+
}
|
|
11690
|
+
class CompositeNode extends Node {
|
|
11691
|
+
constructor(options) {
|
|
11692
|
+
super(options);
|
|
11693
|
+
this._content = options.content;
|
|
11694
|
+
this._shapeType = options.shapeType ?? "rectangle";
|
|
11695
|
+
this._cornerRadius = options.cornerRadius ?? 0;
|
|
11696
|
+
this._pathFactory = options.pathFactory;
|
|
11697
|
+
this._autoSize = options.autoSize ?? false;
|
|
11698
|
+
this._minWidth = options.minWidth ?? 0;
|
|
11699
|
+
this._minHeight = options.minHeight ?? 0;
|
|
11700
|
+
this._content.setOnChange(() => this.markDirty());
|
|
11701
|
+
}
|
|
11702
|
+
get typeName() {
|
|
11703
|
+
return "composite";
|
|
11704
|
+
}
|
|
11705
|
+
get content() {
|
|
11706
|
+
return this._content;
|
|
11707
|
+
}
|
|
11708
|
+
get shapeType() {
|
|
11709
|
+
return this._shapeType;
|
|
11710
|
+
}
|
|
11711
|
+
get cornerRadius() {
|
|
11712
|
+
return this._cornerRadius;
|
|
11713
|
+
}
|
|
11714
|
+
get autoSize() {
|
|
11715
|
+
return this._autoSize;
|
|
11716
|
+
}
|
|
11717
|
+
set autoSize(value) {
|
|
11718
|
+
if (this._autoSize !== value) {
|
|
11719
|
+
this._autoSize = value;
|
|
11720
|
+
this.markDirty();
|
|
11721
|
+
}
|
|
11722
|
+
}
|
|
11723
|
+
get minWidth() {
|
|
11724
|
+
return this._minWidth;
|
|
11725
|
+
}
|
|
11726
|
+
get minHeight() {
|
|
11727
|
+
return this._minHeight;
|
|
11728
|
+
}
|
|
11729
|
+
/**
|
|
11730
|
+
* Find a component by id in the content tree.
|
|
11731
|
+
*/
|
|
11732
|
+
getComponent(id) {
|
|
11733
|
+
if (this._content.id === id) return this._content;
|
|
11734
|
+
return this._content.findById(id);
|
|
11735
|
+
}
|
|
11736
|
+
/**
|
|
11737
|
+
* Hit test the component tree. Returns the deepest component at the given world point.
|
|
11738
|
+
*/
|
|
11739
|
+
getComponentAtPoint(worldPoint) {
|
|
11740
|
+
const bounds = this.getContentBounds();
|
|
11741
|
+
return this._content.hitTest(worldPoint, bounds);
|
|
11742
|
+
}
|
|
11743
|
+
getContentBounds() {
|
|
11744
|
+
return this.getLabelContainerBounds(this.getBounds());
|
|
11745
|
+
}
|
|
11746
|
+
// --- Shape rendering ---
|
|
11747
|
+
hitTest(point) {
|
|
11748
|
+
if (this._shapeType === "circle") {
|
|
11749
|
+
return this.hitTestEllipse(point);
|
|
11750
|
+
}
|
|
11751
|
+
if (this._shapeType === "diamond") {
|
|
11752
|
+
return this.hitTestDiamond(point);
|
|
11753
|
+
}
|
|
11754
|
+
return super.hitTest(point);
|
|
11755
|
+
}
|
|
11756
|
+
hitTestEllipse(point) {
|
|
11757
|
+
const center = this.getCenter();
|
|
11758
|
+
const padding = NODE_HITBOX_PADDING;
|
|
11759
|
+
const rx = this._width / 2 + padding;
|
|
11760
|
+
const ry = this._height / 2 + padding;
|
|
11761
|
+
const dx = point.x - center.x;
|
|
11762
|
+
const dy = point.y - center.y;
|
|
11763
|
+
return dx * dx / (rx * rx) + dy * dy / (ry * ry) <= 1;
|
|
11764
|
+
}
|
|
11765
|
+
hitTestDiamond(point) {
|
|
11766
|
+
const center = this.getCenter();
|
|
11767
|
+
const padding = NODE_HITBOX_PADDING;
|
|
11768
|
+
const hw = this._width / 2 + padding;
|
|
11769
|
+
const hh = this._height / 2 + padding;
|
|
11770
|
+
const dx = Math.abs(point.x - center.x);
|
|
11771
|
+
const dy = Math.abs(point.y - center.y);
|
|
11772
|
+
return dx / hw + dy / hh <= 1;
|
|
11773
|
+
}
|
|
11774
|
+
render(ctx) {
|
|
11775
|
+
if (this._autoSize) {
|
|
11776
|
+
this.applyAutoSize(ctx);
|
|
11777
|
+
}
|
|
11778
|
+
const { x, y, width, height } = this.getBounds();
|
|
11779
|
+
const style = this.style;
|
|
11780
|
+
const baseOpacity = style.opacity ?? 1;
|
|
11781
|
+
const fillOpacity = style.fillOpacity ?? 1;
|
|
11782
|
+
const strokeOpacity = style.strokeOpacity ?? 1;
|
|
11783
|
+
this.applyStyle(ctx);
|
|
11784
|
+
ctx.beginPath();
|
|
11785
|
+
this.buildShapePath(ctx, x, y, width, height);
|
|
11786
|
+
ctx.closePath();
|
|
11787
|
+
ctx.globalAlpha = baseOpacity * fillOpacity;
|
|
11788
|
+
ctx.fill();
|
|
11789
|
+
ctx.globalAlpha = baseOpacity * strokeOpacity;
|
|
11790
|
+
ctx.stroke();
|
|
11791
|
+
ctx.globalAlpha = 1;
|
|
11792
|
+
this.renderContents(ctx);
|
|
11793
|
+
}
|
|
11794
|
+
buildShapePath(ctx, x, y, w, h) {
|
|
11795
|
+
switch (this._shapeType) {
|
|
11796
|
+
case "circle": {
|
|
11797
|
+
const cx = x + w / 2;
|
|
11798
|
+
const cy = y + h / 2;
|
|
11799
|
+
ctx.ellipse(cx, cy, w / 2, h / 2, 0, 0, Math.PI * 2);
|
|
11800
|
+
break;
|
|
11801
|
+
}
|
|
11802
|
+
case "diamond": {
|
|
11803
|
+
const cx = x + w / 2;
|
|
11804
|
+
const cy = y + h / 2;
|
|
11805
|
+
ctx.moveTo(cx, y);
|
|
11806
|
+
ctx.lineTo(x + w, cy);
|
|
11807
|
+
ctx.lineTo(cx, y + h);
|
|
11808
|
+
ctx.lineTo(x, cy);
|
|
11809
|
+
break;
|
|
11810
|
+
}
|
|
11811
|
+
case "custom": {
|
|
11812
|
+
if (this._pathFactory) {
|
|
11813
|
+
const path = this._pathFactory(w, h);
|
|
11814
|
+
ctx.save();
|
|
11815
|
+
ctx.translate(x, y);
|
|
11816
|
+
ctx.fill(path);
|
|
11817
|
+
ctx.stroke(path);
|
|
11818
|
+
ctx.restore();
|
|
11819
|
+
return;
|
|
11820
|
+
}
|
|
11821
|
+
this.buildRectPath(ctx, x, y, w, h);
|
|
11822
|
+
break;
|
|
11823
|
+
}
|
|
11824
|
+
default:
|
|
11825
|
+
this.buildRectPath(ctx, x, y, w, h);
|
|
11826
|
+
break;
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
buildRectPath(ctx, x, y, w, h) {
|
|
11830
|
+
const radius = Math.min(this._cornerRadius, w / 2, h / 2);
|
|
11831
|
+
if (radius > 0) {
|
|
11832
|
+
ctx.moveTo(x + radius, y);
|
|
11833
|
+
ctx.lineTo(x + w - radius, y);
|
|
11834
|
+
ctx.arcTo(x + w, y, x + w, y + radius, radius);
|
|
11835
|
+
ctx.lineTo(x + w, y + h - radius);
|
|
11836
|
+
ctx.arcTo(x + w, y + h, x + w - radius, y + h, radius);
|
|
11837
|
+
ctx.lineTo(x + radius, y + h);
|
|
11838
|
+
ctx.arcTo(x, y + h, x, y + h - radius, radius);
|
|
11839
|
+
ctx.lineTo(x, y + radius);
|
|
11840
|
+
ctx.arcTo(x, y, x + radius, y, radius);
|
|
11841
|
+
} else {
|
|
11842
|
+
ctx.rect(x, y, w, h);
|
|
11843
|
+
}
|
|
11844
|
+
}
|
|
11845
|
+
// --- Content rendering (overrides Node.renderContents) ---
|
|
11846
|
+
renderContents(ctx) {
|
|
11847
|
+
ctx.setLineDash([]);
|
|
11848
|
+
ctx.lineDashOffset = 0;
|
|
11849
|
+
const contentBounds = this.getContentBounds();
|
|
11850
|
+
this._content.render(ctx, contentBounds);
|
|
11851
|
+
this.renderPorts(ctx);
|
|
11852
|
+
}
|
|
11853
|
+
applyAutoSize(ctx) {
|
|
11854
|
+
const contentSize = this._content.measure(ctx);
|
|
11855
|
+
const inset = this.contentInset;
|
|
11856
|
+
const neededWidth = Math.max(
|
|
11857
|
+
this._minWidth,
|
|
11858
|
+
contentSize.width + inset.left + inset.right
|
|
11859
|
+
);
|
|
11860
|
+
const neededHeight = Math.max(
|
|
11861
|
+
this._minHeight,
|
|
11862
|
+
contentSize.height + inset.top + inset.bottom
|
|
11863
|
+
);
|
|
11864
|
+
if (neededWidth > this._width) {
|
|
11865
|
+
this._width = neededWidth;
|
|
11866
|
+
}
|
|
11867
|
+
if (neededHeight > this._height) {
|
|
11868
|
+
this._height = neededHeight;
|
|
11869
|
+
}
|
|
11870
|
+
}
|
|
11871
|
+
// --- Outline methods for connections (delegate based on shapeType) ---
|
|
11872
|
+
getLabelContainerBounds(bounds) {
|
|
11873
|
+
if (this._shapeType === "circle") {
|
|
11874
|
+
const factor = 1 / Math.SQRT2;
|
|
11875
|
+
const w = bounds.width * factor;
|
|
11876
|
+
const h = bounds.height * factor;
|
|
11877
|
+
return {
|
|
11878
|
+
x: bounds.x + (bounds.width - w) / 2,
|
|
11879
|
+
y: bounds.y + (bounds.height - h) / 2,
|
|
11880
|
+
width: w,
|
|
11881
|
+
height: h
|
|
11882
|
+
};
|
|
11883
|
+
}
|
|
11884
|
+
if (this._shapeType === "diamond") {
|
|
11885
|
+
const w = bounds.width / 2;
|
|
11886
|
+
const h = bounds.height / 2;
|
|
11887
|
+
return {
|
|
11888
|
+
x: bounds.x + (bounds.width - w) / 2,
|
|
11889
|
+
y: bounds.y + (bounds.height - h) / 2,
|
|
11890
|
+
width: w,
|
|
11891
|
+
height: h
|
|
11892
|
+
};
|
|
11893
|
+
}
|
|
11894
|
+
const ci = this.contentInset;
|
|
11895
|
+
return {
|
|
11896
|
+
x: bounds.x + ci.left,
|
|
11897
|
+
y: bounds.y + ci.top,
|
|
11898
|
+
width: Math.max(0, bounds.width - ci.left - ci.right),
|
|
11899
|
+
height: Math.max(0, bounds.height - ci.top - ci.bottom)
|
|
11900
|
+
};
|
|
11901
|
+
}
|
|
11902
|
+
/**
|
|
11903
|
+
* Get the minimum content size needed for the content tree.
|
|
11904
|
+
* Useful for external auto-sizing logic.
|
|
11905
|
+
*/
|
|
11906
|
+
getContentMinSize(ctx) {
|
|
11907
|
+
return this._content.measure(ctx);
|
|
11908
|
+
}
|
|
11909
|
+
}
|
|
11910
|
+
function text(options) {
|
|
11911
|
+
return new CText(options);
|
|
11912
|
+
}
|
|
11913
|
+
function icon(options) {
|
|
11914
|
+
return new CIcon(options);
|
|
11915
|
+
}
|
|
11916
|
+
function divider(options) {
|
|
11917
|
+
return new CDivider(options);
|
|
11918
|
+
}
|
|
11919
|
+
function container(options) {
|
|
11920
|
+
return new CContainer(options);
|
|
11921
|
+
}
|
|
11922
|
+
function shape(options) {
|
|
11923
|
+
return new CShape(options);
|
|
11924
|
+
}
|
|
11925
|
+
const DEFAULT_THEME = {
|
|
11926
|
+
name: "default",
|
|
11927
|
+
colors: {
|
|
11928
|
+
background: "#ffffff",
|
|
11929
|
+
grid: "#e5e5e5",
|
|
11930
|
+
selection: "rgba(59, 130, 246, 0.1)",
|
|
11931
|
+
connectionPreview: "#3b82f6"
|
|
11932
|
+
},
|
|
11933
|
+
node: {
|
|
11934
|
+
default: {
|
|
11935
|
+
fillColor: "#ffffff",
|
|
11936
|
+
strokeColor: "#333333",
|
|
11937
|
+
strokeWidth: 2,
|
|
11938
|
+
opacity: 1,
|
|
11939
|
+
cornerRadius: 4
|
|
11940
|
+
},
|
|
11941
|
+
hover: {
|
|
11942
|
+
fillColor: "#f5f5f5",
|
|
11943
|
+
strokeColor: "#6366f1",
|
|
11944
|
+
strokeWidth: 2,
|
|
11945
|
+
opacity: 1,
|
|
11946
|
+
cornerRadius: 4
|
|
11947
|
+
},
|
|
11948
|
+
selected: {
|
|
11949
|
+
strokeColor: "#3b82f6",
|
|
11950
|
+
strokeWidth: 2,
|
|
11951
|
+
opacity: 1
|
|
11952
|
+
},
|
|
11953
|
+
dragging: {
|
|
11954
|
+
strokeColor: "#333333",
|
|
11955
|
+
strokeWidth: 2,
|
|
11956
|
+
opacity: 0.8
|
|
11957
|
+
}
|
|
11958
|
+
},
|
|
11959
|
+
edge: {
|
|
11960
|
+
default: {
|
|
11961
|
+
strokeColor: "#666666",
|
|
11962
|
+
strokeWidth: 2,
|
|
11963
|
+
opacity: 1
|
|
11964
|
+
},
|
|
11965
|
+
hover: {
|
|
11966
|
+
strokeColor: "#6366f1",
|
|
11967
|
+
strokeWidth: 2,
|
|
11968
|
+
opacity: 1
|
|
11969
|
+
},
|
|
11970
|
+
selected: {
|
|
11971
|
+
strokeColor: "#3b82f6",
|
|
11972
|
+
strokeWidth: 3,
|
|
11973
|
+
opacity: 1
|
|
11974
|
+
}
|
|
11975
|
+
},
|
|
11976
|
+
text: {
|
|
11977
|
+
font: "14px sans-serif",
|
|
11978
|
+
fontSize: 14,
|
|
11979
|
+
fontFamily: "sans-serif",
|
|
11980
|
+
fontWeight: "normal",
|
|
11981
|
+
color: "#333333",
|
|
11982
|
+
align: "center",
|
|
11983
|
+
baseline: "middle"
|
|
11984
|
+
},
|
|
11985
|
+
port: {
|
|
11986
|
+
default: { color: "#666666", radius: 6 },
|
|
11987
|
+
hover: { color: "#3b82f6", radius: 7 }
|
|
11988
|
+
},
|
|
11989
|
+
group: {
|
|
11990
|
+
default: {
|
|
11991
|
+
fillColor: "rgba(200, 200, 200, 0.2)",
|
|
11992
|
+
strokeColor: "#999999",
|
|
11993
|
+
strokeWidth: 1,
|
|
11994
|
+
opacity: 1
|
|
11995
|
+
},
|
|
11996
|
+
selected: {
|
|
11997
|
+
fillColor: "rgba(59, 130, 246, 0.1)",
|
|
11998
|
+
strokeColor: "#3b82f6",
|
|
11999
|
+
strokeWidth: 2,
|
|
12000
|
+
opacity: 1
|
|
12001
|
+
}
|
|
12002
|
+
}
|
|
12003
|
+
};
|
|
12004
|
+
const DARK_THEME = {
|
|
12005
|
+
name: "dark",
|
|
12006
|
+
colors: {
|
|
12007
|
+
background: "#1a1a1a",
|
|
12008
|
+
grid: "#333333",
|
|
12009
|
+
selection: "rgba(99, 102, 241, 0.2)",
|
|
12010
|
+
connectionPreview: "#6366f1"
|
|
12011
|
+
},
|
|
12012
|
+
node: {
|
|
12013
|
+
default: {
|
|
12014
|
+
fillColor: "#2d2d2d",
|
|
12015
|
+
strokeColor: "#555555",
|
|
12016
|
+
strokeWidth: 2,
|
|
12017
|
+
opacity: 1,
|
|
12018
|
+
cornerRadius: 4
|
|
12019
|
+
},
|
|
12020
|
+
hover: {
|
|
12021
|
+
fillColor: "#3d3d3d",
|
|
12022
|
+
strokeColor: "#6366f1",
|
|
12023
|
+
strokeWidth: 2,
|
|
12024
|
+
opacity: 1,
|
|
12025
|
+
cornerRadius: 4
|
|
12026
|
+
},
|
|
12027
|
+
selected: {
|
|
12028
|
+
fillColor: "#2d2d2d",
|
|
12029
|
+
strokeColor: "#555555",
|
|
12030
|
+
strokeWidth: 2,
|
|
12031
|
+
opacity: 1,
|
|
12032
|
+
cornerRadius: 4
|
|
12033
|
+
},
|
|
12034
|
+
dragging: {
|
|
12035
|
+
fillColor: "#404040",
|
|
12036
|
+
strokeColor: "#555555",
|
|
12037
|
+
strokeWidth: 2,
|
|
12038
|
+
opacity: 0.8,
|
|
12039
|
+
cornerRadius: 4
|
|
12040
|
+
}
|
|
12041
|
+
},
|
|
12042
|
+
edge: {
|
|
12043
|
+
default: {
|
|
12044
|
+
strokeColor: "#888888",
|
|
12045
|
+
strokeWidth: 2,
|
|
12046
|
+
opacity: 1
|
|
12047
|
+
},
|
|
12048
|
+
hover: {
|
|
12049
|
+
strokeColor: "#6366f1",
|
|
12050
|
+
strokeWidth: 2,
|
|
12051
|
+
opacity: 1
|
|
12052
|
+
},
|
|
12053
|
+
selected: {
|
|
12054
|
+
strokeColor: "#818cf8",
|
|
12055
|
+
strokeWidth: 3,
|
|
12056
|
+
opacity: 1
|
|
12057
|
+
}
|
|
12058
|
+
},
|
|
12059
|
+
text: {
|
|
12060
|
+
font: "14px sans-serif",
|
|
12061
|
+
fontSize: 14,
|
|
12062
|
+
fontFamily: "sans-serif",
|
|
12063
|
+
fontWeight: "normal",
|
|
12064
|
+
color: "#e0e0e0",
|
|
12065
|
+
align: "center",
|
|
12066
|
+
baseline: "middle"
|
|
12067
|
+
},
|
|
12068
|
+
port: {
|
|
12069
|
+
default: { color: "#888888", radius: 6 },
|
|
12070
|
+
hover: { color: "#6366f1", radius: 7 }
|
|
12071
|
+
},
|
|
12072
|
+
group: {
|
|
12073
|
+
default: {
|
|
12074
|
+
fillColor: "rgba(100, 100, 100, 0.2)",
|
|
12075
|
+
strokeColor: "#666666",
|
|
12076
|
+
strokeWidth: 1,
|
|
12077
|
+
opacity: 1
|
|
12078
|
+
},
|
|
12079
|
+
selected: {
|
|
12080
|
+
fillColor: "rgba(99, 102, 241, 0.2)",
|
|
12081
|
+
strokeColor: "#6366f1",
|
|
12082
|
+
strokeWidth: 2,
|
|
12083
|
+
opacity: 1
|
|
12084
|
+
}
|
|
12085
|
+
}
|
|
12086
|
+
};
|
|
12087
|
+
class StyleManager {
|
|
12088
|
+
constructor(theme) {
|
|
12089
|
+
this._classes = /* @__PURE__ */ new Map();
|
|
12090
|
+
this._builtInThemes = /* @__PURE__ */ new Map([
|
|
12091
|
+
["default", DEFAULT_THEME],
|
|
12092
|
+
["dark", DARK_THEME]
|
|
12093
|
+
]);
|
|
12094
|
+
if (theme === void 0) {
|
|
12095
|
+
this._theme = DEFAULT_THEME;
|
|
12096
|
+
} else if (typeof theme === "string") {
|
|
12097
|
+
this._theme = this._builtInThemes.get(theme) ?? DEFAULT_THEME;
|
|
12098
|
+
} else {
|
|
12099
|
+
this._theme = theme;
|
|
10527
12100
|
}
|
|
10528
12101
|
}
|
|
10529
12102
|
/**
|
|
@@ -10873,12 +12446,12 @@ class Serializer {
|
|
|
10873
12446
|
label = node.label.text;
|
|
10874
12447
|
}
|
|
10875
12448
|
}
|
|
10876
|
-
let
|
|
12449
|
+
let icon2;
|
|
10877
12450
|
if (node.icon) {
|
|
10878
12451
|
const opts = node.icon.options;
|
|
10879
12452
|
const source = typeof opts.source === "string" ? opts.source : void 0;
|
|
10880
12453
|
if (source) {
|
|
10881
|
-
|
|
12454
|
+
icon2 = omitEmptyValues({
|
|
10882
12455
|
source,
|
|
10883
12456
|
width: opts.width,
|
|
10884
12457
|
height: opts.height,
|
|
@@ -10899,7 +12472,7 @@ class Serializer {
|
|
|
10899
12472
|
}
|
|
10900
12473
|
const contentInset = node.contentInset;
|
|
10901
12474
|
const hasContentInset = contentInset.top !== 0 || contentInset.right !== 0 || contentInset.bottom !== 0 || contentInset.left !== 0;
|
|
10902
|
-
|
|
12475
|
+
const base = omitEmptyValues({
|
|
10903
12476
|
id: node.id,
|
|
10904
12477
|
type: node.typeName,
|
|
10905
12478
|
x: node.x,
|
|
@@ -10910,12 +12483,23 @@ class Serializer {
|
|
|
10910
12483
|
styleClass: node.styleClass,
|
|
10911
12484
|
label,
|
|
10912
12485
|
labelStyleClass: typeof label === "string" ? node.label?.styleClass : void 0,
|
|
10913
|
-
icon,
|
|
12486
|
+
icon: icon2,
|
|
10914
12487
|
contentInset: hasContentInset ? contentInset : void 0,
|
|
10915
12488
|
anchorPoints,
|
|
10916
12489
|
ports: ports.length > 0 ? ports : void 0,
|
|
10917
12490
|
data: Object.keys(node.data).length > 0 ? node.data : void 0
|
|
10918
12491
|
});
|
|
12492
|
+
if (node.typeName === "composite") {
|
|
12493
|
+
const cn = node;
|
|
12494
|
+
const ext = base;
|
|
12495
|
+
ext.content = cn.content.serialize();
|
|
12496
|
+
ext.shapeType = cn.shapeType;
|
|
12497
|
+
if (cn.cornerRadius !== 0) ext.cornerRadius = cn.cornerRadius;
|
|
12498
|
+
ext.autoSize = cn.autoSize;
|
|
12499
|
+
if (cn.minWidth !== 0) ext.minWidth = cn.minWidth;
|
|
12500
|
+
if (cn.minHeight !== 0) ext.minHeight = cn.minHeight;
|
|
12501
|
+
}
|
|
12502
|
+
return base;
|
|
10919
12503
|
}
|
|
10920
12504
|
serializeEdge(edge) {
|
|
10921
12505
|
return {
|
|
@@ -10932,6 +12516,8 @@ class Serializer {
|
|
|
10932
12516
|
label: edge.label?.text,
|
|
10933
12517
|
labelStyleClass: edge.label?.styleClass,
|
|
10934
12518
|
labelOffset: edge.labelOffset !== 0 ? edge.labelOffset : void 0,
|
|
12519
|
+
labelPosition: edge.labelPosition !== 0.5 ? edge.labelPosition : void 0,
|
|
12520
|
+
labelFollowPath: edge.labelFollowPath ? true : void 0,
|
|
10935
12521
|
labelBackground: edge.labelBackground,
|
|
10936
12522
|
labelLineGap: edge.labelLineGap ? true : void 0,
|
|
10937
12523
|
data: Object.keys(edge.data).length > 0 ? edge.data : void 0
|
|
@@ -11290,16 +12876,16 @@ class SvgExporter {
|
|
|
11290
12876
|
const strokeOpacity = (style.strokeOpacity ?? 1) * baseOpacity;
|
|
11291
12877
|
const dash = style.lineDash?.length ? ` stroke-dasharray="${style.lineDash.join(" ")}"` : "";
|
|
11292
12878
|
const dashOffset = style.lineDashOffset !== void 0 ? ` stroke-dashoffset="${style.lineDashOffset}"` : "";
|
|
11293
|
-
let
|
|
12879
|
+
let shape2;
|
|
11294
12880
|
switch (node.typeName) {
|
|
11295
12881
|
case "rectangle": {
|
|
11296
12882
|
const radius = this.getNodeCornerRadius(node, bounds);
|
|
11297
|
-
|
|
12883
|
+
shape2 = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}" rx="${radius}" ry="${radius}"`;
|
|
11298
12884
|
break;
|
|
11299
12885
|
}
|
|
11300
12886
|
case "circle": {
|
|
11301
12887
|
const center = node.getCenter();
|
|
11302
|
-
|
|
12888
|
+
shape2 = `<ellipse cx="${center.x}" cy="${center.y}" rx="${bounds.width / 2}" ry="${bounds.height / 2}"`;
|
|
11303
12889
|
break;
|
|
11304
12890
|
}
|
|
11305
12891
|
case "diamond": {
|
|
@@ -11312,27 +12898,61 @@ class SvgExporter {
|
|
|
11312
12898
|
`${center.x},${center.y + hh}`,
|
|
11313
12899
|
`${center.x - hw},${center.y}`
|
|
11314
12900
|
].join(" ");
|
|
11315
|
-
|
|
12901
|
+
shape2 = `<polygon points="${points}"`;
|
|
11316
12902
|
break;
|
|
11317
12903
|
}
|
|
11318
12904
|
case "custom": {
|
|
11319
12905
|
const svgPath = "getSvgPath" in node && typeof node.getSvgPath === "function" ? node.getSvgPath() : null;
|
|
11320
12906
|
if (svgPath) {
|
|
11321
|
-
|
|
12907
|
+
shape2 = `<path d="${this.escapeAttribute(svgPath)}" transform="translate(${bounds.x}, ${bounds.y})"`;
|
|
11322
12908
|
} else {
|
|
11323
|
-
|
|
12909
|
+
shape2 = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}"`;
|
|
11324
12910
|
}
|
|
11325
12911
|
break;
|
|
11326
12912
|
}
|
|
12913
|
+
case "composite": {
|
|
12914
|
+
const cn = node;
|
|
12915
|
+
switch (cn.shapeType) {
|
|
12916
|
+
case "circle": {
|
|
12917
|
+
const center = node.getCenter();
|
|
12918
|
+
shape2 = `<ellipse cx="${center.x}" cy="${center.y}" rx="${bounds.width / 2}" ry="${bounds.height / 2}"`;
|
|
12919
|
+
break;
|
|
12920
|
+
}
|
|
12921
|
+
case "diamond": {
|
|
12922
|
+
const center = node.getCenter();
|
|
12923
|
+
const hw = bounds.width / 2;
|
|
12924
|
+
const hh = bounds.height / 2;
|
|
12925
|
+
const points = [
|
|
12926
|
+
`${center.x},${center.y - hh}`,
|
|
12927
|
+
`${center.x + hw},${center.y}`,
|
|
12928
|
+
`${center.x},${center.y + hh}`,
|
|
12929
|
+
`${center.x - hw},${center.y}`
|
|
12930
|
+
].join(" ");
|
|
12931
|
+
shape2 = `<polygon points="${points}"`;
|
|
12932
|
+
break;
|
|
12933
|
+
}
|
|
12934
|
+
default: {
|
|
12935
|
+
const radius = cn.cornerRadius;
|
|
12936
|
+
shape2 = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}" rx="${radius}" ry="${radius}"`;
|
|
12937
|
+
break;
|
|
12938
|
+
}
|
|
12939
|
+
}
|
|
12940
|
+
const contentBounds = cn.getLabelContainerBounds(bounds);
|
|
12941
|
+
const contentSvg = cn.content.toSVG(contentBounds);
|
|
12942
|
+
return [
|
|
12943
|
+
`${shape2} fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${strokeOpacity}"${dash}${dashOffset}/>`,
|
|
12944
|
+
contentSvg
|
|
12945
|
+
].join("");
|
|
12946
|
+
}
|
|
11327
12947
|
default: {
|
|
11328
|
-
|
|
12948
|
+
shape2 = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}"`;
|
|
11329
12949
|
}
|
|
11330
12950
|
}
|
|
11331
12951
|
const label = this.renderNodeLabel(node, bounds);
|
|
11332
|
-
const
|
|
12952
|
+
const icon2 = this.renderNodeIcon(node, bounds);
|
|
11333
12953
|
return [
|
|
11334
|
-
`${
|
|
11335
|
-
|
|
12954
|
+
`${shape2} fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${strokeOpacity}"${dash}${dashOffset}/>`,
|
|
12955
|
+
icon2,
|
|
11336
12956
|
label
|
|
11337
12957
|
].join("");
|
|
11338
12958
|
}
|
|
@@ -11360,9 +12980,9 @@ class SvgExporter {
|
|
|
11360
12980
|
return "";
|
|
11361
12981
|
}
|
|
11362
12982
|
const labelPoint = this.getEdgeLabelPoint(edge, edgeLabelOffset);
|
|
11363
|
-
const
|
|
12983
|
+
const text2 = this.renderTextLabel(edge.label.text, labelPoint, edge.label.style);
|
|
11364
12984
|
const bg = this.renderEdgeLabelBackground(edge, labelPoint);
|
|
11365
|
-
return `${bg}${
|
|
12985
|
+
return `${bg}${text2}`;
|
|
11366
12986
|
}
|
|
11367
12987
|
renderEdgeLabelBackground(edge, point) {
|
|
11368
12988
|
if (!edge.label) {
|
|
@@ -11398,12 +13018,12 @@ class SvgExporter {
|
|
|
11398
13018
|
left: n(value.left)
|
|
11399
13019
|
};
|
|
11400
13020
|
}
|
|
11401
|
-
measureTextLabel(
|
|
13021
|
+
measureTextLabel(text2, style = {}, inset = 8) {
|
|
11402
13022
|
const fontSize = style.fontSize ?? 14;
|
|
11403
13023
|
const fontFamily = style.fontFamily ?? "sans-serif";
|
|
11404
13024
|
const fontWeight = style.fontWeight ?? "normal";
|
|
11405
13025
|
const lineHeight = fontSize * 1.2;
|
|
11406
|
-
const lines =
|
|
13026
|
+
const lines = text2.split("\n");
|
|
11407
13027
|
let maxWidth = 0;
|
|
11408
13028
|
if (typeof document !== "undefined") {
|
|
11409
13029
|
const canvas = document.createElement("canvas");
|
|
@@ -11516,7 +13136,7 @@ class SvgExporter {
|
|
|
11516
13136
|
y: midpoint.y + effectiveOffset
|
|
11517
13137
|
};
|
|
11518
13138
|
}
|
|
11519
|
-
renderTextLabel(
|
|
13139
|
+
renderTextLabel(text2, point, style = {}) {
|
|
11520
13140
|
const fill = style.color ?? "#000000";
|
|
11521
13141
|
const fontSize = style.fontSize ?? 14;
|
|
11522
13142
|
const fontFamily = style.fontFamily ?? "sans-serif";
|
|
@@ -11524,10 +13144,10 @@ class SvgExporter {
|
|
|
11524
13144
|
const opacity = style.opacity ?? 1;
|
|
11525
13145
|
const anchor = style.align === "left" ? "start" : style.align === "right" ? "end" : "middle";
|
|
11526
13146
|
const baseline = style.baseline === "top" ? "text-before-edge" : style.baseline === "bottom" ? "text-after-edge" : "middle";
|
|
11527
|
-
const lines =
|
|
13147
|
+
const lines = text2.split("\n");
|
|
11528
13148
|
if (lines.length <= 1) {
|
|
11529
13149
|
return `<text x="${point.x}" y="${point.y}" fill="${fill}" fill-opacity="${opacity}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" text-anchor="${anchor}" dominant-baseline="${baseline}">${this.escapeText(
|
|
11530
|
-
|
|
13150
|
+
text2
|
|
11531
13151
|
)}</text>`;
|
|
11532
13152
|
}
|
|
11533
13153
|
const lineHeight = fontSize * 1.2;
|
|
@@ -11596,16 +13216,16 @@ class SvgExporter {
|
|
|
11596
13216
|
return Math.max(0, Math.min(rectangleRadius, bounds.width / 2, bounds.height / 2));
|
|
11597
13217
|
}
|
|
11598
13218
|
renderNodeIcon(node, nodeBounds) {
|
|
11599
|
-
const
|
|
11600
|
-
if (!
|
|
13219
|
+
const icon2 = node.icon;
|
|
13220
|
+
if (!icon2) {
|
|
11601
13221
|
return "";
|
|
11602
13222
|
}
|
|
11603
|
-
const opts =
|
|
11604
|
-
const iconSize =
|
|
13223
|
+
const opts = icon2.options;
|
|
13224
|
+
const iconSize = icon2.getSize();
|
|
11605
13225
|
if (iconSize.width <= 0 || iconSize.height <= 0) {
|
|
11606
13226
|
return "";
|
|
11607
13227
|
}
|
|
11608
|
-
const iconInset =
|
|
13228
|
+
const iconInset = icon2.inset;
|
|
11609
13229
|
const iconBoxSize = this.getIconBoxSize(iconSize, iconInset);
|
|
11610
13230
|
const iconBounds = this.getIconBounds(
|
|
11611
13231
|
nodeBounds,
|
|
@@ -11817,8 +13437,8 @@ class SvgExporter {
|
|
|
11817
13437
|
"</svg>"
|
|
11818
13438
|
].join("");
|
|
11819
13439
|
}
|
|
11820
|
-
escapeText(
|
|
11821
|
-
return
|
|
13440
|
+
escapeText(text2) {
|
|
13441
|
+
return text2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11822
13442
|
}
|
|
11823
13443
|
}
|
|
11824
13444
|
class AutoLayout {
|
|
@@ -12370,8 +13990,14 @@ export {
|
|
|
12370
13990
|
AnimationManager,
|
|
12371
13991
|
AutoLayout,
|
|
12372
13992
|
AutoRouting,
|
|
13993
|
+
CContainer,
|
|
13994
|
+
CDivider,
|
|
13995
|
+
CIcon,
|
|
13996
|
+
CShape,
|
|
13997
|
+
CText,
|
|
12373
13998
|
CircleNode,
|
|
12374
13999
|
CompositeCommand,
|
|
14000
|
+
CompositeNode,
|
|
12375
14001
|
ConnectionManager,
|
|
12376
14002
|
ContextMenuManager,
|
|
12377
14003
|
CustomShapeNode,
|
|
@@ -12416,15 +14042,21 @@ export {
|
|
|
12416
14042
|
calculateBezierControlPoints,
|
|
12417
14043
|
clamp,
|
|
12418
14044
|
clonePoints,
|
|
14045
|
+
container,
|
|
14046
|
+
deserializeCComponent,
|
|
12419
14047
|
distance,
|
|
12420
14048
|
distanceToSegment,
|
|
12421
14049
|
distributeNodes,
|
|
14050
|
+
divider,
|
|
12422
14051
|
drawRoundedRectPath,
|
|
12423
14052
|
expandBounds,
|
|
14053
|
+
flexLayout,
|
|
12424
14054
|
generateId,
|
|
14055
|
+
icon,
|
|
12425
14056
|
isCornerPlacement,
|
|
12426
14057
|
lerp,
|
|
12427
14058
|
mergeBounds,
|
|
14059
|
+
normalizeSides,
|
|
12428
14060
|
pointInEllipse,
|
|
12429
14061
|
pointInRect,
|
|
12430
14062
|
rectIntersection,
|
|
@@ -12435,7 +14067,9 @@ export {
|
|
|
12435
14067
|
resetPortIdCounter,
|
|
12436
14068
|
rotatePoint,
|
|
12437
14069
|
segmentRectIntersections,
|
|
14070
|
+
shape,
|
|
12438
14071
|
snapPointToGrid,
|
|
12439
|
-
snapToGrid
|
|
14072
|
+
snapToGrid,
|
|
14073
|
+
text
|
|
12440
14074
|
};
|
|
12441
14075
|
//# sourceMappingURL=papirus.js.map
|