@ngroznykh/papirus 0.5.10 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/README.ru.md +42 -0
- 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 +86 -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 +1915 -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 = "";
|
|
@@ -4266,6 +4266,8 @@ class Edge extends Element {
|
|
|
4266
4266
|
this._pathStrategy = this.getPathStrategy(this._type);
|
|
4267
4267
|
this._lockAnchors = options.lockAnchors ?? true;
|
|
4268
4268
|
this._labelOffset = options.labelOffset ?? 0;
|
|
4269
|
+
this._labelPosition = options.labelPosition ?? 0.5;
|
|
4270
|
+
this._labelFollowPath = options.labelFollowPath ?? false;
|
|
4269
4271
|
this._labelBackground = options.labelBackground;
|
|
4270
4272
|
this._labelLineGap = options.labelLineGap ?? false;
|
|
4271
4273
|
if (options.label !== void 0) {
|
|
@@ -4359,6 +4361,31 @@ class Edge extends Element {
|
|
|
4359
4361
|
this.markDirty();
|
|
4360
4362
|
}
|
|
4361
4363
|
}
|
|
4364
|
+
/**
|
|
4365
|
+
* Position along path (0 = source, 0.5 = midpoint, 1 = target)
|
|
4366
|
+
*/
|
|
4367
|
+
get labelPosition() {
|
|
4368
|
+
return this._labelPosition;
|
|
4369
|
+
}
|
|
4370
|
+
set labelPosition(value) {
|
|
4371
|
+
const clamped = Math.max(0, Math.min(1, value));
|
|
4372
|
+
if (this._labelPosition !== clamped) {
|
|
4373
|
+
this._labelPosition = clamped;
|
|
4374
|
+
this.markDirty();
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
/**
|
|
4378
|
+
* Whether label text rotates to follow the path tangent
|
|
4379
|
+
*/
|
|
4380
|
+
get labelFollowPath() {
|
|
4381
|
+
return this._labelFollowPath;
|
|
4382
|
+
}
|
|
4383
|
+
set labelFollowPath(value) {
|
|
4384
|
+
if (this._labelFollowPath !== value) {
|
|
4385
|
+
this._labelFollowPath = value;
|
|
4386
|
+
this.markDirty();
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4362
4389
|
/**
|
|
4363
4390
|
* Label background configuration
|
|
4364
4391
|
*/
|
|
@@ -4794,11 +4821,20 @@ class Edge extends Element {
|
|
|
4794
4821
|
}
|
|
4795
4822
|
const labelWidth = this._label.measuredWidth;
|
|
4796
4823
|
const labelHeight = this._label.measuredHeight;
|
|
4824
|
+
const rot = this.getLabelRotation();
|
|
4825
|
+
let effectiveWidth = labelWidth;
|
|
4826
|
+
let effectiveHeight = labelHeight;
|
|
4827
|
+
if (rot !== 0) {
|
|
4828
|
+
const cosR = Math.abs(Math.cos(rot));
|
|
4829
|
+
const sinR = Math.abs(Math.sin(rot));
|
|
4830
|
+
effectiveWidth = labelWidth * cosR + labelHeight * sinR;
|
|
4831
|
+
effectiveHeight = labelWidth * sinR + labelHeight * cosR;
|
|
4832
|
+
}
|
|
4797
4833
|
const labelRect = {
|
|
4798
|
-
x: labelCenter.x -
|
|
4799
|
-
y: labelCenter.y -
|
|
4800
|
-
width:
|
|
4801
|
-
height:
|
|
4834
|
+
x: labelCenter.x - effectiveWidth / 2,
|
|
4835
|
+
y: labelCenter.y - effectiveHeight / 2,
|
|
4836
|
+
width: effectiveWidth,
|
|
4837
|
+
height: effectiveHeight
|
|
4802
4838
|
};
|
|
4803
4839
|
const segmentIntersections = [];
|
|
4804
4840
|
for (let i = 0; i < polyline.length - 1; i++) {
|
|
@@ -5072,10 +5108,12 @@ class Edge extends Element {
|
|
|
5072
5108
|
if (this._label === void 0 || this._path.length < 2) {
|
|
5073
5109
|
return;
|
|
5074
5110
|
}
|
|
5075
|
-
const
|
|
5076
|
-
const
|
|
5077
|
-
|
|
5078
|
-
|
|
5111
|
+
const { point, angle: pathAngle } = this.getPathPointAt(this._labelPosition);
|
|
5112
|
+
const rotation = this.getLabelRotation();
|
|
5113
|
+
const perpAngle = this._labelFollowPath ? pathAngle + Math.PI / 2 : Math.PI / 2;
|
|
5114
|
+
const labelCenter = {
|
|
5115
|
+
x: point.x + this._labelOffset * Math.cos(perpAngle),
|
|
5116
|
+
y: point.y + this._labelOffset * Math.sin(perpAngle)
|
|
5079
5117
|
};
|
|
5080
5118
|
const labelOpacity = this._label.style.opacity ?? 1;
|
|
5081
5119
|
this._label.measure(ctx);
|
|
@@ -5084,29 +5122,39 @@ class Edge extends Element {
|
|
|
5084
5122
|
const bgColor = this._labelBackground?.color ?? "#ffffff";
|
|
5085
5123
|
const bgOpacity = this._labelBackground?.opacity ?? 1;
|
|
5086
5124
|
const bgRadius = this._labelBackground?.borderRadius ?? EDGE_LABEL_BACKGROUND_RADIUS;
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5125
|
+
ctx.save();
|
|
5126
|
+
if (rotation !== 0) {
|
|
5127
|
+
ctx.translate(labelCenter.x, labelCenter.y);
|
|
5128
|
+
ctx.rotate(rotation);
|
|
5129
|
+
ctx.translate(-labelCenter.x, -labelCenter.y);
|
|
5130
|
+
}
|
|
5131
|
+
const bgX = labelCenter.x - labelWidth / 2;
|
|
5132
|
+
const bgY = labelCenter.y - labelHeight / 2;
|
|
5091
5133
|
ctx.fillStyle = bgColor;
|
|
5092
5134
|
ctx.globalAlpha = bgOpacity;
|
|
5093
5135
|
if (bgRadius > 0) {
|
|
5094
|
-
this.drawRoundedRect(ctx, bgX, bgY,
|
|
5136
|
+
this.drawRoundedRect(ctx, bgX, bgY, labelWidth, labelHeight, bgRadius);
|
|
5095
5137
|
ctx.fill();
|
|
5096
5138
|
} else {
|
|
5097
|
-
ctx.fillRect(bgX, bgY,
|
|
5139
|
+
ctx.fillRect(bgX, bgY, labelWidth, labelHeight);
|
|
5098
5140
|
}
|
|
5099
5141
|
ctx.globalAlpha = labelOpacity;
|
|
5100
|
-
this._label.renderAt(ctx,
|
|
5142
|
+
this._label.renderAt(ctx, labelCenter);
|
|
5101
5143
|
ctx.globalAlpha = 1;
|
|
5144
|
+
ctx.restore();
|
|
5102
5145
|
}
|
|
5103
5146
|
drawRoundedRect(ctx, x, y, width, height, radius) {
|
|
5104
5147
|
drawRoundedRectPath(ctx, x, y, width, height, radius);
|
|
5105
5148
|
}
|
|
5106
|
-
|
|
5149
|
+
/**
|
|
5150
|
+
* Get a point along the sampled path at parameter t (0..1).
|
|
5151
|
+
* Also returns the tangent angle in radians at that point.
|
|
5152
|
+
*/
|
|
5153
|
+
getPathPointAt(t) {
|
|
5107
5154
|
const path = this._path;
|
|
5155
|
+
let samples;
|
|
5108
5156
|
if (this._type === "bezier" && path.length >= 4) {
|
|
5109
|
-
|
|
5157
|
+
samples = [];
|
|
5110
5158
|
const steps = 20;
|
|
5111
5159
|
for (let i = 1; i + 2 < path.length; i += 3) {
|
|
5112
5160
|
const p0 = path[i - 1];
|
|
@@ -5114,16 +5162,15 @@ class Edge extends Element {
|
|
|
5114
5162
|
const p2 = path[i + 1];
|
|
5115
5163
|
const p3 = path[i + 2];
|
|
5116
5164
|
for (let s = 0; s <= steps; s++) {
|
|
5117
|
-
const
|
|
5118
|
-
if (samples.length > 0 &&
|
|
5119
|
-
|
|
5120
|
-
}
|
|
5121
|
-
samples.push(bezierPoint(p0, p1, p2, p3, t));
|
|
5165
|
+
const st = s / steps;
|
|
5166
|
+
if (samples.length > 0 && st === 0) continue;
|
|
5167
|
+
samples.push(bezierPoint(p0, p1, p2, p3, st));
|
|
5122
5168
|
}
|
|
5123
5169
|
}
|
|
5124
|
-
|
|
5170
|
+
} else {
|
|
5171
|
+
samples = path;
|
|
5125
5172
|
}
|
|
5126
|
-
return this.
|
|
5173
|
+
return this.getPointAlongPolyline(samples, t);
|
|
5127
5174
|
}
|
|
5128
5175
|
/**
|
|
5129
5176
|
* Get world position of label center along path.
|
|
@@ -5132,18 +5179,33 @@ class Edge extends Element {
|
|
|
5132
5179
|
if (this._path.length < 2) {
|
|
5133
5180
|
return null;
|
|
5134
5181
|
}
|
|
5135
|
-
const
|
|
5182
|
+
const { point, angle: pathAngle } = this.getPathPointAt(this._labelPosition);
|
|
5183
|
+
const perpAngle = this._labelFollowPath ? pathAngle + Math.PI / 2 : Math.PI / 2;
|
|
5136
5184
|
return {
|
|
5137
|
-
x:
|
|
5138
|
-
y:
|
|
5185
|
+
x: point.x + this._labelOffset * Math.cos(perpAngle),
|
|
5186
|
+
y: point.y + this._labelOffset * Math.sin(perpAngle)
|
|
5139
5187
|
};
|
|
5140
5188
|
}
|
|
5141
|
-
|
|
5189
|
+
/**
|
|
5190
|
+
* Get label rotation angle in radians (if labelFollowPath is true).
|
|
5191
|
+
*/
|
|
5192
|
+
getLabelRotation() {
|
|
5193
|
+
if (!this._labelFollowPath || this._path.length < 2) return 0;
|
|
5194
|
+
const { angle: angle2 } = this.getPathPointAt(this._labelPosition);
|
|
5195
|
+
let a = angle2;
|
|
5196
|
+
if (a > Math.PI / 2) a -= Math.PI;
|
|
5197
|
+
if (a < -Math.PI / 2) a += Math.PI;
|
|
5198
|
+
return a;
|
|
5199
|
+
}
|
|
5200
|
+
/**
|
|
5201
|
+
* Get point and tangent angle at parameter t along a polyline.
|
|
5202
|
+
*/
|
|
5203
|
+
getPointAlongPolyline(path, t) {
|
|
5142
5204
|
if (path.length === 0) {
|
|
5143
|
-
return { x: 0, y: 0 };
|
|
5205
|
+
return { point: { x: 0, y: 0 }, angle: 0 };
|
|
5144
5206
|
}
|
|
5145
5207
|
if (path.length === 1) {
|
|
5146
|
-
return path[0];
|
|
5208
|
+
return { point: path[0], angle: 0 };
|
|
5147
5209
|
}
|
|
5148
5210
|
let totalLength = 0;
|
|
5149
5211
|
const segments = [];
|
|
@@ -5154,19 +5216,28 @@ class Edge extends Element {
|
|
|
5154
5216
|
segments.push({ start, end, length });
|
|
5155
5217
|
totalLength += length;
|
|
5156
5218
|
}
|
|
5157
|
-
const
|
|
5219
|
+
const targetLength = totalLength * Math.max(0, Math.min(1, t));
|
|
5158
5220
|
let accumulated = 0;
|
|
5159
5221
|
for (const seg of segments) {
|
|
5160
|
-
if (accumulated + seg.length >=
|
|
5161
|
-
const
|
|
5162
|
-
|
|
5163
|
-
x: seg.start.x +
|
|
5164
|
-
y: seg.start.y +
|
|
5222
|
+
if (accumulated + seg.length >= targetLength) {
|
|
5223
|
+
const segT = seg.length > 0 ? (targetLength - accumulated) / seg.length : 0;
|
|
5224
|
+
const point = {
|
|
5225
|
+
x: seg.start.x + segT * (seg.end.x - seg.start.x),
|
|
5226
|
+
y: seg.start.y + segT * (seg.end.y - seg.start.y)
|
|
5165
5227
|
};
|
|
5228
|
+
const angle2 = Math.atan2(seg.end.y - seg.start.y, seg.end.x - seg.start.x);
|
|
5229
|
+
return { point, angle: angle2 };
|
|
5166
5230
|
}
|
|
5167
5231
|
accumulated += seg.length;
|
|
5168
5232
|
}
|
|
5169
|
-
|
|
5233
|
+
const lastSeg = segments[segments.length - 1];
|
|
5234
|
+
return {
|
|
5235
|
+
point: lastSeg.end,
|
|
5236
|
+
angle: Math.atan2(
|
|
5237
|
+
lastSeg.end.y - lastSeg.start.y,
|
|
5238
|
+
lastSeg.end.x - lastSeg.start.x
|
|
5239
|
+
)
|
|
5240
|
+
};
|
|
5170
5241
|
}
|
|
5171
5242
|
getPathStrategy(type) {
|
|
5172
5243
|
switch (type) {
|
|
@@ -5214,10 +5285,10 @@ class LabelEditor {
|
|
|
5214
5285
|
/**
|
|
5215
5286
|
* Start editing a label
|
|
5216
5287
|
*/
|
|
5217
|
-
start(kind, id,
|
|
5288
|
+
start(kind, id, text2, worldPosition, worldToScreen, onCommit) {
|
|
5218
5289
|
this.finish(true);
|
|
5219
5290
|
const screenPoint = worldToScreen(worldPosition.x, worldPosition.y);
|
|
5220
|
-
const textarea = this.createTextarea(
|
|
5291
|
+
const textarea = this.createTextarea(text2, screenPoint);
|
|
5221
5292
|
const { cleanup } = this.setupEventHandlers(textarea, onCommit);
|
|
5222
5293
|
document.body.appendChild(textarea);
|
|
5223
5294
|
textarea.focus();
|
|
@@ -5241,9 +5312,9 @@ class LabelEditor {
|
|
|
5241
5312
|
onCommit(kind, id, value);
|
|
5242
5313
|
}
|
|
5243
5314
|
}
|
|
5244
|
-
createTextarea(
|
|
5315
|
+
createTextarea(text2, screenPoint) {
|
|
5245
5316
|
const textarea = document.createElement("textarea");
|
|
5246
|
-
textarea.value =
|
|
5317
|
+
textarea.value = text2;
|
|
5247
5318
|
textarea.rows = 1;
|
|
5248
5319
|
textarea.spellcheck = false;
|
|
5249
5320
|
textarea.setAttribute("aria-label", "Edit label");
|
|
@@ -5820,6 +5891,24 @@ class InteractionManager {
|
|
|
5820
5891
|
return;
|
|
5821
5892
|
}
|
|
5822
5893
|
this.selectionManager.handleClick(event);
|
|
5894
|
+
this.emitComponentClick(event);
|
|
5895
|
+
}
|
|
5896
|
+
emitComponentClick(event) {
|
|
5897
|
+
const point = { x: event.worldX, y: event.worldY };
|
|
5898
|
+
const hitElement = this.renderer.getInteractableElementAtPoint(point, event.screenX, event.screenY);
|
|
5899
|
+
if (!hitElement || !("typeName" in hitElement)) return;
|
|
5900
|
+
const node = hitElement;
|
|
5901
|
+
if (node.typeName !== "composite") return;
|
|
5902
|
+
const compositeNode = node;
|
|
5903
|
+
const component = compositeNode.getComponentAtPoint(point);
|
|
5904
|
+
if (!component) return;
|
|
5905
|
+
if (component.id !== void 0) {
|
|
5906
|
+
this.renderer.emit("componentClick", node.id, component, point);
|
|
5907
|
+
}
|
|
5908
|
+
const asAny = component;
|
|
5909
|
+
if (typeof asAny["onClick"] === "function") {
|
|
5910
|
+
asAny["onClick"](component);
|
|
5911
|
+
}
|
|
5823
5912
|
}
|
|
5824
5913
|
handleDoubleClick(event) {
|
|
5825
5914
|
if (this.renderer.blocksDiagramPointerAtScreen(event.screenX, event.screenY)) {
|
|
@@ -5847,6 +5936,10 @@ class InteractionManager {
|
|
|
5847
5936
|
return;
|
|
5848
5937
|
}
|
|
5849
5938
|
if ("typeName" in hitElement) {
|
|
5939
|
+
if (hitElement.typeName === "composite") {
|
|
5940
|
+
this.startCompositeNameEdit(hitElement);
|
|
5941
|
+
return;
|
|
5942
|
+
}
|
|
5850
5943
|
const editText = hitElement.label?.editableText ?? hitElement.label?.text ?? "";
|
|
5851
5944
|
this.startLabelEdit("node", hitElement.id, editText, hitElement.getLabelPosition());
|
|
5852
5945
|
return;
|
|
@@ -5956,16 +6049,67 @@ class InteractionManager {
|
|
|
5956
6049
|
return false;
|
|
5957
6050
|
}
|
|
5958
6051
|
}
|
|
5959
|
-
startLabelEdit(kind, id,
|
|
6052
|
+
startLabelEdit(kind, id, text2, worldPosition) {
|
|
5960
6053
|
this.labelEditor.start(
|
|
5961
6054
|
kind,
|
|
5962
6055
|
id,
|
|
5963
|
-
|
|
6056
|
+
text2,
|
|
5964
6057
|
worldPosition,
|
|
5965
6058
|
(x, y) => this.renderer.worldToScreen(x, y),
|
|
5966
6059
|
(k, i, value) => this.handleLabelCommit(k, i, value)
|
|
5967
6060
|
);
|
|
5968
6061
|
}
|
|
6062
|
+
startCompositeNameEdit(node) {
|
|
6063
|
+
const nameText = this.findNameComponent(node.content);
|
|
6064
|
+
if (!nameText) return;
|
|
6065
|
+
const currentText = nameText.text;
|
|
6066
|
+
const worldPos = node.getLabelPosition();
|
|
6067
|
+
this.labelEditor.start(
|
|
6068
|
+
"node",
|
|
6069
|
+
node.id,
|
|
6070
|
+
currentText,
|
|
6071
|
+
worldPos,
|
|
6072
|
+
(x, y) => this.renderer.worldToScreen(x, y),
|
|
6073
|
+
(_kind, _id, value) => {
|
|
6074
|
+
const trimmed = value.trim();
|
|
6075
|
+
if (trimmed.length > 0 && trimmed !== currentText) {
|
|
6076
|
+
const before = currentText;
|
|
6077
|
+
nameText.text = trimmed;
|
|
6078
|
+
this.historyManager.execute({
|
|
6079
|
+
execute: () => {
|
|
6080
|
+
nameText.text = trimmed;
|
|
6081
|
+
},
|
|
6082
|
+
undo: () => {
|
|
6083
|
+
nameText.text = before;
|
|
6084
|
+
}
|
|
6085
|
+
});
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
);
|
|
6089
|
+
}
|
|
6090
|
+
findNameComponent(container2) {
|
|
6091
|
+
const bindToDisplayName = "__name__";
|
|
6092
|
+
for (const child of container2.children) {
|
|
6093
|
+
if (child.type === "text") {
|
|
6094
|
+
const ctext = child;
|
|
6095
|
+
if (ctext.bindToProperty === bindToDisplayName) return ctext;
|
|
6096
|
+
}
|
|
6097
|
+
if (child.type === "container") {
|
|
6098
|
+
const found = this.findNameComponent(
|
|
6099
|
+
child
|
|
6100
|
+
);
|
|
6101
|
+
if (found) return found;
|
|
6102
|
+
}
|
|
6103
|
+
if (child.type === "shape") {
|
|
6104
|
+
const content = child.content;
|
|
6105
|
+
if (content) {
|
|
6106
|
+
const found = this.findNameComponent(content);
|
|
6107
|
+
if (found) return found;
|
|
6108
|
+
}
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
return null;
|
|
6112
|
+
}
|
|
5969
6113
|
handleLabelCommit(kind, id, nextValue) {
|
|
5970
6114
|
if (kind === "node") {
|
|
5971
6115
|
this.changeNodeProperties(id, (node) => {
|
|
@@ -6500,20 +6644,20 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6500
6644
|
}
|
|
6501
6645
|
}
|
|
6502
6646
|
buildMenu(items, target) {
|
|
6503
|
-
const
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6647
|
+
const container2 = document.createElement("div");
|
|
6648
|
+
container2.style.position = "fixed";
|
|
6649
|
+
container2.style.zIndex = "10000";
|
|
6650
|
+
container2.style.background = "#ffffff";
|
|
6651
|
+
container2.style.border = "1px solid #e5e7eb";
|
|
6652
|
+
container2.style.borderRadius = "8px";
|
|
6653
|
+
container2.style.padding = "6px";
|
|
6654
|
+
container2.style.boxShadow = "0 8px 20px rgba(15, 23, 42, 0.18)";
|
|
6655
|
+
container2.style.fontFamily = "system-ui, -apple-system, Segoe UI, sans-serif";
|
|
6656
|
+
container2.style.fontSize = "14px";
|
|
6657
|
+
container2.style.color = "#0f172a";
|
|
6514
6658
|
const list = this.buildList(items, target);
|
|
6515
|
-
|
|
6516
|
-
return
|
|
6659
|
+
container2.appendChild(list);
|
|
6660
|
+
return container2;
|
|
6517
6661
|
}
|
|
6518
6662
|
buildList(items, target) {
|
|
6519
6663
|
const list = document.createElement("ul");
|
|
@@ -6615,7 +6759,7 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6615
6759
|
}
|
|
6616
6760
|
return list;
|
|
6617
6761
|
}
|
|
6618
|
-
createIconElement(
|
|
6762
|
+
createIconElement(icon2) {
|
|
6619
6763
|
const iconEl = document.createElement("span");
|
|
6620
6764
|
iconEl.style.width = "16px";
|
|
6621
6765
|
iconEl.style.height = "16px";
|
|
@@ -6624,16 +6768,16 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6624
6768
|
iconEl.style.justifyContent = "center";
|
|
6625
6769
|
iconEl.style.textAlign = "center";
|
|
6626
6770
|
iconEl.style.flexShrink = "0";
|
|
6627
|
-
if (!
|
|
6771
|
+
if (!icon2) {
|
|
6628
6772
|
iconEl.textContent = "";
|
|
6629
6773
|
return iconEl;
|
|
6630
6774
|
}
|
|
6631
|
-
if (typeof
|
|
6632
|
-
if (this.isSvgString(
|
|
6633
|
-
iconEl.innerHTML =
|
|
6775
|
+
if (typeof icon2 === "string") {
|
|
6776
|
+
if (this.isSvgString(icon2)) {
|
|
6777
|
+
iconEl.innerHTML = icon2;
|
|
6634
6778
|
} else if (this.options.iconToUrl) {
|
|
6635
6779
|
const img = document.createElement("img");
|
|
6636
|
-
img.src = this.options.iconToUrl(
|
|
6780
|
+
img.src = this.options.iconToUrl(icon2);
|
|
6637
6781
|
img.alt = "";
|
|
6638
6782
|
img.style.width = "16px";
|
|
6639
6783
|
img.style.height = "16px";
|
|
@@ -6642,15 +6786,15 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6642
6786
|
} else {
|
|
6643
6787
|
iconEl.classList.add("material-symbols-outlined");
|
|
6644
6788
|
iconEl.style.fontSize = "16px";
|
|
6645
|
-
iconEl.textContent =
|
|
6789
|
+
iconEl.textContent = icon2;
|
|
6646
6790
|
}
|
|
6647
6791
|
return iconEl;
|
|
6648
6792
|
}
|
|
6649
|
-
if (
|
|
6650
|
-
iconEl.innerHTML =
|
|
6793
|
+
if (icon2.type === "svg" || icon2.type === "html") {
|
|
6794
|
+
iconEl.innerHTML = icon2.value;
|
|
6651
6795
|
} else if (this.options.iconToUrl) {
|
|
6652
6796
|
const img = document.createElement("img");
|
|
6653
|
-
img.src = this.options.iconToUrl(
|
|
6797
|
+
img.src = this.options.iconToUrl(icon2.value);
|
|
6654
6798
|
img.alt = "";
|
|
6655
6799
|
img.style.width = "16px";
|
|
6656
6800
|
img.style.height = "16px";
|
|
@@ -6659,7 +6803,7 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6659
6803
|
} else {
|
|
6660
6804
|
iconEl.classList.add("material-symbols-outlined");
|
|
6661
6805
|
iconEl.style.fontSize = "16px";
|
|
6662
|
-
iconEl.textContent =
|
|
6806
|
+
iconEl.textContent = icon2.value;
|
|
6663
6807
|
}
|
|
6664
6808
|
return iconEl;
|
|
6665
6809
|
}
|
|
@@ -8540,10 +8684,6 @@ class LRUCache {
|
|
|
8540
8684
|
return this.maxSize;
|
|
8541
8685
|
}
|
|
8542
8686
|
}
|
|
8543
|
-
function isCornerPlacement(placement) {
|
|
8544
|
-
return placement === "top-left" || placement === "top-right" || placement === "bottom-left" || placement === "bottom-right";
|
|
8545
|
-
}
|
|
8546
|
-
const svgTextCache = new LRUCache(100);
|
|
8547
8687
|
function styleSetColor(style, key, color) {
|
|
8548
8688
|
const hasKey = new RegExp(`${key}\\s*:`).test(style);
|
|
8549
8689
|
if (hasKey) {
|
|
@@ -8594,6 +8734,18 @@ function svgToDataUrl(svg) {
|
|
|
8594
8734
|
const encoded = encodeURIComponent(svg).replace(/%0A/g, "").replace(/%0D/g, "").replace(/%09/g, " ").replace(/%20/g, " ");
|
|
8595
8735
|
return `data:image/svg+xml;utf8,${encoded}`;
|
|
8596
8736
|
}
|
|
8737
|
+
function isSvgUrl(url) {
|
|
8738
|
+
try {
|
|
8739
|
+
const path = new URL(url, "http://localhost").pathname;
|
|
8740
|
+
return path.endsWith(".svg");
|
|
8741
|
+
} catch {
|
|
8742
|
+
return url.endsWith(".svg");
|
|
8743
|
+
}
|
|
8744
|
+
}
|
|
8745
|
+
function isCornerPlacement(placement) {
|
|
8746
|
+
return placement === "top-left" || placement === "top-right" || placement === "bottom-left" || placement === "bottom-right";
|
|
8747
|
+
}
|
|
8748
|
+
const svgTextCache = new LRUCache(100);
|
|
8597
8749
|
class NodeImage {
|
|
8598
8750
|
constructor(options, onChange) {
|
|
8599
8751
|
this._loaded = false;
|
|
@@ -10454,181 +10606,1609 @@ const ShapeFactories = {
|
|
|
10454
10606
|
}
|
|
10455
10607
|
}
|
|
10456
10608
|
};
|
|
10457
|
-
const
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
}
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
},
|
|
10491
|
-
edge: {
|
|
10492
|
-
default: {
|
|
10493
|
-
strokeColor: "#666666",
|
|
10494
|
-
strokeWidth: 2,
|
|
10495
|
-
opacity: 1
|
|
10496
|
-
},
|
|
10497
|
-
hover: {
|
|
10498
|
-
strokeColor: "#6366f1",
|
|
10499
|
-
strokeWidth: 2,
|
|
10500
|
-
opacity: 1
|
|
10501
|
-
},
|
|
10502
|
-
selected: {
|
|
10503
|
-
strokeColor: "#3b82f6",
|
|
10504
|
-
strokeWidth: 3,
|
|
10505
|
-
opacity: 1
|
|
10506
|
-
}
|
|
10507
|
-
},
|
|
10508
|
-
text: {
|
|
10509
|
-
font: "14px sans-serif",
|
|
10510
|
-
fontSize: 14,
|
|
10511
|
-
fontFamily: "sans-serif",
|
|
10512
|
-
fontWeight: "normal",
|
|
10513
|
-
color: "#333333",
|
|
10514
|
-
align: "center",
|
|
10515
|
-
baseline: "middle"
|
|
10516
|
-
},
|
|
10517
|
-
port: {
|
|
10518
|
-
default: { color: "#666666", radius: 6 },
|
|
10519
|
-
hover: { color: "#3b82f6", radius: 7 }
|
|
10520
|
-
},
|
|
10521
|
-
group: {
|
|
10522
|
-
default: {
|
|
10523
|
-
fillColor: "rgba(200, 200, 200, 0.2)",
|
|
10524
|
-
strokeColor: "#999999",
|
|
10525
|
-
strokeWidth: 1,
|
|
10526
|
-
opacity: 1
|
|
10527
|
-
},
|
|
10528
|
-
selected: {
|
|
10529
|
-
fillColor: "rgba(59, 130, 246, 0.1)",
|
|
10530
|
-
strokeColor: "#3b82f6",
|
|
10531
|
-
strokeWidth: 2,
|
|
10532
|
-
opacity: 1
|
|
10609
|
+
const DEFAULT_FONT_FAMILY = "sans-serif";
|
|
10610
|
+
const DEFAULT_FONT_SIZE = 14;
|
|
10611
|
+
const DEFAULT_LINE_HEIGHT = 1.2;
|
|
10612
|
+
const DEFAULT_COLOR$1 = "#000000";
|
|
10613
|
+
class CText {
|
|
10614
|
+
constructor(options) {
|
|
10615
|
+
this.type = "text";
|
|
10616
|
+
this._cachedLines = null;
|
|
10617
|
+
this.id = options.id;
|
|
10618
|
+
this._text = options.text;
|
|
10619
|
+
this._fontFamily = options.fontFamily ?? DEFAULT_FONT_FAMILY;
|
|
10620
|
+
this._fontWeight = options.fontWeight ?? "normal";
|
|
10621
|
+
this._fontStyle = options.fontStyle ?? "normal";
|
|
10622
|
+
this._fontSize = options.fontSize ?? DEFAULT_FONT_SIZE;
|
|
10623
|
+
this._color = options.color ?? DEFAULT_COLOR$1;
|
|
10624
|
+
this._align = options.align ?? "center";
|
|
10625
|
+
this._verticalAlign = options.verticalAlign ?? "middle";
|
|
10626
|
+
this._maxLines = options.maxLines;
|
|
10627
|
+
this._lineHeight = options.lineHeight ?? DEFAULT_LINE_HEIGHT;
|
|
10628
|
+
this._role = options.role;
|
|
10629
|
+
this._bindToProperty = options.bindToProperty;
|
|
10630
|
+
this._rotation = options.rotation ?? 0;
|
|
10631
|
+
this.style = options.style ?? {};
|
|
10632
|
+
}
|
|
10633
|
+
// --- Property accessors with dirty notification ---
|
|
10634
|
+
get text() {
|
|
10635
|
+
return this._text;
|
|
10636
|
+
}
|
|
10637
|
+
set text(value) {
|
|
10638
|
+
if (this._text !== value) {
|
|
10639
|
+
this._text = value;
|
|
10640
|
+
this._cachedLines = null;
|
|
10641
|
+
this._onChange?.();
|
|
10533
10642
|
}
|
|
10534
10643
|
}
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
10541
|
-
|
|
10542
|
-
connectionPreview: "#6366f1"
|
|
10543
|
-
},
|
|
10544
|
-
node: {
|
|
10545
|
-
default: {
|
|
10546
|
-
fillColor: "#2d2d2d",
|
|
10547
|
-
strokeColor: "#555555",
|
|
10548
|
-
strokeWidth: 2,
|
|
10549
|
-
opacity: 1,
|
|
10550
|
-
cornerRadius: 4
|
|
10551
|
-
},
|
|
10552
|
-
hover: {
|
|
10553
|
-
fillColor: "#3d3d3d",
|
|
10554
|
-
strokeColor: "#6366f1",
|
|
10555
|
-
strokeWidth: 2,
|
|
10556
|
-
opacity: 1,
|
|
10557
|
-
cornerRadius: 4
|
|
10558
|
-
},
|
|
10559
|
-
selected: {
|
|
10560
|
-
fillColor: "#2d2d2d",
|
|
10561
|
-
strokeColor: "#555555",
|
|
10562
|
-
strokeWidth: 2,
|
|
10563
|
-
opacity: 1,
|
|
10564
|
-
cornerRadius: 4
|
|
10565
|
-
},
|
|
10566
|
-
dragging: {
|
|
10567
|
-
fillColor: "#404040",
|
|
10568
|
-
strokeColor: "#555555",
|
|
10569
|
-
strokeWidth: 2,
|
|
10570
|
-
opacity: 0.8,
|
|
10571
|
-
cornerRadius: 4
|
|
10644
|
+
get fontFamily() {
|
|
10645
|
+
return this._fontFamily;
|
|
10646
|
+
}
|
|
10647
|
+
set fontFamily(value) {
|
|
10648
|
+
if (this._fontFamily !== value) {
|
|
10649
|
+
this._fontFamily = value;
|
|
10650
|
+
this._onChange?.();
|
|
10572
10651
|
}
|
|
10573
|
-
}
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
strokeColor: "#6366f1",
|
|
10582
|
-
strokeWidth: 2,
|
|
10583
|
-
opacity: 1
|
|
10584
|
-
},
|
|
10585
|
-
selected: {
|
|
10586
|
-
strokeColor: "#818cf8",
|
|
10587
|
-
strokeWidth: 3,
|
|
10588
|
-
opacity: 1
|
|
10652
|
+
}
|
|
10653
|
+
get fontWeight() {
|
|
10654
|
+
return this._fontWeight;
|
|
10655
|
+
}
|
|
10656
|
+
set fontWeight(value) {
|
|
10657
|
+
if (this._fontWeight !== value) {
|
|
10658
|
+
this._fontWeight = value;
|
|
10659
|
+
this._onChange?.();
|
|
10589
10660
|
}
|
|
10590
|
-
}
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
baseline: "middle"
|
|
10599
|
-
},
|
|
10600
|
-
port: {
|
|
10601
|
-
default: { color: "#888888", radius: 6 },
|
|
10602
|
-
hover: { color: "#6366f1", radius: 7 }
|
|
10603
|
-
},
|
|
10604
|
-
group: {
|
|
10605
|
-
default: {
|
|
10606
|
-
fillColor: "rgba(100, 100, 100, 0.2)",
|
|
10607
|
-
strokeColor: "#666666",
|
|
10608
|
-
strokeWidth: 1,
|
|
10609
|
-
opacity: 1
|
|
10610
|
-
},
|
|
10611
|
-
selected: {
|
|
10612
|
-
fillColor: "rgba(99, 102, 241, 0.2)",
|
|
10613
|
-
strokeColor: "#6366f1",
|
|
10614
|
-
strokeWidth: 2,
|
|
10615
|
-
opacity: 1
|
|
10661
|
+
}
|
|
10662
|
+
get fontStyle() {
|
|
10663
|
+
return this._fontStyle;
|
|
10664
|
+
}
|
|
10665
|
+
set fontStyle(value) {
|
|
10666
|
+
if (this._fontStyle !== value) {
|
|
10667
|
+
this._fontStyle = value;
|
|
10668
|
+
this._onChange?.();
|
|
10616
10669
|
}
|
|
10617
10670
|
}
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
this.
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10671
|
+
get fontSize() {
|
|
10672
|
+
return this._fontSize;
|
|
10673
|
+
}
|
|
10674
|
+
set fontSize(value) {
|
|
10675
|
+
if (this._fontSize !== value) {
|
|
10676
|
+
this._fontSize = value;
|
|
10677
|
+
this._onChange?.();
|
|
10678
|
+
}
|
|
10679
|
+
}
|
|
10680
|
+
get color() {
|
|
10681
|
+
return this._color;
|
|
10682
|
+
}
|
|
10683
|
+
set color(value) {
|
|
10684
|
+
if (this._color !== value) {
|
|
10685
|
+
this._color = value;
|
|
10686
|
+
this._onChange?.();
|
|
10687
|
+
}
|
|
10688
|
+
}
|
|
10689
|
+
get align() {
|
|
10690
|
+
return this._align;
|
|
10691
|
+
}
|
|
10692
|
+
set align(value) {
|
|
10693
|
+
if (this._align !== value) {
|
|
10694
|
+
this._align = value;
|
|
10695
|
+
this._onChange?.();
|
|
10696
|
+
}
|
|
10697
|
+
}
|
|
10698
|
+
get verticalAlign() {
|
|
10699
|
+
return this._verticalAlign;
|
|
10700
|
+
}
|
|
10701
|
+
set verticalAlign(value) {
|
|
10702
|
+
if (this._verticalAlign !== value) {
|
|
10703
|
+
this._verticalAlign = value;
|
|
10704
|
+
this._onChange?.();
|
|
10705
|
+
}
|
|
10706
|
+
}
|
|
10707
|
+
get maxLines() {
|
|
10708
|
+
return this._maxLines;
|
|
10709
|
+
}
|
|
10710
|
+
set maxLines(value) {
|
|
10711
|
+
if (this._maxLines !== value) {
|
|
10712
|
+
this._maxLines = value;
|
|
10713
|
+
this._onChange?.();
|
|
10714
|
+
}
|
|
10715
|
+
}
|
|
10716
|
+
get lineHeight() {
|
|
10717
|
+
return this._lineHeight;
|
|
10718
|
+
}
|
|
10719
|
+
get role() {
|
|
10720
|
+
return this._role;
|
|
10721
|
+
}
|
|
10722
|
+
get bindToProperty() {
|
|
10723
|
+
return this._bindToProperty;
|
|
10724
|
+
}
|
|
10725
|
+
get rotation() {
|
|
10726
|
+
return this._rotation;
|
|
10727
|
+
}
|
|
10728
|
+
set rotation(value) {
|
|
10729
|
+
if (this._rotation !== value) {
|
|
10730
|
+
this._rotation = value;
|
|
10731
|
+
this._onChange?.();
|
|
10732
|
+
}
|
|
10733
|
+
}
|
|
10734
|
+
setOnChange(cb) {
|
|
10735
|
+
this._onChange = cb;
|
|
10736
|
+
}
|
|
10737
|
+
getFont() {
|
|
10738
|
+
return `${this._fontStyle} ${this._fontWeight} ${this._fontSize}px ${this._fontFamily}`;
|
|
10739
|
+
}
|
|
10740
|
+
getLineHeightPx() {
|
|
10741
|
+
return this._fontSize * this._lineHeight;
|
|
10742
|
+
}
|
|
10743
|
+
/**
|
|
10744
|
+
* Word-wrap text to fit within maxWidth.
|
|
10745
|
+
*/
|
|
10746
|
+
wrapText(ctx, maxWidth) {
|
|
10747
|
+
ctx.font = this.getFont();
|
|
10748
|
+
const words = this._text.split(" ");
|
|
10749
|
+
const lines = [];
|
|
10750
|
+
let currentLine = "";
|
|
10751
|
+
for (const word of words) {
|
|
10752
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
10753
|
+
const metrics = ctx.measureText(testLine);
|
|
10754
|
+
if (metrics.width > maxWidth && currentLine) {
|
|
10755
|
+
lines.push(currentLine);
|
|
10756
|
+
currentLine = word;
|
|
10757
|
+
} else {
|
|
10758
|
+
currentLine = testLine;
|
|
10759
|
+
}
|
|
10760
|
+
}
|
|
10761
|
+
if (currentLine) {
|
|
10762
|
+
lines.push(currentLine);
|
|
10763
|
+
}
|
|
10764
|
+
if (this._maxLines !== void 0 && lines.length > this._maxLines) {
|
|
10765
|
+
const truncated = lines.slice(0, this._maxLines);
|
|
10766
|
+
const lastLine = truncated[truncated.length - 1];
|
|
10767
|
+
if (lastLine !== void 0) {
|
|
10768
|
+
truncated[truncated.length - 1] = lastLine + "…";
|
|
10769
|
+
}
|
|
10770
|
+
return truncated;
|
|
10771
|
+
}
|
|
10772
|
+
return lines;
|
|
10773
|
+
}
|
|
10774
|
+
measure(ctx) {
|
|
10775
|
+
ctx.font = this.getFont();
|
|
10776
|
+
const lineHeightPx = this.getLineHeightPx();
|
|
10777
|
+
const rawLines = this._text.split("\n");
|
|
10778
|
+
let maxWidth = 0;
|
|
10779
|
+
for (const line of rawLines) {
|
|
10780
|
+
const metrics = ctx.measureText(line);
|
|
10781
|
+
if (metrics.width > maxWidth) {
|
|
10782
|
+
maxWidth = metrics.width;
|
|
10783
|
+
}
|
|
10784
|
+
}
|
|
10785
|
+
let lineCount = rawLines.length;
|
|
10786
|
+
if (this._maxLines !== void 0 && lineCount > this._maxLines) {
|
|
10787
|
+
lineCount = this._maxLines;
|
|
10788
|
+
}
|
|
10789
|
+
const width = maxWidth;
|
|
10790
|
+
const height = lineCount * lineHeightPx;
|
|
10791
|
+
if (this._rotation === 90 || this._rotation === -90) {
|
|
10792
|
+
return { width: height, height: width };
|
|
10793
|
+
}
|
|
10794
|
+
return { width, height };
|
|
10795
|
+
}
|
|
10796
|
+
render(ctx, bounds) {
|
|
10797
|
+
if (this.style.visible === false) return;
|
|
10798
|
+
const opacity = this.style.opacity ?? 1;
|
|
10799
|
+
if (opacity <= 0) return;
|
|
10800
|
+
ctx.save();
|
|
10801
|
+
ctx.globalAlpha *= opacity;
|
|
10802
|
+
ctx.font = this.getFont();
|
|
10803
|
+
ctx.fillStyle = this._color;
|
|
10804
|
+
const isRotated = this._rotation === 90 || this._rotation === -90;
|
|
10805
|
+
const textBounds = isRotated ? { x: bounds.x, y: bounds.y, width: bounds.height, height: bounds.width } : bounds;
|
|
10806
|
+
const lines = this.wrapText(ctx, textBounds.width);
|
|
10807
|
+
this._cachedLines = lines;
|
|
10808
|
+
const lineHeightPx = this.getLineHeightPx();
|
|
10809
|
+
const totalTextHeight = lines.length * lineHeightPx;
|
|
10810
|
+
let textAlign;
|
|
10811
|
+
let xBase;
|
|
10812
|
+
switch (this._align) {
|
|
10813
|
+
case "left":
|
|
10814
|
+
textAlign = "left";
|
|
10815
|
+
xBase = textBounds.x;
|
|
10816
|
+
break;
|
|
10817
|
+
case "right":
|
|
10818
|
+
textAlign = "right";
|
|
10819
|
+
xBase = textBounds.x + textBounds.width;
|
|
10820
|
+
break;
|
|
10821
|
+
default:
|
|
10822
|
+
textAlign = "center";
|
|
10823
|
+
xBase = textBounds.x + textBounds.width / 2;
|
|
10824
|
+
break;
|
|
10825
|
+
}
|
|
10826
|
+
let yStart;
|
|
10827
|
+
switch (this._verticalAlign) {
|
|
10828
|
+
case "top":
|
|
10829
|
+
yStart = textBounds.y + lineHeightPx / 2;
|
|
10830
|
+
break;
|
|
10831
|
+
case "bottom":
|
|
10832
|
+
yStart = textBounds.y + textBounds.height - totalTextHeight + lineHeightPx / 2;
|
|
10833
|
+
break;
|
|
10834
|
+
default:
|
|
10835
|
+
yStart = textBounds.y + (textBounds.height - totalTextHeight) / 2 + lineHeightPx / 2;
|
|
10836
|
+
break;
|
|
10837
|
+
}
|
|
10838
|
+
ctx.textAlign = textAlign;
|
|
10839
|
+
ctx.textBaseline = "middle";
|
|
10840
|
+
if (isRotated) {
|
|
10841
|
+
const cx = bounds.x + bounds.width / 2;
|
|
10842
|
+
const cy = bounds.y + bounds.height / 2;
|
|
10843
|
+
ctx.translate(cx, cy);
|
|
10844
|
+
ctx.rotate(this._rotation * Math.PI / 180);
|
|
10845
|
+
const offsetX = xBase - (textBounds.x + textBounds.width / 2);
|
|
10846
|
+
const offsetY = yStart - (textBounds.y + textBounds.height / 2);
|
|
10847
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10848
|
+
ctx.fillText(lines[i], offsetX, offsetY + i * lineHeightPx);
|
|
10849
|
+
}
|
|
10850
|
+
} else {
|
|
10851
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10852
|
+
ctx.fillText(lines[i], xBase, yStart + i * lineHeightPx);
|
|
10853
|
+
}
|
|
10854
|
+
}
|
|
10855
|
+
ctx.restore();
|
|
10856
|
+
}
|
|
10857
|
+
hitTest(point, bounds) {
|
|
10858
|
+
if (this.style.visible === false) return null;
|
|
10859
|
+
if (point.x >= bounds.x && point.x <= bounds.x + bounds.width && point.y >= bounds.y && point.y <= bounds.y + bounds.height) {
|
|
10860
|
+
return this;
|
|
10861
|
+
}
|
|
10862
|
+
return null;
|
|
10863
|
+
}
|
|
10864
|
+
serialize() {
|
|
10865
|
+
const data = {
|
|
10866
|
+
type: "text",
|
|
10867
|
+
text: this._text
|
|
10868
|
+
};
|
|
10869
|
+
if (this.id !== void 0) data.id = this.id;
|
|
10870
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
10871
|
+
if (this._fontFamily !== DEFAULT_FONT_FAMILY) data.fontFamily = this._fontFamily;
|
|
10872
|
+
if (this._fontWeight !== "normal") data.fontWeight = this._fontWeight;
|
|
10873
|
+
if (this._fontStyle !== "normal") data.fontStyle = this._fontStyle;
|
|
10874
|
+
if (this._fontSize !== DEFAULT_FONT_SIZE) data.fontSize = this._fontSize;
|
|
10875
|
+
if (this._color !== DEFAULT_COLOR$1) data.color = this._color;
|
|
10876
|
+
if (this._align !== "center") data.align = this._align;
|
|
10877
|
+
if (this._verticalAlign !== "middle") data.verticalAlign = this._verticalAlign;
|
|
10878
|
+
if (this._maxLines !== void 0) data.maxLines = this._maxLines;
|
|
10879
|
+
if (this._lineHeight !== DEFAULT_LINE_HEIGHT) data.lineHeight = this._lineHeight;
|
|
10880
|
+
if (this._bindToProperty !== void 0) data.bindToProperty = this._bindToProperty;
|
|
10881
|
+
if (this._role !== void 0) data.role = this._role;
|
|
10882
|
+
if (this._rotation !== 0) data.rotation = this._rotation;
|
|
10883
|
+
return data;
|
|
10884
|
+
}
|
|
10885
|
+
toSVG(bounds) {
|
|
10886
|
+
if (this.style.visible === false) return "";
|
|
10887
|
+
const lineHeightPx = this.getLineHeightPx();
|
|
10888
|
+
const displayLines = this._cachedLines ?? this._text.split("\n");
|
|
10889
|
+
const totalTextHeight = displayLines.length * lineHeightPx;
|
|
10890
|
+
let anchor;
|
|
10891
|
+
let xBase;
|
|
10892
|
+
switch (this._align) {
|
|
10893
|
+
case "left":
|
|
10894
|
+
anchor = "start";
|
|
10895
|
+
xBase = bounds.x;
|
|
10896
|
+
break;
|
|
10897
|
+
case "right":
|
|
10898
|
+
anchor = "end";
|
|
10899
|
+
xBase = bounds.x + bounds.width;
|
|
10900
|
+
break;
|
|
10901
|
+
default:
|
|
10902
|
+
anchor = "middle";
|
|
10903
|
+
xBase = bounds.x + bounds.width / 2;
|
|
10904
|
+
break;
|
|
10905
|
+
}
|
|
10906
|
+
let yStart;
|
|
10907
|
+
switch (this._verticalAlign) {
|
|
10908
|
+
case "top":
|
|
10909
|
+
yStart = bounds.y + lineHeightPx * 0.75;
|
|
10910
|
+
break;
|
|
10911
|
+
case "bottom":
|
|
10912
|
+
yStart = bounds.y + bounds.height - totalTextHeight + lineHeightPx * 0.75;
|
|
10913
|
+
break;
|
|
10914
|
+
default:
|
|
10915
|
+
yStart = bounds.y + (bounds.height - totalTextHeight) / 2 + lineHeightPx * 0.75;
|
|
10916
|
+
break;
|
|
10917
|
+
}
|
|
10918
|
+
const fontAttrs = [
|
|
10919
|
+
`font-family="${this._fontFamily}"`,
|
|
10920
|
+
`font-size="${this._fontSize}"`,
|
|
10921
|
+
this._fontWeight !== "normal" ? `font-weight="${this._fontWeight}"` : "",
|
|
10922
|
+
this._fontStyle !== "normal" ? `font-style="${this._fontStyle}"` : ""
|
|
10923
|
+
].filter(Boolean).join(" ");
|
|
10924
|
+
const transform = this._rotation !== 0 ? ` transform="rotate(${this._rotation}, ${bounds.x + bounds.width / 2}, ${bounds.y + bounds.height / 2})"` : "";
|
|
10925
|
+
const tspans = displayLines.map(
|
|
10926
|
+
(line, i) => `<tspan x="${xBase}" dy="${i === 0 ? 0 : lineHeightPx}">${escapeXml(line)}</tspan>`
|
|
10927
|
+
).join("");
|
|
10928
|
+
return `<text x="${xBase}" y="${yStart}" ${fontAttrs} fill="${this._color}" text-anchor="${anchor}"${transform}>${tspans}</text>`;
|
|
10929
|
+
}
|
|
10930
|
+
}
|
|
10931
|
+
function escapeXml(str) {
|
|
10932
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
10933
|
+
}
|
|
10934
|
+
const DEFAULT_ICON_SIZE = 24;
|
|
10935
|
+
const svgFetchCache = new LRUCache(100);
|
|
10936
|
+
class CIcon {
|
|
10937
|
+
constructor(options) {
|
|
10938
|
+
this.type = "icon";
|
|
10939
|
+
this._loaded = false;
|
|
10940
|
+
this.id = options.id;
|
|
10941
|
+
this._source = options.source;
|
|
10942
|
+
this._width = options.width ?? DEFAULT_ICON_SIZE;
|
|
10943
|
+
this._height = options.height ?? DEFAULT_ICON_SIZE;
|
|
10944
|
+
this._backgroundColor = options.backgroundColor;
|
|
10945
|
+
this._fillColor = options.fillColor;
|
|
10946
|
+
this._bindsNotationIcon = options.bindsNotationIcon ?? false;
|
|
10947
|
+
this.onClick = options.onClick;
|
|
10948
|
+
this.style = options.style ?? {};
|
|
10949
|
+
if (options.visible === false) {
|
|
10950
|
+
this.style.visible = false;
|
|
10951
|
+
}
|
|
10952
|
+
this._image = new Image();
|
|
10953
|
+
this._image.onload = () => {
|
|
10954
|
+
this._loaded = true;
|
|
10955
|
+
this._onChange?.();
|
|
10956
|
+
};
|
|
10957
|
+
this._image.onerror = () => {
|
|
10958
|
+
this._onChange?.();
|
|
10959
|
+
};
|
|
10960
|
+
void this.loadSource(this._source);
|
|
10961
|
+
}
|
|
10962
|
+
get source() {
|
|
10963
|
+
return this._source;
|
|
10964
|
+
}
|
|
10965
|
+
set source(value) {
|
|
10966
|
+
if (this._source !== value) {
|
|
10967
|
+
this._source = value;
|
|
10968
|
+
this._loaded = false;
|
|
10969
|
+
void this.loadSource(value);
|
|
10970
|
+
this._onChange?.();
|
|
10971
|
+
}
|
|
10972
|
+
}
|
|
10973
|
+
get width() {
|
|
10974
|
+
return this._width;
|
|
10975
|
+
}
|
|
10976
|
+
set width(value) {
|
|
10977
|
+
if (this._width !== value) {
|
|
10978
|
+
this._width = value;
|
|
10979
|
+
this._onChange?.();
|
|
10980
|
+
}
|
|
10981
|
+
}
|
|
10982
|
+
get height() {
|
|
10983
|
+
return this._height;
|
|
10984
|
+
}
|
|
10985
|
+
set height(value) {
|
|
10986
|
+
if (this._height !== value) {
|
|
10987
|
+
this._height = value;
|
|
10988
|
+
this._onChange?.();
|
|
10989
|
+
}
|
|
10990
|
+
}
|
|
10991
|
+
get backgroundColor() {
|
|
10992
|
+
return this._backgroundColor;
|
|
10993
|
+
}
|
|
10994
|
+
set backgroundColor(value) {
|
|
10995
|
+
if (this._backgroundColor !== value) {
|
|
10996
|
+
this._backgroundColor = value;
|
|
10997
|
+
this._onChange?.();
|
|
10998
|
+
}
|
|
10999
|
+
}
|
|
11000
|
+
get fillColor() {
|
|
11001
|
+
return this._fillColor;
|
|
11002
|
+
}
|
|
11003
|
+
set fillColor(value) {
|
|
11004
|
+
if (this._fillColor !== value) {
|
|
11005
|
+
this._fillColor = value;
|
|
11006
|
+
void this.loadSource(this._source);
|
|
11007
|
+
this._onChange?.();
|
|
11008
|
+
}
|
|
11009
|
+
}
|
|
11010
|
+
get loaded() {
|
|
11011
|
+
return this._loaded;
|
|
11012
|
+
}
|
|
11013
|
+
get bindsNotationIcon() {
|
|
11014
|
+
return this._bindsNotationIcon;
|
|
11015
|
+
}
|
|
11016
|
+
set bindsNotationIcon(value) {
|
|
11017
|
+
if (this._bindsNotationIcon !== value) {
|
|
11018
|
+
this._bindsNotationIcon = value;
|
|
11019
|
+
this._onChange?.();
|
|
11020
|
+
}
|
|
11021
|
+
}
|
|
11022
|
+
setOnChange(cb) {
|
|
11023
|
+
this._onChange = cb;
|
|
11024
|
+
}
|
|
11025
|
+
async loadSource(source) {
|
|
11026
|
+
if (isSvgMarkup(source)) {
|
|
11027
|
+
const tinted = this._fillColor ? tintSvg(source, void 0, this._fillColor) : source;
|
|
11028
|
+
this._image.src = svgToDataUrl(tinted);
|
|
11029
|
+
} else if (isSvgUrl(source) && this._fillColor) {
|
|
11030
|
+
try {
|
|
11031
|
+
const svgText = await this.fetchSvg(source);
|
|
11032
|
+
const tinted = tintSvg(svgText, void 0, this._fillColor);
|
|
11033
|
+
this._image.src = svgToDataUrl(tinted);
|
|
11034
|
+
} catch {
|
|
11035
|
+
this._image.src = source;
|
|
11036
|
+
}
|
|
11037
|
+
} else {
|
|
11038
|
+
this._image.src = source;
|
|
11039
|
+
}
|
|
11040
|
+
}
|
|
11041
|
+
async fetchSvg(url) {
|
|
11042
|
+
let promise = svgFetchCache.get(url);
|
|
11043
|
+
if (!promise) {
|
|
11044
|
+
promise = fetch(url).then((r) => r.text());
|
|
11045
|
+
svgFetchCache.set(url, promise);
|
|
11046
|
+
}
|
|
11047
|
+
return promise;
|
|
11048
|
+
}
|
|
11049
|
+
measure(_ctx) {
|
|
11050
|
+
return { width: this._width, height: this._height };
|
|
11051
|
+
}
|
|
11052
|
+
render(ctx, bounds) {
|
|
11053
|
+
if (this.style.visible === false) return;
|
|
11054
|
+
const opacity = this.style.opacity ?? 1;
|
|
11055
|
+
if (opacity <= 0) return;
|
|
11056
|
+
ctx.save();
|
|
11057
|
+
ctx.globalAlpha *= opacity;
|
|
11058
|
+
if (this._backgroundColor) {
|
|
11059
|
+
ctx.fillStyle = this._backgroundColor;
|
|
11060
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
11061
|
+
}
|
|
11062
|
+
if (this._loaded) {
|
|
11063
|
+
const drawWidth = Math.min(this._width, bounds.width);
|
|
11064
|
+
const drawHeight = Math.min(this._height, bounds.height);
|
|
11065
|
+
const dx = bounds.x + (bounds.width - drawWidth) / 2;
|
|
11066
|
+
const dy = bounds.y + (bounds.height - drawHeight) / 2;
|
|
11067
|
+
ctx.drawImage(this._image, dx, dy, drawWidth, drawHeight);
|
|
11068
|
+
}
|
|
11069
|
+
ctx.restore();
|
|
11070
|
+
}
|
|
11071
|
+
hitTest(point, bounds) {
|
|
11072
|
+
if (this.style.visible === false) return null;
|
|
11073
|
+
if (point.x >= bounds.x && point.x <= bounds.x + bounds.width && point.y >= bounds.y && point.y <= bounds.y + bounds.height) {
|
|
11074
|
+
return this;
|
|
11075
|
+
}
|
|
11076
|
+
return null;
|
|
11077
|
+
}
|
|
11078
|
+
serialize() {
|
|
11079
|
+
const data = {
|
|
11080
|
+
type: "icon",
|
|
11081
|
+
source: this._source,
|
|
11082
|
+
width: this._width,
|
|
11083
|
+
height: this._height
|
|
11084
|
+
};
|
|
11085
|
+
if (this.id !== void 0) data.id = this.id;
|
|
11086
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
11087
|
+
if (this._backgroundColor) data.backgroundColor = this._backgroundColor;
|
|
11088
|
+
if (this._fillColor) data.fillColor = this._fillColor;
|
|
11089
|
+
if (this._bindsNotationIcon) data.bindsNotationIcon = true;
|
|
11090
|
+
return data;
|
|
11091
|
+
}
|
|
11092
|
+
toSVG(bounds) {
|
|
11093
|
+
if (this.style.visible === false) return "";
|
|
11094
|
+
let svg = "";
|
|
11095
|
+
if (this._backgroundColor) {
|
|
11096
|
+
svg += `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}" fill="${this._backgroundColor}" />`;
|
|
11097
|
+
}
|
|
11098
|
+
const drawWidth = Math.min(this._width, bounds.width);
|
|
11099
|
+
const drawHeight = Math.min(this._height, bounds.height);
|
|
11100
|
+
const dx = bounds.x + (bounds.width - drawWidth) / 2;
|
|
11101
|
+
const dy = bounds.y + (bounds.height - drawHeight) / 2;
|
|
11102
|
+
svg += `<image href="${escapeXmlAttr(this._source)}" x="${dx}" y="${dy}" width="${drawWidth}" height="${drawHeight}" />`;
|
|
11103
|
+
return svg;
|
|
11104
|
+
}
|
|
11105
|
+
}
|
|
11106
|
+
function escapeXmlAttr(str) {
|
|
11107
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
11108
|
+
}
|
|
11109
|
+
const DEFAULT_COLOR = "#cccccc";
|
|
11110
|
+
const DEFAULT_THICKNESS = 1;
|
|
11111
|
+
class CDivider {
|
|
11112
|
+
constructor(options = {}) {
|
|
11113
|
+
this.type = "divider";
|
|
11114
|
+
this.id = options.id;
|
|
11115
|
+
this._color = options.color ?? DEFAULT_COLOR;
|
|
11116
|
+
this._thickness = options.thickness ?? DEFAULT_THICKNESS;
|
|
11117
|
+
this.style = options.style ?? {};
|
|
11118
|
+
}
|
|
11119
|
+
get color() {
|
|
11120
|
+
return this._color;
|
|
11121
|
+
}
|
|
11122
|
+
set color(value) {
|
|
11123
|
+
if (this._color !== value) {
|
|
11124
|
+
this._color = value;
|
|
11125
|
+
this._onChange?.();
|
|
11126
|
+
}
|
|
11127
|
+
}
|
|
11128
|
+
get thickness() {
|
|
11129
|
+
return this._thickness;
|
|
11130
|
+
}
|
|
11131
|
+
set thickness(value) {
|
|
11132
|
+
if (this._thickness !== value) {
|
|
11133
|
+
this._thickness = value;
|
|
11134
|
+
this._onChange?.();
|
|
11135
|
+
}
|
|
11136
|
+
}
|
|
11137
|
+
setOnChange(cb) {
|
|
11138
|
+
this._onChange = cb;
|
|
11139
|
+
}
|
|
11140
|
+
measure(_ctx) {
|
|
11141
|
+
return { width: this._thickness, height: this._thickness };
|
|
11142
|
+
}
|
|
11143
|
+
render(ctx, bounds) {
|
|
11144
|
+
if (this.style.visible === false) return;
|
|
11145
|
+
ctx.save();
|
|
11146
|
+
ctx.strokeStyle = this._color;
|
|
11147
|
+
ctx.lineWidth = this._thickness;
|
|
11148
|
+
ctx.globalAlpha *= this.style.opacity ?? 1;
|
|
11149
|
+
ctx.beginPath();
|
|
11150
|
+
if (bounds.width >= bounds.height) {
|
|
11151
|
+
const y = bounds.y + bounds.height / 2;
|
|
11152
|
+
ctx.moveTo(bounds.x, y);
|
|
11153
|
+
ctx.lineTo(bounds.x + bounds.width, y);
|
|
11154
|
+
} else {
|
|
11155
|
+
const x = bounds.x + bounds.width / 2;
|
|
11156
|
+
ctx.moveTo(x, bounds.y);
|
|
11157
|
+
ctx.lineTo(x, bounds.y + bounds.height);
|
|
11158
|
+
}
|
|
11159
|
+
ctx.stroke();
|
|
11160
|
+
ctx.restore();
|
|
11161
|
+
}
|
|
11162
|
+
hitTest(_point, _bounds) {
|
|
11163
|
+
return null;
|
|
11164
|
+
}
|
|
11165
|
+
serialize() {
|
|
11166
|
+
const data = { type: "divider" };
|
|
11167
|
+
if (this.id !== void 0) data.id = this.id;
|
|
11168
|
+
if (this._color !== DEFAULT_COLOR) data.color = this._color;
|
|
11169
|
+
if (this._thickness !== DEFAULT_THICKNESS) data.thickness = this._thickness;
|
|
11170
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
11171
|
+
return data;
|
|
11172
|
+
}
|
|
11173
|
+
toSVG(bounds) {
|
|
11174
|
+
if (this.style.visible === false) return "";
|
|
11175
|
+
if (bounds.width >= bounds.height) {
|
|
11176
|
+
const y = bounds.y + bounds.height / 2;
|
|
11177
|
+
return `<line x1="${bounds.x}" y1="${y}" x2="${bounds.x + bounds.width}" y2="${y}" stroke="${this._color}" stroke-width="${this._thickness}" />`;
|
|
11178
|
+
} else {
|
|
11179
|
+
const x = bounds.x + bounds.width / 2;
|
|
11180
|
+
return `<line x1="${x}" y1="${bounds.y}" x2="${x}" y2="${bounds.y + bounds.height}" stroke="${this._color}" stroke-width="${this._thickness}" />`;
|
|
11181
|
+
}
|
|
11182
|
+
}
|
|
11183
|
+
}
|
|
11184
|
+
function normalizeSides(value, fallback = 0) {
|
|
11185
|
+
if (value === void 0) {
|
|
11186
|
+
return { top: fallback, right: fallback, bottom: fallback, left: fallback };
|
|
11187
|
+
}
|
|
11188
|
+
if (typeof value === "number") {
|
|
11189
|
+
return { top: value, right: value, bottom: value, left: value };
|
|
11190
|
+
}
|
|
11191
|
+
return {
|
|
11192
|
+
top: value.top ?? fallback,
|
|
11193
|
+
right: value.right ?? fallback,
|
|
11194
|
+
bottom: value.bottom ?? fallback,
|
|
11195
|
+
left: value.left ?? fallback
|
|
11196
|
+
};
|
|
11197
|
+
}
|
|
11198
|
+
function flexLayout(container2, config, children) {
|
|
11199
|
+
const pad = normalizeSides(config.padding);
|
|
11200
|
+
const isRow = config.direction === "row";
|
|
11201
|
+
const innerWidth = Math.max(0, container2.width - pad.left - pad.right);
|
|
11202
|
+
const innerHeight = Math.max(0, container2.height - pad.top - pad.bottom);
|
|
11203
|
+
const availableMain = isRow ? innerWidth : innerHeight;
|
|
11204
|
+
const availableCross = isRow ? innerHeight : innerWidth;
|
|
11205
|
+
if (children.length === 0) {
|
|
11206
|
+
return {
|
|
11207
|
+
childBounds: [],
|
|
11208
|
+
contentSize: { width: pad.left + pad.right, height: pad.top + pad.bottom }
|
|
11209
|
+
};
|
|
11210
|
+
}
|
|
11211
|
+
const baseSizes = [];
|
|
11212
|
+
const mainMargins = [];
|
|
11213
|
+
const crossMargins = [];
|
|
11214
|
+
for (const child of children) {
|
|
11215
|
+
const m = child.margin;
|
|
11216
|
+
if (isRow) {
|
|
11217
|
+
mainMargins.push({ before: m.left, after: m.right });
|
|
11218
|
+
crossMargins.push({ before: m.top, after: m.bottom });
|
|
11219
|
+
} else {
|
|
11220
|
+
mainMargins.push({ before: m.top, after: m.bottom });
|
|
11221
|
+
crossMargins.push({ before: m.left, after: m.right });
|
|
11222
|
+
}
|
|
11223
|
+
const measured = isRow ? child.measure.width : child.measure.height;
|
|
11224
|
+
const minMain = isRow ? child.minSize.width : child.minSize.height;
|
|
11225
|
+
const basis = child.flexBasis === "auto" ? measured : child.flexBasis;
|
|
11226
|
+
baseSizes.push(Math.max(basis, minMain));
|
|
11227
|
+
}
|
|
11228
|
+
const totalGaps = config.gap * Math.max(0, children.length - 1);
|
|
11229
|
+
let totalMargins = 0;
|
|
11230
|
+
for (const mm of mainMargins) {
|
|
11231
|
+
totalMargins += mm.before + mm.after;
|
|
11232
|
+
}
|
|
11233
|
+
let totalBase = 0;
|
|
11234
|
+
for (const s of baseSizes) {
|
|
11235
|
+
totalBase += s;
|
|
11236
|
+
}
|
|
11237
|
+
const totalUsed = totalBase + totalGaps + totalMargins;
|
|
11238
|
+
const finalSizes = [...baseSizes];
|
|
11239
|
+
const remaining = availableMain - totalUsed;
|
|
11240
|
+
if (remaining > 0) {
|
|
11241
|
+
let totalGrow = 0;
|
|
11242
|
+
for (const child of children) {
|
|
11243
|
+
totalGrow += child.flexGrow;
|
|
11244
|
+
}
|
|
11245
|
+
if (totalGrow > 0) {
|
|
11246
|
+
for (let i = 0; i < children.length; i++) {
|
|
11247
|
+
const child = children[i];
|
|
11248
|
+
finalSizes[i] = finalSizes[i] + remaining * child.flexGrow / totalGrow;
|
|
11249
|
+
}
|
|
11250
|
+
}
|
|
11251
|
+
} else if (remaining < 0) {
|
|
11252
|
+
let totalShrink = 0;
|
|
11253
|
+
for (let i = 0; i < children.length; i++) {
|
|
11254
|
+
totalShrink += children[i].flexShrink * baseSizes[i];
|
|
11255
|
+
}
|
|
11256
|
+
if (totalShrink > 0) {
|
|
11257
|
+
const deficit = -remaining;
|
|
11258
|
+
for (let i = 0; i < children.length; i++) {
|
|
11259
|
+
const child = children[i];
|
|
11260
|
+
const shrinkRatio = child.flexShrink * baseSizes[i] / totalShrink;
|
|
11261
|
+
const minMain = isRow ? child.minSize.width : child.minSize.height;
|
|
11262
|
+
finalSizes[i] = Math.max(minMain, finalSizes[i] - deficit * shrinkRatio);
|
|
11263
|
+
}
|
|
11264
|
+
}
|
|
11265
|
+
}
|
|
11266
|
+
let actualTotal = totalGaps + totalMargins;
|
|
11267
|
+
for (const s of finalSizes) {
|
|
11268
|
+
actualTotal += s;
|
|
11269
|
+
}
|
|
11270
|
+
const freeSpace = Math.max(0, availableMain - actualTotal);
|
|
11271
|
+
let mainOffset;
|
|
11272
|
+
let betweenExtra;
|
|
11273
|
+
switch (config.justifyContent) {
|
|
11274
|
+
case "center":
|
|
11275
|
+
mainOffset = freeSpace / 2;
|
|
11276
|
+
betweenExtra = 0;
|
|
11277
|
+
break;
|
|
11278
|
+
case "end":
|
|
11279
|
+
mainOffset = freeSpace;
|
|
11280
|
+
betweenExtra = 0;
|
|
11281
|
+
break;
|
|
11282
|
+
case "space-between":
|
|
11283
|
+
mainOffset = 0;
|
|
11284
|
+
betweenExtra = children.length > 1 ? freeSpace / (children.length - 1) : 0;
|
|
11285
|
+
break;
|
|
11286
|
+
case "space-around":
|
|
11287
|
+
betweenExtra = freeSpace / children.length;
|
|
11288
|
+
mainOffset = betweenExtra / 2;
|
|
11289
|
+
break;
|
|
11290
|
+
default:
|
|
11291
|
+
mainOffset = 0;
|
|
11292
|
+
betweenExtra = 0;
|
|
11293
|
+
break;
|
|
11294
|
+
}
|
|
11295
|
+
const childBounds = [];
|
|
11296
|
+
let cursor = mainOffset;
|
|
11297
|
+
let maxCrossContent = 0;
|
|
11298
|
+
for (let i = 0; i < children.length; i++) {
|
|
11299
|
+
const child = children[i];
|
|
11300
|
+
const mainSize = finalSizes[i];
|
|
11301
|
+
const mm = mainMargins[i];
|
|
11302
|
+
const cm = crossMargins[i];
|
|
11303
|
+
cursor += mm.before;
|
|
11304
|
+
const crossAvailable = availableCross - cm.before - cm.after;
|
|
11305
|
+
const measuredCross = isRow ? child.measure.height : child.measure.width;
|
|
11306
|
+
const align = child.alignSelf === "auto" ? config.alignItems : child.alignSelf;
|
|
11307
|
+
let crossSize;
|
|
11308
|
+
let crossOffset;
|
|
11309
|
+
if (align === "stretch") {
|
|
11310
|
+
crossSize = crossAvailable;
|
|
11311
|
+
crossOffset = cm.before;
|
|
11312
|
+
} else {
|
|
11313
|
+
crossSize = Math.min(measuredCross, crossAvailable);
|
|
11314
|
+
switch (align) {
|
|
11315
|
+
case "center":
|
|
11316
|
+
crossOffset = cm.before + (crossAvailable - crossSize) / 2;
|
|
11317
|
+
break;
|
|
11318
|
+
case "end":
|
|
11319
|
+
crossOffset = cm.before + crossAvailable - crossSize;
|
|
11320
|
+
break;
|
|
11321
|
+
default:
|
|
11322
|
+
crossOffset = cm.before;
|
|
11323
|
+
break;
|
|
11324
|
+
}
|
|
11325
|
+
}
|
|
11326
|
+
maxCrossContent = Math.max(maxCrossContent, crossOffset + crossSize + cm.after);
|
|
11327
|
+
if (isRow) {
|
|
11328
|
+
childBounds.push({
|
|
11329
|
+
x: pad.left + cursor,
|
|
11330
|
+
y: pad.top + crossOffset,
|
|
11331
|
+
width: mainSize,
|
|
11332
|
+
height: crossSize
|
|
11333
|
+
});
|
|
11334
|
+
} else {
|
|
11335
|
+
childBounds.push({
|
|
11336
|
+
x: pad.left + crossOffset,
|
|
11337
|
+
y: pad.top + cursor,
|
|
11338
|
+
width: crossSize,
|
|
11339
|
+
height: mainSize
|
|
11340
|
+
});
|
|
11341
|
+
}
|
|
11342
|
+
cursor += mainSize + mm.after;
|
|
11343
|
+
if (i < children.length - 1) {
|
|
11344
|
+
cursor += config.gap + betweenExtra;
|
|
11345
|
+
}
|
|
11346
|
+
}
|
|
11347
|
+
const mainContent = cursor;
|
|
11348
|
+
const contentWidth = isRow ? pad.left + mainContent + pad.right : pad.left + maxCrossContent + pad.right;
|
|
11349
|
+
const contentHeight = isRow ? pad.top + maxCrossContent + pad.bottom : pad.top + mainContent + pad.bottom;
|
|
11350
|
+
return {
|
|
11351
|
+
childBounds,
|
|
11352
|
+
contentSize: { width: contentWidth, height: contentHeight }
|
|
11353
|
+
};
|
|
11354
|
+
}
|
|
11355
|
+
class CContainer {
|
|
11356
|
+
constructor(options = {}) {
|
|
11357
|
+
this.type = "container";
|
|
11358
|
+
this._cachedBounds = null;
|
|
11359
|
+
this._cachedContainerBounds = null;
|
|
11360
|
+
this.id = options.id;
|
|
11361
|
+
this._direction = options.direction ?? "column";
|
|
11362
|
+
this._justifyContent = options.justifyContent ?? "start";
|
|
11363
|
+
this._alignItems = options.alignItems ?? "stretch";
|
|
11364
|
+
this._gap = options.gap ?? 0;
|
|
11365
|
+
this._padding = options.padding ?? 0;
|
|
11366
|
+
this._children = options.children ?? [];
|
|
11367
|
+
this.style = options.style ?? {};
|
|
11368
|
+
for (const child of this._children) {
|
|
11369
|
+
child.setOnChange(() => this.handleChildChange());
|
|
11370
|
+
}
|
|
11371
|
+
}
|
|
11372
|
+
get direction() {
|
|
11373
|
+
return this._direction;
|
|
11374
|
+
}
|
|
11375
|
+
get justifyContent() {
|
|
11376
|
+
return this._justifyContent;
|
|
11377
|
+
}
|
|
11378
|
+
get alignItems() {
|
|
11379
|
+
return this._alignItems;
|
|
11380
|
+
}
|
|
11381
|
+
get gap() {
|
|
11382
|
+
return this._gap;
|
|
11383
|
+
}
|
|
11384
|
+
get padding() {
|
|
11385
|
+
return this._padding;
|
|
11386
|
+
}
|
|
11387
|
+
get children() {
|
|
11388
|
+
return this._children;
|
|
11389
|
+
}
|
|
11390
|
+
addChild(child) {
|
|
11391
|
+
child.setOnChange(() => this.handleChildChange());
|
|
11392
|
+
this._children.push(child);
|
|
11393
|
+
this.handleChildChange();
|
|
11394
|
+
}
|
|
11395
|
+
removeChild(index) {
|
|
11396
|
+
const removed = this._children.splice(index, 1)[0];
|
|
11397
|
+
if (removed) {
|
|
11398
|
+
removed.setOnChange(void 0);
|
|
11399
|
+
this.handleChildChange();
|
|
11400
|
+
}
|
|
11401
|
+
return removed;
|
|
11402
|
+
}
|
|
11403
|
+
insertChild(index, child) {
|
|
11404
|
+
child.setOnChange(() => this.handleChildChange());
|
|
11405
|
+
this._children.splice(index, 0, child);
|
|
11406
|
+
this.handleChildChange();
|
|
11407
|
+
}
|
|
11408
|
+
setOnChange(cb) {
|
|
11409
|
+
this._onChange = cb;
|
|
11410
|
+
}
|
|
11411
|
+
handleChildChange() {
|
|
11412
|
+
this._cachedBounds = null;
|
|
11413
|
+
this._onChange?.();
|
|
11414
|
+
}
|
|
11415
|
+
getFlexConfig() {
|
|
11416
|
+
return {
|
|
11417
|
+
direction: this._direction,
|
|
11418
|
+
justifyContent: this._justifyContent,
|
|
11419
|
+
alignItems: this._alignItems,
|
|
11420
|
+
gap: this._gap,
|
|
11421
|
+
padding: this._padding
|
|
11422
|
+
};
|
|
11423
|
+
}
|
|
11424
|
+
buildFlexChildren(ctx) {
|
|
11425
|
+
return this._children.map((child) => {
|
|
11426
|
+
const s = child.style;
|
|
11427
|
+
const measured = child.measure(ctx);
|
|
11428
|
+
return {
|
|
11429
|
+
measure: measured,
|
|
11430
|
+
minSize: { width: 0, height: 0 },
|
|
11431
|
+
flexGrow: s.flexGrow ?? 0,
|
|
11432
|
+
flexShrink: s.flexShrink ?? 1,
|
|
11433
|
+
flexBasis: s.flexBasis ?? "auto",
|
|
11434
|
+
alignSelf: s.alignSelf ?? "auto",
|
|
11435
|
+
margin: normalizeSides(s.margin)
|
|
11436
|
+
};
|
|
11437
|
+
});
|
|
11438
|
+
}
|
|
11439
|
+
measure(ctx) {
|
|
11440
|
+
const flexChildren = this.buildFlexChildren(ctx);
|
|
11441
|
+
const measureConfig = {
|
|
11442
|
+
...this.getFlexConfig(),
|
|
11443
|
+
justifyContent: "start",
|
|
11444
|
+
alignItems: "start"
|
|
11445
|
+
};
|
|
11446
|
+
const measureChildren = flexChildren.map((c) => ({
|
|
11447
|
+
...c,
|
|
11448
|
+
flexGrow: 0,
|
|
11449
|
+
alignSelf: "start"
|
|
11450
|
+
}));
|
|
11451
|
+
const result = flexLayout(
|
|
11452
|
+
{ width: 1e5, height: 1e5 },
|
|
11453
|
+
measureConfig,
|
|
11454
|
+
measureChildren
|
|
11455
|
+
);
|
|
11456
|
+
return result.contentSize;
|
|
11457
|
+
}
|
|
11458
|
+
render(ctx, bounds) {
|
|
11459
|
+
if (this.style.visible === false) return;
|
|
11460
|
+
const flexChildren = this.buildFlexChildren(ctx);
|
|
11461
|
+
const result = flexLayout(
|
|
11462
|
+
{ width: bounds.width, height: bounds.height },
|
|
11463
|
+
this.getFlexConfig(),
|
|
11464
|
+
flexChildren
|
|
11465
|
+
);
|
|
11466
|
+
this._cachedBounds = result.childBounds;
|
|
11467
|
+
this._cachedContainerBounds = bounds;
|
|
11468
|
+
const opacity = this.style.opacity ?? 1;
|
|
11469
|
+
if (opacity < 1) {
|
|
11470
|
+
ctx.save();
|
|
11471
|
+
ctx.globalAlpha *= opacity;
|
|
11472
|
+
}
|
|
11473
|
+
for (let i = 0; i < this._children.length; i++) {
|
|
11474
|
+
const child = this._children[i];
|
|
11475
|
+
if (child.style.visible === false) continue;
|
|
11476
|
+
const cb = result.childBounds[i];
|
|
11477
|
+
child.render(ctx, {
|
|
11478
|
+
x: bounds.x + cb.x,
|
|
11479
|
+
y: bounds.y + cb.y,
|
|
11480
|
+
width: cb.width,
|
|
11481
|
+
height: cb.height
|
|
11482
|
+
});
|
|
11483
|
+
}
|
|
11484
|
+
if (opacity < 1) {
|
|
11485
|
+
ctx.restore();
|
|
11486
|
+
}
|
|
11487
|
+
}
|
|
11488
|
+
hitTest(point, bounds) {
|
|
11489
|
+
if (this.style.visible === false) return null;
|
|
11490
|
+
if (!this._cachedBounds) return null;
|
|
11491
|
+
for (let i = this._children.length - 1; i >= 0; i--) {
|
|
11492
|
+
const child = this._children[i];
|
|
11493
|
+
if (child.style.visible === false) continue;
|
|
11494
|
+
const cb = this._cachedBounds[i];
|
|
11495
|
+
const absBounds = {
|
|
11496
|
+
x: bounds.x + cb.x,
|
|
11497
|
+
y: bounds.y + cb.y,
|
|
11498
|
+
width: cb.width,
|
|
11499
|
+
height: cb.height
|
|
11500
|
+
};
|
|
11501
|
+
const hit = child.hitTest(point, absBounds);
|
|
11502
|
+
if (hit) return hit;
|
|
11503
|
+
}
|
|
11504
|
+
return null;
|
|
11505
|
+
}
|
|
11506
|
+
/**
|
|
11507
|
+
* Find a component by id recursively.
|
|
11508
|
+
*/
|
|
11509
|
+
findById(id) {
|
|
11510
|
+
for (const child of this._children) {
|
|
11511
|
+
if (child.id === id) return child;
|
|
11512
|
+
if (child.type === "container") {
|
|
11513
|
+
const found = child.findById(id);
|
|
11514
|
+
if (found) return found;
|
|
11515
|
+
}
|
|
11516
|
+
if (child.type === "shape") {
|
|
11517
|
+
const content = child.content;
|
|
11518
|
+
if (content) {
|
|
11519
|
+
const found = content.findById(id);
|
|
11520
|
+
if (found) return found;
|
|
11521
|
+
}
|
|
11522
|
+
}
|
|
11523
|
+
}
|
|
11524
|
+
return void 0;
|
|
11525
|
+
}
|
|
11526
|
+
serialize() {
|
|
11527
|
+
const data = {
|
|
11528
|
+
type: "container",
|
|
11529
|
+
direction: this._direction,
|
|
11530
|
+
children: this._children.map((c) => c.serialize())
|
|
11531
|
+
};
|
|
11532
|
+
if (this.id !== void 0) data.id = this.id;
|
|
11533
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
11534
|
+
if (this._justifyContent !== "start") data.justifyContent = this._justifyContent;
|
|
11535
|
+
if (this._alignItems !== "stretch") data.alignItems = this._alignItems;
|
|
11536
|
+
if (this._gap !== 0) data.gap = this._gap;
|
|
11537
|
+
if (typeof this._padding === "number" && this._padding !== 0 || typeof this._padding === "object" && Object.values(this._padding).some((v) => v !== void 0 && v !== 0)) {
|
|
11538
|
+
data.padding = this._padding;
|
|
11539
|
+
}
|
|
11540
|
+
return data;
|
|
11541
|
+
}
|
|
11542
|
+
toSVG(bounds) {
|
|
11543
|
+
if (this.style.visible === false) return "";
|
|
11544
|
+
const cachedBounds = this._cachedBounds;
|
|
11545
|
+
const cachedContainer = this._cachedContainerBounds;
|
|
11546
|
+
const childSvg = this._children.map((child, i) => {
|
|
11547
|
+
if (child.style.visible === false) return "";
|
|
11548
|
+
if (cachedBounds && cachedContainer) {
|
|
11549
|
+
const cb = cachedBounds[i];
|
|
11550
|
+
const dx = bounds.x - cachedContainer.x;
|
|
11551
|
+
const dy = bounds.y - cachedContainer.y;
|
|
11552
|
+
return child.toSVG({
|
|
11553
|
+
x: cachedContainer.x + cb.x + dx,
|
|
11554
|
+
y: cachedContainer.y + cb.y + dy,
|
|
11555
|
+
width: cb.width,
|
|
11556
|
+
height: cb.height
|
|
11557
|
+
});
|
|
11558
|
+
}
|
|
11559
|
+
const h = bounds.height / Math.max(1, this._children.length);
|
|
11560
|
+
return child.toSVG({
|
|
11561
|
+
x: bounds.x,
|
|
11562
|
+
y: bounds.y + i * h,
|
|
11563
|
+
width: bounds.width,
|
|
11564
|
+
height: h
|
|
11565
|
+
});
|
|
11566
|
+
}).join("");
|
|
11567
|
+
return `<g>${childSvg}</g>`;
|
|
11568
|
+
}
|
|
11569
|
+
}
|
|
11570
|
+
class CShape {
|
|
11571
|
+
constructor(options = {}) {
|
|
11572
|
+
this.type = "shape";
|
|
11573
|
+
this.id = options.id;
|
|
11574
|
+
this._borderColor = options.borderColor;
|
|
11575
|
+
this._borderWidth = options.borderWidth ?? 0;
|
|
11576
|
+
this._backgroundColor = options.backgroundColor;
|
|
11577
|
+
this._cornerRadius = options.cornerRadius ?? 0;
|
|
11578
|
+
this._padding = options.padding ?? 0;
|
|
11579
|
+
this.onClick = options.onClick;
|
|
11580
|
+
this._content = options.content;
|
|
11581
|
+
this.style = options.style ?? {};
|
|
11582
|
+
if (this._content) {
|
|
11583
|
+
this._content.setOnChange(() => this._onChange?.());
|
|
11584
|
+
}
|
|
11585
|
+
}
|
|
11586
|
+
get borderColor() {
|
|
11587
|
+
return this._borderColor;
|
|
11588
|
+
}
|
|
11589
|
+
set borderColor(value) {
|
|
11590
|
+
if (this._borderColor !== value) {
|
|
11591
|
+
this._borderColor = value;
|
|
11592
|
+
this._onChange?.();
|
|
11593
|
+
}
|
|
11594
|
+
}
|
|
11595
|
+
get borderWidth() {
|
|
11596
|
+
return this._borderWidth;
|
|
11597
|
+
}
|
|
11598
|
+
set borderWidth(value) {
|
|
11599
|
+
if (this._borderWidth !== value) {
|
|
11600
|
+
this._borderWidth = value;
|
|
11601
|
+
this._onChange?.();
|
|
11602
|
+
}
|
|
11603
|
+
}
|
|
11604
|
+
get backgroundColor() {
|
|
11605
|
+
return this._backgroundColor;
|
|
11606
|
+
}
|
|
11607
|
+
set backgroundColor(value) {
|
|
11608
|
+
if (this._backgroundColor !== value) {
|
|
11609
|
+
this._backgroundColor = value;
|
|
11610
|
+
this._onChange?.();
|
|
11611
|
+
}
|
|
11612
|
+
}
|
|
11613
|
+
get cornerRadius() {
|
|
11614
|
+
return this._cornerRadius;
|
|
11615
|
+
}
|
|
11616
|
+
get padding() {
|
|
11617
|
+
return this._padding;
|
|
11618
|
+
}
|
|
11619
|
+
get content() {
|
|
11620
|
+
return this._content;
|
|
11621
|
+
}
|
|
11622
|
+
setOnChange(cb) {
|
|
11623
|
+
this._onChange = cb;
|
|
11624
|
+
}
|
|
11625
|
+
measure(ctx) {
|
|
11626
|
+
const pad = normalizeSides(this._padding);
|
|
11627
|
+
const bw = this._borderWidth;
|
|
11628
|
+
if (this._content) {
|
|
11629
|
+
const contentSize = this._content.measure(ctx);
|
|
11630
|
+
return {
|
|
11631
|
+
width: contentSize.width + pad.left + pad.right + bw * 2,
|
|
11632
|
+
height: contentSize.height + pad.top + pad.bottom + bw * 2
|
|
11633
|
+
};
|
|
11634
|
+
}
|
|
11635
|
+
return {
|
|
11636
|
+
width: pad.left + pad.right + bw * 2,
|
|
11637
|
+
height: pad.top + pad.bottom + bw * 2
|
|
11638
|
+
};
|
|
11639
|
+
}
|
|
11640
|
+
render(ctx, bounds) {
|
|
11641
|
+
if (this.style.visible === false) return;
|
|
11642
|
+
ctx.save();
|
|
11643
|
+
ctx.globalAlpha *= this.style.opacity ?? 1;
|
|
11644
|
+
const { x, y, width, height } = bounds;
|
|
11645
|
+
if (this._backgroundColor) {
|
|
11646
|
+
ctx.fillStyle = this._backgroundColor;
|
|
11647
|
+
if (this._cornerRadius > 0 && ctx.roundRect) {
|
|
11648
|
+
ctx.beginPath();
|
|
11649
|
+
ctx.roundRect(x, y, width, height, this._cornerRadius);
|
|
11650
|
+
ctx.fill();
|
|
11651
|
+
} else {
|
|
11652
|
+
ctx.fillRect(x, y, width, height);
|
|
11653
|
+
}
|
|
11654
|
+
}
|
|
11655
|
+
if (this._borderColor && this._borderWidth > 0) {
|
|
11656
|
+
ctx.strokeStyle = this._borderColor;
|
|
11657
|
+
ctx.lineWidth = this._borderWidth;
|
|
11658
|
+
if (this._cornerRadius > 0 && ctx.roundRect) {
|
|
11659
|
+
ctx.beginPath();
|
|
11660
|
+
ctx.roundRect(x, y, width, height, this._cornerRadius);
|
|
11661
|
+
ctx.stroke();
|
|
11662
|
+
} else {
|
|
11663
|
+
ctx.strokeRect(x, y, width, height);
|
|
11664
|
+
}
|
|
11665
|
+
}
|
|
11666
|
+
if (this._content) {
|
|
11667
|
+
const pad = normalizeSides(this._padding);
|
|
11668
|
+
const bw = this._borderWidth;
|
|
11669
|
+
this._content.render(ctx, {
|
|
11670
|
+
x: x + pad.left + bw,
|
|
11671
|
+
y: y + pad.top + bw,
|
|
11672
|
+
width: Math.max(0, width - pad.left - pad.right - bw * 2),
|
|
11673
|
+
height: Math.max(0, height - pad.top - pad.bottom - bw * 2)
|
|
11674
|
+
});
|
|
11675
|
+
}
|
|
11676
|
+
ctx.restore();
|
|
11677
|
+
}
|
|
11678
|
+
hitTest(point, bounds) {
|
|
11679
|
+
if (this.style.visible === false) return null;
|
|
11680
|
+
if (point.x < bounds.x || point.x > bounds.x + bounds.width || point.y < bounds.y || point.y > bounds.y + bounds.height) {
|
|
11681
|
+
return null;
|
|
11682
|
+
}
|
|
11683
|
+
if (this._content) {
|
|
11684
|
+
const pad = normalizeSides(this._padding);
|
|
11685
|
+
const bw = this._borderWidth;
|
|
11686
|
+
const contentBounds = {
|
|
11687
|
+
x: bounds.x + pad.left + bw,
|
|
11688
|
+
y: bounds.y + pad.top + bw,
|
|
11689
|
+
width: Math.max(0, bounds.width - pad.left - pad.right - bw * 2),
|
|
11690
|
+
height: Math.max(0, bounds.height - pad.top - pad.bottom - bw * 2)
|
|
11691
|
+
};
|
|
11692
|
+
const hit = this._content.hitTest(point, contentBounds);
|
|
11693
|
+
if (hit) return hit;
|
|
11694
|
+
}
|
|
11695
|
+
return this;
|
|
11696
|
+
}
|
|
11697
|
+
serialize() {
|
|
11698
|
+
const data = { type: "shape" };
|
|
11699
|
+
if (this.id !== void 0) data.id = this.id;
|
|
11700
|
+
if (this.style && Object.keys(this.style).length > 0) data.style = this.style;
|
|
11701
|
+
if (this._borderColor) data.borderColor = this._borderColor;
|
|
11702
|
+
if (this._borderWidth !== 0) data.borderWidth = this._borderWidth;
|
|
11703
|
+
if (this._backgroundColor) data.backgroundColor = this._backgroundColor;
|
|
11704
|
+
if (this._cornerRadius !== 0) data.cornerRadius = this._cornerRadius;
|
|
11705
|
+
if (typeof this._padding === "number" && this._padding !== 0 || typeof this._padding === "object" && Object.values(this._padding).some((v) => v !== void 0 && v !== 0)) {
|
|
11706
|
+
data.padding = this._padding;
|
|
11707
|
+
}
|
|
11708
|
+
if (this._content) {
|
|
11709
|
+
data.content = this._content.serialize();
|
|
11710
|
+
}
|
|
11711
|
+
return data;
|
|
11712
|
+
}
|
|
11713
|
+
toSVG(bounds) {
|
|
11714
|
+
if (this.style.visible === false) return "";
|
|
11715
|
+
let svg = "";
|
|
11716
|
+
const { x, y, width, height } = bounds;
|
|
11717
|
+
const hasFill = !!this._backgroundColor;
|
|
11718
|
+
const hasStroke = !!this._borderColor && this._borderWidth > 0;
|
|
11719
|
+
if (hasFill || hasStroke) {
|
|
11720
|
+
const fill = hasFill ? `fill="${this._backgroundColor}"` : 'fill="none"';
|
|
11721
|
+
const stroke = hasStroke ? `stroke="${this._borderColor}" stroke-width="${this._borderWidth}"` : "";
|
|
11722
|
+
const rx = this._cornerRadius > 0 ? ` rx="${this._cornerRadius}"` : "";
|
|
11723
|
+
svg += `<rect x="${x}" y="${y}" width="${width}" height="${height}" ${fill} ${stroke}${rx} />`;
|
|
11724
|
+
}
|
|
11725
|
+
if (this._content) {
|
|
11726
|
+
const pad = normalizeSides(this._padding);
|
|
11727
|
+
const bw = this._borderWidth;
|
|
11728
|
+
svg += this._content.toSVG({
|
|
11729
|
+
x: x + pad.left + bw,
|
|
11730
|
+
y: y + pad.top + bw,
|
|
11731
|
+
width: Math.max(0, width - pad.left - pad.right - bw * 2),
|
|
11732
|
+
height: Math.max(0, height - pad.top - pad.bottom - bw * 2)
|
|
11733
|
+
});
|
|
11734
|
+
}
|
|
11735
|
+
return svg;
|
|
11736
|
+
}
|
|
11737
|
+
}
|
|
11738
|
+
function deserializeCComponent(data) {
|
|
11739
|
+
switch (data.type) {
|
|
11740
|
+
case "text":
|
|
11741
|
+
return new CText({
|
|
11742
|
+
id: data.id,
|
|
11743
|
+
text: data.text ?? "",
|
|
11744
|
+
fontFamily: data.fontFamily,
|
|
11745
|
+
fontWeight: data.fontWeight,
|
|
11746
|
+
fontStyle: data.fontStyle,
|
|
11747
|
+
fontSize: data.fontSize,
|
|
11748
|
+
color: data.color,
|
|
11749
|
+
align: data.align,
|
|
11750
|
+
verticalAlign: data.verticalAlign,
|
|
11751
|
+
maxLines: data.maxLines,
|
|
11752
|
+
lineHeight: data.lineHeight,
|
|
11753
|
+
role: data.role,
|
|
11754
|
+
bindToProperty: data.bindToProperty,
|
|
11755
|
+
rotation: data.rotation,
|
|
11756
|
+
style: data.style
|
|
11757
|
+
});
|
|
11758
|
+
case "icon":
|
|
11759
|
+
return new CIcon({
|
|
11760
|
+
id: data.id,
|
|
11761
|
+
source: data.source ?? "",
|
|
11762
|
+
width: data.width,
|
|
11763
|
+
height: data.height,
|
|
11764
|
+
backgroundColor: data.backgroundColor,
|
|
11765
|
+
fillColor: data.fillColor,
|
|
11766
|
+
bindsNotationIcon: data.bindsNotationIcon === true,
|
|
11767
|
+
style: data.style
|
|
11768
|
+
});
|
|
11769
|
+
case "divider":
|
|
11770
|
+
return new CDivider({
|
|
11771
|
+
id: data.id,
|
|
11772
|
+
color: data.color,
|
|
11773
|
+
thickness: data.thickness,
|
|
11774
|
+
style: data.style
|
|
11775
|
+
});
|
|
11776
|
+
case "container":
|
|
11777
|
+
return new CContainer({
|
|
11778
|
+
id: data.id,
|
|
11779
|
+
direction: data.direction,
|
|
11780
|
+
justifyContent: data.justifyContent,
|
|
11781
|
+
alignItems: data.alignItems,
|
|
11782
|
+
gap: data.gap,
|
|
11783
|
+
padding: data.padding,
|
|
11784
|
+
children: data.children?.map(deserializeCComponent),
|
|
11785
|
+
style: data.style
|
|
11786
|
+
});
|
|
11787
|
+
case "shape":
|
|
11788
|
+
return new CShape({
|
|
11789
|
+
id: data.id,
|
|
11790
|
+
borderColor: data.borderColor,
|
|
11791
|
+
borderWidth: data.borderWidth,
|
|
11792
|
+
backgroundColor: data.backgroundColor,
|
|
11793
|
+
cornerRadius: data.cornerRadius,
|
|
11794
|
+
padding: data.padding,
|
|
11795
|
+
content: data.content ? deserializeCComponent(data.content) : void 0,
|
|
11796
|
+
style: data.style
|
|
11797
|
+
});
|
|
11798
|
+
default:
|
|
11799
|
+
throw new Error(`Unknown component type: ${String(data.type)}`);
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
class CompositeNode extends Node {
|
|
11803
|
+
constructor(options) {
|
|
11804
|
+
super(options);
|
|
11805
|
+
this._content = options.content;
|
|
11806
|
+
this._shapeType = options.shapeType ?? "rectangle";
|
|
11807
|
+
this._cornerRadius = options.cornerRadius ?? 0;
|
|
11808
|
+
this._pathFactory = options.pathFactory;
|
|
11809
|
+
this._autoSize = options.autoSize ?? false;
|
|
11810
|
+
this._minWidth = options.minWidth ?? 0;
|
|
11811
|
+
this._minHeight = options.minHeight ?? 0;
|
|
11812
|
+
this._content.setOnChange(() => this.markDirty());
|
|
11813
|
+
}
|
|
11814
|
+
get typeName() {
|
|
11815
|
+
return "composite";
|
|
11816
|
+
}
|
|
11817
|
+
get content() {
|
|
11818
|
+
return this._content;
|
|
11819
|
+
}
|
|
11820
|
+
get shapeType() {
|
|
11821
|
+
return this._shapeType;
|
|
11822
|
+
}
|
|
11823
|
+
get cornerRadius() {
|
|
11824
|
+
return this._cornerRadius;
|
|
11825
|
+
}
|
|
11826
|
+
get autoSize() {
|
|
11827
|
+
return this._autoSize;
|
|
11828
|
+
}
|
|
11829
|
+
set autoSize(value) {
|
|
11830
|
+
if (this._autoSize !== value) {
|
|
11831
|
+
this._autoSize = value;
|
|
11832
|
+
this.markDirty();
|
|
11833
|
+
}
|
|
11834
|
+
}
|
|
11835
|
+
get minWidth() {
|
|
11836
|
+
return this._minWidth;
|
|
11837
|
+
}
|
|
11838
|
+
get minHeight() {
|
|
11839
|
+
return this._minHeight;
|
|
11840
|
+
}
|
|
11841
|
+
/**
|
|
11842
|
+
* Find a component by id in the content tree.
|
|
11843
|
+
*/
|
|
11844
|
+
getComponent(id) {
|
|
11845
|
+
if (this._content.id === id) return this._content;
|
|
11846
|
+
return this._content.findById(id);
|
|
11847
|
+
}
|
|
11848
|
+
/**
|
|
11849
|
+
* Hit test the component tree. Returns the deepest component at the given world point.
|
|
11850
|
+
*/
|
|
11851
|
+
getComponentAtPoint(worldPoint) {
|
|
11852
|
+
const bounds = this.getContentBounds();
|
|
11853
|
+
return this._content.hitTest(worldPoint, bounds);
|
|
11854
|
+
}
|
|
11855
|
+
getContentBounds() {
|
|
11856
|
+
return this.getLabelContainerBounds(this.getBounds());
|
|
11857
|
+
}
|
|
11858
|
+
// --- Shape rendering ---
|
|
11859
|
+
hitTest(point) {
|
|
11860
|
+
if (this._shapeType === "circle") {
|
|
11861
|
+
return this.hitTestEllipse(point);
|
|
11862
|
+
}
|
|
11863
|
+
if (this._shapeType === "diamond") {
|
|
11864
|
+
return this.hitTestDiamond(point);
|
|
11865
|
+
}
|
|
11866
|
+
return super.hitTest(point);
|
|
11867
|
+
}
|
|
11868
|
+
hitTestEllipse(point) {
|
|
11869
|
+
const center = this.getCenter();
|
|
11870
|
+
const padding = NODE_HITBOX_PADDING;
|
|
11871
|
+
const rx = this._width / 2 + padding;
|
|
11872
|
+
const ry = this._height / 2 + padding;
|
|
11873
|
+
const dx = point.x - center.x;
|
|
11874
|
+
const dy = point.y - center.y;
|
|
11875
|
+
return dx * dx / (rx * rx) + dy * dy / (ry * ry) <= 1;
|
|
11876
|
+
}
|
|
11877
|
+
hitTestDiamond(point) {
|
|
11878
|
+
const center = this.getCenter();
|
|
11879
|
+
const padding = NODE_HITBOX_PADDING;
|
|
11880
|
+
const hw = this._width / 2 + padding;
|
|
11881
|
+
const hh = this._height / 2 + padding;
|
|
11882
|
+
const dx = Math.abs(point.x - center.x);
|
|
11883
|
+
const dy = Math.abs(point.y - center.y);
|
|
11884
|
+
return dx / hw + dy / hh <= 1;
|
|
11885
|
+
}
|
|
11886
|
+
render(ctx) {
|
|
11887
|
+
if (this._autoSize) {
|
|
11888
|
+
this.applyAutoSize(ctx);
|
|
11889
|
+
}
|
|
11890
|
+
const { x, y, width, height } = this.getBounds();
|
|
11891
|
+
const style = this.style;
|
|
11892
|
+
const baseOpacity = style.opacity ?? 1;
|
|
11893
|
+
const fillOpacity = style.fillOpacity ?? 1;
|
|
11894
|
+
const strokeOpacity = style.strokeOpacity ?? 1;
|
|
11895
|
+
this.applyStyle(ctx);
|
|
11896
|
+
ctx.beginPath();
|
|
11897
|
+
this.buildShapePath(ctx, x, y, width, height);
|
|
11898
|
+
ctx.closePath();
|
|
11899
|
+
ctx.globalAlpha = baseOpacity * fillOpacity;
|
|
11900
|
+
ctx.fill();
|
|
11901
|
+
ctx.globalAlpha = baseOpacity * strokeOpacity;
|
|
11902
|
+
ctx.stroke();
|
|
11903
|
+
ctx.globalAlpha = 1;
|
|
11904
|
+
this.renderContents(ctx);
|
|
11905
|
+
}
|
|
11906
|
+
buildShapePath(ctx, x, y, w, h) {
|
|
11907
|
+
switch (this._shapeType) {
|
|
11908
|
+
case "circle": {
|
|
11909
|
+
const cx = x + w / 2;
|
|
11910
|
+
const cy = y + h / 2;
|
|
11911
|
+
ctx.ellipse(cx, cy, w / 2, h / 2, 0, 0, Math.PI * 2);
|
|
11912
|
+
break;
|
|
11913
|
+
}
|
|
11914
|
+
case "diamond": {
|
|
11915
|
+
const cx = x + w / 2;
|
|
11916
|
+
const cy = y + h / 2;
|
|
11917
|
+
ctx.moveTo(cx, y);
|
|
11918
|
+
ctx.lineTo(x + w, cy);
|
|
11919
|
+
ctx.lineTo(cx, y + h);
|
|
11920
|
+
ctx.lineTo(x, cy);
|
|
11921
|
+
break;
|
|
11922
|
+
}
|
|
11923
|
+
case "custom": {
|
|
11924
|
+
if (this._pathFactory) {
|
|
11925
|
+
const path = this._pathFactory(w, h);
|
|
11926
|
+
ctx.save();
|
|
11927
|
+
ctx.translate(x, y);
|
|
11928
|
+
ctx.fill(path);
|
|
11929
|
+
ctx.stroke(path);
|
|
11930
|
+
ctx.restore();
|
|
11931
|
+
return;
|
|
11932
|
+
}
|
|
11933
|
+
this.buildRectPath(ctx, x, y, w, h);
|
|
11934
|
+
break;
|
|
11935
|
+
}
|
|
11936
|
+
default:
|
|
11937
|
+
this.buildRectPath(ctx, x, y, w, h);
|
|
11938
|
+
break;
|
|
11939
|
+
}
|
|
11940
|
+
}
|
|
11941
|
+
buildRectPath(ctx, x, y, w, h) {
|
|
11942
|
+
const radius = Math.min(this._cornerRadius, w / 2, h / 2);
|
|
11943
|
+
if (radius > 0) {
|
|
11944
|
+
ctx.moveTo(x + radius, y);
|
|
11945
|
+
ctx.lineTo(x + w - radius, y);
|
|
11946
|
+
ctx.arcTo(x + w, y, x + w, y + radius, radius);
|
|
11947
|
+
ctx.lineTo(x + w, y + h - radius);
|
|
11948
|
+
ctx.arcTo(x + w, y + h, x + w - radius, y + h, radius);
|
|
11949
|
+
ctx.lineTo(x + radius, y + h);
|
|
11950
|
+
ctx.arcTo(x, y + h, x, y + h - radius, radius);
|
|
11951
|
+
ctx.lineTo(x, y + radius);
|
|
11952
|
+
ctx.arcTo(x, y, x + radius, y, radius);
|
|
11953
|
+
} else {
|
|
11954
|
+
ctx.rect(x, y, w, h);
|
|
11955
|
+
}
|
|
11956
|
+
}
|
|
11957
|
+
// --- Content rendering (overrides Node.renderContents) ---
|
|
11958
|
+
renderContents(ctx) {
|
|
11959
|
+
ctx.setLineDash([]);
|
|
11960
|
+
ctx.lineDashOffset = 0;
|
|
11961
|
+
const contentBounds = this.getContentBounds();
|
|
11962
|
+
this._content.render(ctx, contentBounds);
|
|
11963
|
+
this.renderPorts(ctx);
|
|
11964
|
+
}
|
|
11965
|
+
applyAutoSize(ctx) {
|
|
11966
|
+
const contentSize = this._content.measure(ctx);
|
|
11967
|
+
const inset = this.contentInset;
|
|
11968
|
+
const neededWidth = Math.max(
|
|
11969
|
+
this._minWidth,
|
|
11970
|
+
contentSize.width + inset.left + inset.right
|
|
11971
|
+
);
|
|
11972
|
+
const neededHeight = Math.max(
|
|
11973
|
+
this._minHeight,
|
|
11974
|
+
contentSize.height + inset.top + inset.bottom
|
|
11975
|
+
);
|
|
11976
|
+
if (neededWidth > this._width) {
|
|
11977
|
+
this._width = neededWidth;
|
|
11978
|
+
}
|
|
11979
|
+
if (neededHeight > this._height) {
|
|
11980
|
+
this._height = neededHeight;
|
|
11981
|
+
}
|
|
11982
|
+
}
|
|
11983
|
+
// --- Outline methods for connections (delegate based on shapeType) ---
|
|
11984
|
+
getLabelContainerBounds(bounds) {
|
|
11985
|
+
if (this._shapeType === "circle") {
|
|
11986
|
+
const factor = 1 / Math.SQRT2;
|
|
11987
|
+
const w = bounds.width * factor;
|
|
11988
|
+
const h = bounds.height * factor;
|
|
11989
|
+
return {
|
|
11990
|
+
x: bounds.x + (bounds.width - w) / 2,
|
|
11991
|
+
y: bounds.y + (bounds.height - h) / 2,
|
|
11992
|
+
width: w,
|
|
11993
|
+
height: h
|
|
11994
|
+
};
|
|
11995
|
+
}
|
|
11996
|
+
if (this._shapeType === "diamond") {
|
|
11997
|
+
const w = bounds.width / 2;
|
|
11998
|
+
const h = bounds.height / 2;
|
|
11999
|
+
return {
|
|
12000
|
+
x: bounds.x + (bounds.width - w) / 2,
|
|
12001
|
+
y: bounds.y + (bounds.height - h) / 2,
|
|
12002
|
+
width: w,
|
|
12003
|
+
height: h
|
|
12004
|
+
};
|
|
12005
|
+
}
|
|
12006
|
+
const ci = this.contentInset;
|
|
12007
|
+
return {
|
|
12008
|
+
x: bounds.x + ci.left,
|
|
12009
|
+
y: bounds.y + ci.top,
|
|
12010
|
+
width: Math.max(0, bounds.width - ci.left - ci.right),
|
|
12011
|
+
height: Math.max(0, bounds.height - ci.top - ci.bottom)
|
|
12012
|
+
};
|
|
12013
|
+
}
|
|
12014
|
+
/**
|
|
12015
|
+
* Get the minimum content size needed for the content tree.
|
|
12016
|
+
* Useful for external auto-sizing logic.
|
|
12017
|
+
*/
|
|
12018
|
+
getContentMinSize(ctx) {
|
|
12019
|
+
return this._content.measure(ctx);
|
|
12020
|
+
}
|
|
12021
|
+
}
|
|
12022
|
+
function text(options) {
|
|
12023
|
+
return new CText(options);
|
|
12024
|
+
}
|
|
12025
|
+
function icon(options) {
|
|
12026
|
+
return new CIcon(options);
|
|
12027
|
+
}
|
|
12028
|
+
function divider(options) {
|
|
12029
|
+
return new CDivider(options);
|
|
12030
|
+
}
|
|
12031
|
+
function container(options) {
|
|
12032
|
+
return new CContainer(options);
|
|
12033
|
+
}
|
|
12034
|
+
function shape(options) {
|
|
12035
|
+
return new CShape(options);
|
|
12036
|
+
}
|
|
12037
|
+
const DEFAULT_THEME = {
|
|
12038
|
+
name: "default",
|
|
12039
|
+
colors: {
|
|
12040
|
+
background: "#ffffff",
|
|
12041
|
+
grid: "#e5e5e5",
|
|
12042
|
+
selection: "rgba(59, 130, 246, 0.1)",
|
|
12043
|
+
connectionPreview: "#3b82f6"
|
|
12044
|
+
},
|
|
12045
|
+
node: {
|
|
12046
|
+
default: {
|
|
12047
|
+
fillColor: "#ffffff",
|
|
12048
|
+
strokeColor: "#333333",
|
|
12049
|
+
strokeWidth: 2,
|
|
12050
|
+
opacity: 1,
|
|
12051
|
+
cornerRadius: 4
|
|
12052
|
+
},
|
|
12053
|
+
hover: {
|
|
12054
|
+
fillColor: "#f5f5f5",
|
|
12055
|
+
strokeColor: "#6366f1",
|
|
12056
|
+
strokeWidth: 2,
|
|
12057
|
+
opacity: 1,
|
|
12058
|
+
cornerRadius: 4
|
|
12059
|
+
},
|
|
12060
|
+
selected: {
|
|
12061
|
+
strokeColor: "#3b82f6",
|
|
12062
|
+
strokeWidth: 2,
|
|
12063
|
+
opacity: 1
|
|
12064
|
+
},
|
|
12065
|
+
dragging: {
|
|
12066
|
+
strokeColor: "#333333",
|
|
12067
|
+
strokeWidth: 2,
|
|
12068
|
+
opacity: 0.8
|
|
12069
|
+
}
|
|
12070
|
+
},
|
|
12071
|
+
edge: {
|
|
12072
|
+
default: {
|
|
12073
|
+
strokeColor: "#666666",
|
|
12074
|
+
strokeWidth: 2,
|
|
12075
|
+
opacity: 1
|
|
12076
|
+
},
|
|
12077
|
+
hover: {
|
|
12078
|
+
strokeColor: "#6366f1",
|
|
12079
|
+
strokeWidth: 2,
|
|
12080
|
+
opacity: 1
|
|
12081
|
+
},
|
|
12082
|
+
selected: {
|
|
12083
|
+
strokeColor: "#3b82f6",
|
|
12084
|
+
strokeWidth: 3,
|
|
12085
|
+
opacity: 1
|
|
12086
|
+
}
|
|
12087
|
+
},
|
|
12088
|
+
text: {
|
|
12089
|
+
font: "14px sans-serif",
|
|
12090
|
+
fontSize: 14,
|
|
12091
|
+
fontFamily: "sans-serif",
|
|
12092
|
+
fontWeight: "normal",
|
|
12093
|
+
color: "#333333",
|
|
12094
|
+
align: "center",
|
|
12095
|
+
baseline: "middle"
|
|
12096
|
+
},
|
|
12097
|
+
port: {
|
|
12098
|
+
default: { color: "#666666", radius: 6 },
|
|
12099
|
+
hover: { color: "#3b82f6", radius: 7 }
|
|
12100
|
+
},
|
|
12101
|
+
group: {
|
|
12102
|
+
default: {
|
|
12103
|
+
fillColor: "rgba(200, 200, 200, 0.2)",
|
|
12104
|
+
strokeColor: "#999999",
|
|
12105
|
+
strokeWidth: 1,
|
|
12106
|
+
opacity: 1
|
|
12107
|
+
},
|
|
12108
|
+
selected: {
|
|
12109
|
+
fillColor: "rgba(59, 130, 246, 0.1)",
|
|
12110
|
+
strokeColor: "#3b82f6",
|
|
12111
|
+
strokeWidth: 2,
|
|
12112
|
+
opacity: 1
|
|
12113
|
+
}
|
|
12114
|
+
}
|
|
12115
|
+
};
|
|
12116
|
+
const DARK_THEME = {
|
|
12117
|
+
name: "dark",
|
|
12118
|
+
colors: {
|
|
12119
|
+
background: "#1a1a1a",
|
|
12120
|
+
grid: "#333333",
|
|
12121
|
+
selection: "rgba(99, 102, 241, 0.2)",
|
|
12122
|
+
connectionPreview: "#6366f1"
|
|
12123
|
+
},
|
|
12124
|
+
node: {
|
|
12125
|
+
default: {
|
|
12126
|
+
fillColor: "#2d2d2d",
|
|
12127
|
+
strokeColor: "#555555",
|
|
12128
|
+
strokeWidth: 2,
|
|
12129
|
+
opacity: 1,
|
|
12130
|
+
cornerRadius: 4
|
|
12131
|
+
},
|
|
12132
|
+
hover: {
|
|
12133
|
+
fillColor: "#3d3d3d",
|
|
12134
|
+
strokeColor: "#6366f1",
|
|
12135
|
+
strokeWidth: 2,
|
|
12136
|
+
opacity: 1,
|
|
12137
|
+
cornerRadius: 4
|
|
12138
|
+
},
|
|
12139
|
+
selected: {
|
|
12140
|
+
fillColor: "#2d2d2d",
|
|
12141
|
+
strokeColor: "#555555",
|
|
12142
|
+
strokeWidth: 2,
|
|
12143
|
+
opacity: 1,
|
|
12144
|
+
cornerRadius: 4
|
|
12145
|
+
},
|
|
12146
|
+
dragging: {
|
|
12147
|
+
fillColor: "#404040",
|
|
12148
|
+
strokeColor: "#555555",
|
|
12149
|
+
strokeWidth: 2,
|
|
12150
|
+
opacity: 0.8,
|
|
12151
|
+
cornerRadius: 4
|
|
12152
|
+
}
|
|
12153
|
+
},
|
|
12154
|
+
edge: {
|
|
12155
|
+
default: {
|
|
12156
|
+
strokeColor: "#888888",
|
|
12157
|
+
strokeWidth: 2,
|
|
12158
|
+
opacity: 1
|
|
12159
|
+
},
|
|
12160
|
+
hover: {
|
|
12161
|
+
strokeColor: "#6366f1",
|
|
12162
|
+
strokeWidth: 2,
|
|
12163
|
+
opacity: 1
|
|
12164
|
+
},
|
|
12165
|
+
selected: {
|
|
12166
|
+
strokeColor: "#818cf8",
|
|
12167
|
+
strokeWidth: 3,
|
|
12168
|
+
opacity: 1
|
|
12169
|
+
}
|
|
12170
|
+
},
|
|
12171
|
+
text: {
|
|
12172
|
+
font: "14px sans-serif",
|
|
12173
|
+
fontSize: 14,
|
|
12174
|
+
fontFamily: "sans-serif",
|
|
12175
|
+
fontWeight: "normal",
|
|
12176
|
+
color: "#e0e0e0",
|
|
12177
|
+
align: "center",
|
|
12178
|
+
baseline: "middle"
|
|
12179
|
+
},
|
|
12180
|
+
port: {
|
|
12181
|
+
default: { color: "#888888", radius: 6 },
|
|
12182
|
+
hover: { color: "#6366f1", radius: 7 }
|
|
12183
|
+
},
|
|
12184
|
+
group: {
|
|
12185
|
+
default: {
|
|
12186
|
+
fillColor: "rgba(100, 100, 100, 0.2)",
|
|
12187
|
+
strokeColor: "#666666",
|
|
12188
|
+
strokeWidth: 1,
|
|
12189
|
+
opacity: 1
|
|
12190
|
+
},
|
|
12191
|
+
selected: {
|
|
12192
|
+
fillColor: "rgba(99, 102, 241, 0.2)",
|
|
12193
|
+
strokeColor: "#6366f1",
|
|
12194
|
+
strokeWidth: 2,
|
|
12195
|
+
opacity: 1
|
|
12196
|
+
}
|
|
12197
|
+
}
|
|
12198
|
+
};
|
|
12199
|
+
class StyleManager {
|
|
12200
|
+
constructor(theme) {
|
|
12201
|
+
this._classes = /* @__PURE__ */ new Map();
|
|
12202
|
+
this._builtInThemes = /* @__PURE__ */ new Map([
|
|
12203
|
+
["default", DEFAULT_THEME],
|
|
12204
|
+
["dark", DARK_THEME]
|
|
12205
|
+
]);
|
|
12206
|
+
if (theme === void 0) {
|
|
12207
|
+
this._theme = DEFAULT_THEME;
|
|
12208
|
+
} else if (typeof theme === "string") {
|
|
12209
|
+
this._theme = this._builtInThemes.get(theme) ?? DEFAULT_THEME;
|
|
12210
|
+
} else {
|
|
12211
|
+
this._theme = theme;
|
|
10632
12212
|
}
|
|
10633
12213
|
}
|
|
10634
12214
|
/**
|
|
@@ -10978,12 +12558,12 @@ class Serializer {
|
|
|
10978
12558
|
label = node.label.text;
|
|
10979
12559
|
}
|
|
10980
12560
|
}
|
|
10981
|
-
let
|
|
12561
|
+
let icon2;
|
|
10982
12562
|
if (node.icon) {
|
|
10983
12563
|
const opts = node.icon.options;
|
|
10984
12564
|
const source = typeof opts.source === "string" ? opts.source : void 0;
|
|
10985
12565
|
if (source) {
|
|
10986
|
-
|
|
12566
|
+
icon2 = omitEmptyValues({
|
|
10987
12567
|
source,
|
|
10988
12568
|
width: opts.width,
|
|
10989
12569
|
height: opts.height,
|
|
@@ -11004,7 +12584,7 @@ class Serializer {
|
|
|
11004
12584
|
}
|
|
11005
12585
|
const contentInset = node.contentInset;
|
|
11006
12586
|
const hasContentInset = contentInset.top !== 0 || contentInset.right !== 0 || contentInset.bottom !== 0 || contentInset.left !== 0;
|
|
11007
|
-
|
|
12587
|
+
const base = omitEmptyValues({
|
|
11008
12588
|
id: node.id,
|
|
11009
12589
|
type: node.typeName,
|
|
11010
12590
|
x: node.x,
|
|
@@ -11015,12 +12595,23 @@ class Serializer {
|
|
|
11015
12595
|
styleClass: node.styleClass,
|
|
11016
12596
|
label,
|
|
11017
12597
|
labelStyleClass: typeof label === "string" ? node.label?.styleClass : void 0,
|
|
11018
|
-
icon,
|
|
12598
|
+
icon: icon2,
|
|
11019
12599
|
contentInset: hasContentInset ? contentInset : void 0,
|
|
11020
12600
|
anchorPoints,
|
|
11021
12601
|
ports: ports.length > 0 ? ports : void 0,
|
|
11022
12602
|
data: Object.keys(node.data).length > 0 ? node.data : void 0
|
|
11023
12603
|
});
|
|
12604
|
+
if (node.typeName === "composite") {
|
|
12605
|
+
const cn = node;
|
|
12606
|
+
const ext = base;
|
|
12607
|
+
ext.content = cn.content.serialize();
|
|
12608
|
+
ext.shapeType = cn.shapeType;
|
|
12609
|
+
if (cn.cornerRadius !== 0) ext.cornerRadius = cn.cornerRadius;
|
|
12610
|
+
ext.autoSize = cn.autoSize;
|
|
12611
|
+
if (cn.minWidth !== 0) ext.minWidth = cn.minWidth;
|
|
12612
|
+
if (cn.minHeight !== 0) ext.minHeight = cn.minHeight;
|
|
12613
|
+
}
|
|
12614
|
+
return base;
|
|
11024
12615
|
}
|
|
11025
12616
|
serializeEdge(edge) {
|
|
11026
12617
|
return {
|
|
@@ -11037,6 +12628,8 @@ class Serializer {
|
|
|
11037
12628
|
label: edge.label?.text,
|
|
11038
12629
|
labelStyleClass: edge.label?.styleClass,
|
|
11039
12630
|
labelOffset: edge.labelOffset !== 0 ? edge.labelOffset : void 0,
|
|
12631
|
+
labelPosition: edge.labelPosition !== 0.5 ? edge.labelPosition : void 0,
|
|
12632
|
+
labelFollowPath: edge.labelFollowPath ? true : void 0,
|
|
11040
12633
|
labelBackground: edge.labelBackground,
|
|
11041
12634
|
labelLineGap: edge.labelLineGap ? true : void 0,
|
|
11042
12635
|
data: Object.keys(edge.data).length > 0 ? edge.data : void 0
|
|
@@ -11395,16 +12988,16 @@ class SvgExporter {
|
|
|
11395
12988
|
const strokeOpacity = (style.strokeOpacity ?? 1) * baseOpacity;
|
|
11396
12989
|
const dash = style.lineDash?.length ? ` stroke-dasharray="${style.lineDash.join(" ")}"` : "";
|
|
11397
12990
|
const dashOffset = style.lineDashOffset !== void 0 ? ` stroke-dashoffset="${style.lineDashOffset}"` : "";
|
|
11398
|
-
let
|
|
12991
|
+
let shape2;
|
|
11399
12992
|
switch (node.typeName) {
|
|
11400
12993
|
case "rectangle": {
|
|
11401
12994
|
const radius = this.getNodeCornerRadius(node, bounds);
|
|
11402
|
-
|
|
12995
|
+
shape2 = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}" rx="${radius}" ry="${radius}"`;
|
|
11403
12996
|
break;
|
|
11404
12997
|
}
|
|
11405
12998
|
case "circle": {
|
|
11406
12999
|
const center = node.getCenter();
|
|
11407
|
-
|
|
13000
|
+
shape2 = `<ellipse cx="${center.x}" cy="${center.y}" rx="${bounds.width / 2}" ry="${bounds.height / 2}"`;
|
|
11408
13001
|
break;
|
|
11409
13002
|
}
|
|
11410
13003
|
case "diamond": {
|
|
@@ -11417,27 +13010,61 @@ class SvgExporter {
|
|
|
11417
13010
|
`${center.x},${center.y + hh}`,
|
|
11418
13011
|
`${center.x - hw},${center.y}`
|
|
11419
13012
|
].join(" ");
|
|
11420
|
-
|
|
13013
|
+
shape2 = `<polygon points="${points}"`;
|
|
11421
13014
|
break;
|
|
11422
13015
|
}
|
|
11423
13016
|
case "custom": {
|
|
11424
13017
|
const svgPath = "getSvgPath" in node && typeof node.getSvgPath === "function" ? node.getSvgPath() : null;
|
|
11425
13018
|
if (svgPath) {
|
|
11426
|
-
|
|
13019
|
+
shape2 = `<path d="${this.escapeAttribute(svgPath)}" transform="translate(${bounds.x}, ${bounds.y})"`;
|
|
11427
13020
|
} else {
|
|
11428
|
-
|
|
13021
|
+
shape2 = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}"`;
|
|
11429
13022
|
}
|
|
11430
13023
|
break;
|
|
11431
13024
|
}
|
|
13025
|
+
case "composite": {
|
|
13026
|
+
const cn = node;
|
|
13027
|
+
switch (cn.shapeType) {
|
|
13028
|
+
case "circle": {
|
|
13029
|
+
const center = node.getCenter();
|
|
13030
|
+
shape2 = `<ellipse cx="${center.x}" cy="${center.y}" rx="${bounds.width / 2}" ry="${bounds.height / 2}"`;
|
|
13031
|
+
break;
|
|
13032
|
+
}
|
|
13033
|
+
case "diamond": {
|
|
13034
|
+
const center = node.getCenter();
|
|
13035
|
+
const hw = bounds.width / 2;
|
|
13036
|
+
const hh = bounds.height / 2;
|
|
13037
|
+
const points = [
|
|
13038
|
+
`${center.x},${center.y - hh}`,
|
|
13039
|
+
`${center.x + hw},${center.y}`,
|
|
13040
|
+
`${center.x},${center.y + hh}`,
|
|
13041
|
+
`${center.x - hw},${center.y}`
|
|
13042
|
+
].join(" ");
|
|
13043
|
+
shape2 = `<polygon points="${points}"`;
|
|
13044
|
+
break;
|
|
13045
|
+
}
|
|
13046
|
+
default: {
|
|
13047
|
+
const radius = cn.cornerRadius;
|
|
13048
|
+
shape2 = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}" rx="${radius}" ry="${radius}"`;
|
|
13049
|
+
break;
|
|
13050
|
+
}
|
|
13051
|
+
}
|
|
13052
|
+
const contentBounds = cn.getLabelContainerBounds(bounds);
|
|
13053
|
+
const contentSvg = cn.content.toSVG(contentBounds);
|
|
13054
|
+
return [
|
|
13055
|
+
`${shape2} fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${strokeOpacity}"${dash}${dashOffset}/>`,
|
|
13056
|
+
contentSvg
|
|
13057
|
+
].join("");
|
|
13058
|
+
}
|
|
11432
13059
|
default: {
|
|
11433
|
-
|
|
13060
|
+
shape2 = `<rect x="${bounds.x}" y="${bounds.y}" width="${bounds.width}" height="${bounds.height}"`;
|
|
11434
13061
|
}
|
|
11435
13062
|
}
|
|
11436
13063
|
const label = this.renderNodeLabel(node, bounds);
|
|
11437
|
-
const
|
|
13064
|
+
const icon2 = this.renderNodeIcon(node, bounds);
|
|
11438
13065
|
return [
|
|
11439
|
-
`${
|
|
11440
|
-
|
|
13066
|
+
`${shape2} fill="${fill}" fill-opacity="${fillOpacity}" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-opacity="${strokeOpacity}"${dash}${dashOffset}/>`,
|
|
13067
|
+
icon2,
|
|
11441
13068
|
label
|
|
11442
13069
|
].join("");
|
|
11443
13070
|
}
|
|
@@ -11465,9 +13092,9 @@ class SvgExporter {
|
|
|
11465
13092
|
return "";
|
|
11466
13093
|
}
|
|
11467
13094
|
const labelPoint = this.getEdgeLabelPoint(edge, edgeLabelOffset);
|
|
11468
|
-
const
|
|
13095
|
+
const text2 = this.renderTextLabel(edge.label.text, labelPoint, edge.label.style);
|
|
11469
13096
|
const bg = this.renderEdgeLabelBackground(edge, labelPoint);
|
|
11470
|
-
return `${bg}${
|
|
13097
|
+
return `${bg}${text2}`;
|
|
11471
13098
|
}
|
|
11472
13099
|
renderEdgeLabelBackground(edge, point) {
|
|
11473
13100
|
if (!edge.label) {
|
|
@@ -11503,12 +13130,12 @@ class SvgExporter {
|
|
|
11503
13130
|
left: n(value.left)
|
|
11504
13131
|
};
|
|
11505
13132
|
}
|
|
11506
|
-
measureTextLabel(
|
|
13133
|
+
measureTextLabel(text2, style = {}, inset = 8) {
|
|
11507
13134
|
const fontSize = style.fontSize ?? 14;
|
|
11508
13135
|
const fontFamily = style.fontFamily ?? "sans-serif";
|
|
11509
13136
|
const fontWeight = style.fontWeight ?? "normal";
|
|
11510
13137
|
const lineHeight = fontSize * 1.2;
|
|
11511
|
-
const lines =
|
|
13138
|
+
const lines = text2.split("\n");
|
|
11512
13139
|
let maxWidth = 0;
|
|
11513
13140
|
if (typeof document !== "undefined") {
|
|
11514
13141
|
const canvas = document.createElement("canvas");
|
|
@@ -11621,7 +13248,7 @@ class SvgExporter {
|
|
|
11621
13248
|
y: midpoint.y + effectiveOffset
|
|
11622
13249
|
};
|
|
11623
13250
|
}
|
|
11624
|
-
renderTextLabel(
|
|
13251
|
+
renderTextLabel(text2, point, style = {}) {
|
|
11625
13252
|
const fill = style.color ?? "#000000";
|
|
11626
13253
|
const fontSize = style.fontSize ?? 14;
|
|
11627
13254
|
const fontFamily = style.fontFamily ?? "sans-serif";
|
|
@@ -11629,10 +13256,10 @@ class SvgExporter {
|
|
|
11629
13256
|
const opacity = style.opacity ?? 1;
|
|
11630
13257
|
const anchor = style.align === "left" ? "start" : style.align === "right" ? "end" : "middle";
|
|
11631
13258
|
const baseline = style.baseline === "top" ? "text-before-edge" : style.baseline === "bottom" ? "text-after-edge" : "middle";
|
|
11632
|
-
const lines =
|
|
13259
|
+
const lines = text2.split("\n");
|
|
11633
13260
|
if (lines.length <= 1) {
|
|
11634
13261
|
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(
|
|
11635
|
-
|
|
13262
|
+
text2
|
|
11636
13263
|
)}</text>`;
|
|
11637
13264
|
}
|
|
11638
13265
|
const lineHeight = fontSize * 1.2;
|
|
@@ -11701,16 +13328,16 @@ class SvgExporter {
|
|
|
11701
13328
|
return Math.max(0, Math.min(rectangleRadius, bounds.width / 2, bounds.height / 2));
|
|
11702
13329
|
}
|
|
11703
13330
|
renderNodeIcon(node, nodeBounds) {
|
|
11704
|
-
const
|
|
11705
|
-
if (!
|
|
13331
|
+
const icon2 = node.icon;
|
|
13332
|
+
if (!icon2) {
|
|
11706
13333
|
return "";
|
|
11707
13334
|
}
|
|
11708
|
-
const opts =
|
|
11709
|
-
const iconSize =
|
|
13335
|
+
const opts = icon2.options;
|
|
13336
|
+
const iconSize = icon2.getSize();
|
|
11710
13337
|
if (iconSize.width <= 0 || iconSize.height <= 0) {
|
|
11711
13338
|
return "";
|
|
11712
13339
|
}
|
|
11713
|
-
const iconInset =
|
|
13340
|
+
const iconInset = icon2.inset;
|
|
11714
13341
|
const iconBoxSize = this.getIconBoxSize(iconSize, iconInset);
|
|
11715
13342
|
const iconBounds = this.getIconBounds(
|
|
11716
13343
|
nodeBounds,
|
|
@@ -11922,8 +13549,8 @@ class SvgExporter {
|
|
|
11922
13549
|
"</svg>"
|
|
11923
13550
|
].join("");
|
|
11924
13551
|
}
|
|
11925
|
-
escapeText(
|
|
11926
|
-
return
|
|
13552
|
+
escapeText(text2) {
|
|
13553
|
+
return text2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11927
13554
|
}
|
|
11928
13555
|
}
|
|
11929
13556
|
class AutoLayout {
|
|
@@ -12475,9 +14102,15 @@ export {
|
|
|
12475
14102
|
AnimationManager,
|
|
12476
14103
|
AutoLayout,
|
|
12477
14104
|
AutoRouting,
|
|
14105
|
+
CContainer,
|
|
14106
|
+
CDivider,
|
|
14107
|
+
CIcon,
|
|
14108
|
+
CShape,
|
|
14109
|
+
CText,
|
|
12478
14110
|
ChangeEditablePolylineControlPointsCommand,
|
|
12479
14111
|
CircleNode,
|
|
12480
14112
|
CompositeCommand,
|
|
14113
|
+
CompositeNode,
|
|
12481
14114
|
ConnectionManager,
|
|
12482
14115
|
ContextMenuManager,
|
|
12483
14116
|
CustomShapeNode,
|
|
@@ -12522,15 +14155,21 @@ export {
|
|
|
12522
14155
|
calculateBezierControlPoints,
|
|
12523
14156
|
clamp,
|
|
12524
14157
|
clonePoints,
|
|
14158
|
+
container,
|
|
14159
|
+
deserializeCComponent,
|
|
12525
14160
|
distance,
|
|
12526
14161
|
distanceToSegment,
|
|
12527
14162
|
distributeNodes,
|
|
14163
|
+
divider,
|
|
12528
14164
|
drawRoundedRectPath,
|
|
12529
14165
|
expandBounds,
|
|
14166
|
+
flexLayout,
|
|
12530
14167
|
generateId,
|
|
14168
|
+
icon,
|
|
12531
14169
|
isCornerPlacement,
|
|
12532
14170
|
lerp,
|
|
12533
14171
|
mergeBounds,
|
|
14172
|
+
normalizeSides,
|
|
12534
14173
|
pointInEllipse,
|
|
12535
14174
|
pointInRect,
|
|
12536
14175
|
rectIntersection,
|
|
@@ -12541,7 +14180,9 @@ export {
|
|
|
12541
14180
|
resetPortIdCounter,
|
|
12542
14181
|
rotatePoint,
|
|
12543
14182
|
segmentRectIntersections,
|
|
14183
|
+
shape,
|
|
12544
14184
|
snapPointToGrid,
|
|
12545
|
-
snapToGrid
|
|
14185
|
+
snapToGrid,
|
|
14186
|
+
text
|
|
12546
14187
|
};
|
|
12547
14188
|
//# sourceMappingURL=papirus.js.map
|