@ngroznykh/papirus 0.4.0 → 0.5.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/dist/core/ContextMenuManager.d.ts +5 -0
- package/dist/core/ContextMenuManager.d.ts.map +1 -1
- package/dist/core/DiagramRenderer.d.ts +6 -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/core/SelectionManager.d.ts.map +1 -1
- package/dist/elements/Node.d.ts +28 -0
- package/dist/elements/Node.d.ts.map +1 -1
- package/dist/elements/NodeImage.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/papirus.js +280 -96
- package/dist/papirus.js.map +1 -1
- package/package.json +1 -1
package/dist/papirus.js
CHANGED
|
@@ -570,6 +570,14 @@ class SelectionManager extends EventEmitter {
|
|
|
570
570
|
}
|
|
571
571
|
return;
|
|
572
572
|
}
|
|
573
|
+
const node = element;
|
|
574
|
+
if (typeof node.getBadgeAtPoint === "function") {
|
|
575
|
+
const badge = node.getBadgeAtPoint(point);
|
|
576
|
+
if (badge !== null) {
|
|
577
|
+
this.renderer.emit("nodeBadgeClick", element.id, badge.id);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
573
581
|
if (event.ctrlKey || event.metaKey) {
|
|
574
582
|
this.toggleSelection(element.id);
|
|
575
583
|
} else {
|
|
@@ -5002,6 +5010,7 @@ class InteractionManager {
|
|
|
5002
5010
|
this.overlayDragSession = null;
|
|
5003
5011
|
this.handledOverlayMouseDown = false;
|
|
5004
5012
|
this.renderer = options.renderer;
|
|
5013
|
+
this.navigationOnly = options.navigationOnly ?? false;
|
|
5005
5014
|
this.inputHandler = new InputHandler({
|
|
5006
5015
|
canvas: this.renderer.getCanvas(),
|
|
5007
5016
|
screenToWorld: (x, y) => this.renderer.screenToWorld(x, y)
|
|
@@ -5153,13 +5162,15 @@ class InteractionManager {
|
|
|
5153
5162
|
setupEvents(options) {
|
|
5154
5163
|
this.overlayCleanup = this.renderer.addOverlayRenderer((ctx) => {
|
|
5155
5164
|
this.selectionManager.renderSelectionRect(ctx);
|
|
5156
|
-
this.
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
const
|
|
5161
|
-
|
|
5162
|
-
node
|
|
5165
|
+
if (!this.navigationOnly) {
|
|
5166
|
+
this.dragManager.renderAlignmentGuides(ctx);
|
|
5167
|
+
this.connectionManager.renderPreview(ctx);
|
|
5168
|
+
this.connectionManager.renderHoverAnchors(ctx);
|
|
5169
|
+
for (const id of this.selectionManager.selectedIds) {
|
|
5170
|
+
const node = this.renderer.getNode(id);
|
|
5171
|
+
if (node) {
|
|
5172
|
+
node.renderResizeHandles(ctx);
|
|
5173
|
+
}
|
|
5163
5174
|
}
|
|
5164
5175
|
}
|
|
5165
5176
|
});
|
|
@@ -5173,68 +5184,70 @@ class InteractionManager {
|
|
|
5173
5184
|
this.inputHandler.on("pinch", (event) => this.handlePinch(event));
|
|
5174
5185
|
this.inputHandler.on("keydown", (event) => this.handleKeyDown(event, options));
|
|
5175
5186
|
this.inputHandler.on("keyup", (event) => this.handleKeyUp(event));
|
|
5176
|
-
this.
|
|
5177
|
-
this.
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
const
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
}
|
|
5185
|
-
});
|
|
5186
|
-
this.dragManager.on("dragend", (nodeIds) => {
|
|
5187
|
-
this.connectionManager.enableHover();
|
|
5188
|
-
const nodePositions = /* @__PURE__ */ new Map();
|
|
5189
|
-
for (const id of nodeIds) {
|
|
5190
|
-
const node = this.renderer.getNode(id);
|
|
5191
|
-
const before = this.dragStartPositions.get(id);
|
|
5192
|
-
if (!node || !before) continue;
|
|
5193
|
-
const after = { x: node.x, y: node.y };
|
|
5194
|
-
if (before.x !== after.x || before.y !== after.y) {
|
|
5195
|
-
nodePositions.set(id, { before, after });
|
|
5196
|
-
}
|
|
5197
|
-
}
|
|
5198
|
-
if (nodePositions.size > 0) {
|
|
5199
|
-
this.historyManager.execute(
|
|
5200
|
-
new MoveNodesCommand((id) => this.renderer.getNode(id), nodePositions)
|
|
5201
|
-
);
|
|
5202
|
-
}
|
|
5203
|
-
});
|
|
5204
|
-
this.connectionManager.on("edgeReconnectStart", (edge, endpoint, original) => {
|
|
5205
|
-
this.reconnectOrigins.set(edge.id, { endpoint, original: { ...original } });
|
|
5206
|
-
});
|
|
5207
|
-
this.connectionManager.on("edgeReconnect", (edge, endpoint) => {
|
|
5208
|
-
const origin = this.reconnectOrigins.get(edge.id);
|
|
5209
|
-
if (!origin || origin.endpoint !== endpoint) {
|
|
5210
|
-
return;
|
|
5211
|
-
}
|
|
5212
|
-
const before = origin.original;
|
|
5213
|
-
const after = endpoint === "start" ? edge.from : edge.to;
|
|
5214
|
-
if (this.endpointsEqual(before, after)) {
|
|
5215
|
-
this.reconnectOrigins.delete(edge.id);
|
|
5216
|
-
return;
|
|
5217
|
-
}
|
|
5218
|
-
this.historyManager.execute({
|
|
5219
|
-
execute: () => {
|
|
5220
|
-
if (endpoint === "start") {
|
|
5221
|
-
edge.from = { ...after };
|
|
5222
|
-
} else {
|
|
5223
|
-
edge.to = { ...after };
|
|
5187
|
+
if (!this.navigationOnly) {
|
|
5188
|
+
this.dragManager.on("dragstart", (nodeIds) => {
|
|
5189
|
+
this.connectionManager.disableHover();
|
|
5190
|
+
this.dragStartPositions.clear();
|
|
5191
|
+
for (const id of nodeIds) {
|
|
5192
|
+
const node = this.renderer.getNode(id);
|
|
5193
|
+
if (node) {
|
|
5194
|
+
this.dragStartPositions.set(id, { x: node.x, y: node.y });
|
|
5224
5195
|
}
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5196
|
+
}
|
|
5197
|
+
});
|
|
5198
|
+
this.dragManager.on("dragend", (nodeIds) => {
|
|
5199
|
+
this.connectionManager.enableHover();
|
|
5200
|
+
const nodePositions = /* @__PURE__ */ new Map();
|
|
5201
|
+
for (const id of nodeIds) {
|
|
5202
|
+
const node = this.renderer.getNode(id);
|
|
5203
|
+
const before = this.dragStartPositions.get(id);
|
|
5204
|
+
if (!node || !before) continue;
|
|
5205
|
+
const after = { x: node.x, y: node.y };
|
|
5206
|
+
if (before.x !== after.x || before.y !== after.y) {
|
|
5207
|
+
nodePositions.set(id, { before, after });
|
|
5232
5208
|
}
|
|
5233
|
-
|
|
5209
|
+
}
|
|
5210
|
+
if (nodePositions.size > 0) {
|
|
5211
|
+
this.historyManager.execute(
|
|
5212
|
+
new MoveNodesCommand((id) => this.renderer.getNode(id), nodePositions)
|
|
5213
|
+
);
|
|
5234
5214
|
}
|
|
5235
5215
|
});
|
|
5236
|
-
this.
|
|
5237
|
-
|
|
5216
|
+
this.connectionManager.on("edgeReconnectStart", (edge, endpoint, original) => {
|
|
5217
|
+
this.reconnectOrigins.set(edge.id, { endpoint, original: { ...original } });
|
|
5218
|
+
});
|
|
5219
|
+
this.connectionManager.on("edgeReconnect", (edge, endpoint) => {
|
|
5220
|
+
const origin = this.reconnectOrigins.get(edge.id);
|
|
5221
|
+
if (!origin || origin.endpoint !== endpoint) {
|
|
5222
|
+
return;
|
|
5223
|
+
}
|
|
5224
|
+
const before = origin.original;
|
|
5225
|
+
const after = endpoint === "start" ? edge.from : edge.to;
|
|
5226
|
+
if (this.endpointsEqual(before, after)) {
|
|
5227
|
+
this.reconnectOrigins.delete(edge.id);
|
|
5228
|
+
return;
|
|
5229
|
+
}
|
|
5230
|
+
this.historyManager.execute({
|
|
5231
|
+
execute: () => {
|
|
5232
|
+
if (endpoint === "start") {
|
|
5233
|
+
edge.from = { ...after };
|
|
5234
|
+
} else {
|
|
5235
|
+
edge.to = { ...after };
|
|
5236
|
+
}
|
|
5237
|
+
this.renderer.markDirty();
|
|
5238
|
+
},
|
|
5239
|
+
undo: () => {
|
|
5240
|
+
if (endpoint === "start") {
|
|
5241
|
+
edge.from = { ...before };
|
|
5242
|
+
} else {
|
|
5243
|
+
edge.to = { ...before };
|
|
5244
|
+
}
|
|
5245
|
+
this.renderer.markDirty();
|
|
5246
|
+
}
|
|
5247
|
+
});
|
|
5248
|
+
this.reconnectOrigins.delete(edge.id);
|
|
5249
|
+
});
|
|
5250
|
+
}
|
|
5238
5251
|
this.historyManager.on("change", () => {
|
|
5239
5252
|
this.renderer.markDirty();
|
|
5240
5253
|
});
|
|
@@ -5242,14 +5255,16 @@ class InteractionManager {
|
|
|
5242
5255
|
handleMouseDown(event) {
|
|
5243
5256
|
this.handledScrollbarMouseDown = false;
|
|
5244
5257
|
this.handledOverlayMouseDown = false;
|
|
5245
|
-
if (this.
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5258
|
+
if (!this.navigationOnly) {
|
|
5259
|
+
if (this.resizeManager.handleMouseDown(event)) {
|
|
5260
|
+
return;
|
|
5261
|
+
}
|
|
5262
|
+
if (this.connectionManager.tryStartReconnection(event)) {
|
|
5263
|
+
return;
|
|
5264
|
+
}
|
|
5265
|
+
if (this.connectionManager.tryStartConnectionAtPoint(event)) {
|
|
5266
|
+
return;
|
|
5267
|
+
}
|
|
5253
5268
|
}
|
|
5254
5269
|
const overlayDrag = this.renderer.beginOverlayDrag(event.screenX, event.screenY);
|
|
5255
5270
|
if (overlayDrag) {
|
|
@@ -5283,6 +5298,9 @@ class InteractionManager {
|
|
|
5283
5298
|
if (this.navigationManager.handleMouseDown(event)) {
|
|
5284
5299
|
return;
|
|
5285
5300
|
}
|
|
5301
|
+
if (this.navigationOnly) {
|
|
5302
|
+
return;
|
|
5303
|
+
}
|
|
5286
5304
|
if (this.dragManager.handleMouseDown(event)) {
|
|
5287
5305
|
return;
|
|
5288
5306
|
}
|
|
@@ -5302,6 +5320,9 @@ class InteractionManager {
|
|
|
5302
5320
|
}
|
|
5303
5321
|
}
|
|
5304
5322
|
const overScrollbar = this.renderer.updateScrollbarHover(event.screenX, event.screenY);
|
|
5323
|
+
this.renderer.updateBadgeHover(
|
|
5324
|
+
overScrollbar ? { x: -1e9, y: -1e9 } : { x: event.worldX, y: event.worldY }
|
|
5325
|
+
);
|
|
5305
5326
|
if (this.overlayDragSession) {
|
|
5306
5327
|
const moved = this.renderer.updateOverlayDrag(
|
|
5307
5328
|
this.overlayDragSession,
|
|
@@ -5330,14 +5351,16 @@ class InteractionManager {
|
|
|
5330
5351
|
if (overScrollbar) {
|
|
5331
5352
|
return;
|
|
5332
5353
|
}
|
|
5333
|
-
if (this.
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5354
|
+
if (!this.navigationOnly) {
|
|
5355
|
+
if (this.resizeManager.handleMouseMove(event)) {
|
|
5356
|
+
return;
|
|
5357
|
+
}
|
|
5358
|
+
if (this.connectionManager.handleMouseMove(event)) {
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
5361
|
+
if (this.dragManager.handleMouseMove(event)) {
|
|
5362
|
+
return;
|
|
5363
|
+
}
|
|
5341
5364
|
}
|
|
5342
5365
|
if (this.selectionManager.selectionRectangle !== null) {
|
|
5343
5366
|
this.selectionManager.updateSelectionRect({ x: event.worldX, y: event.worldY });
|
|
@@ -5356,14 +5379,16 @@ class InteractionManager {
|
|
|
5356
5379
|
this.renderer.setScrollbarActiveAxis(null);
|
|
5357
5380
|
return;
|
|
5358
5381
|
}
|
|
5359
|
-
if (this.
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5382
|
+
if (!this.navigationOnly) {
|
|
5383
|
+
if (this.resizeManager.handleMouseUp()) {
|
|
5384
|
+
return;
|
|
5385
|
+
}
|
|
5386
|
+
if (this.connectionManager.handleMouseUp(event)) {
|
|
5387
|
+
return;
|
|
5388
|
+
}
|
|
5389
|
+
if (this.dragManager.handleMouseUp(event)) {
|
|
5390
|
+
return;
|
|
5391
|
+
}
|
|
5367
5392
|
}
|
|
5368
5393
|
if (this.selectionManager.selectionRectangle !== null) {
|
|
5369
5394
|
this.selectionManager.endSelectionRect();
|
|
@@ -5389,6 +5414,9 @@ class InteractionManager {
|
|
|
5389
5414
|
if (this.dragManager.handledMouseDown || this.resizeManager.handledMouseDown || this.connectionManager.connecting) {
|
|
5390
5415
|
return;
|
|
5391
5416
|
}
|
|
5417
|
+
if (this.navigationOnly) {
|
|
5418
|
+
return;
|
|
5419
|
+
}
|
|
5392
5420
|
if (this.connectionManager.handleDoubleClick(event)) {
|
|
5393
5421
|
return;
|
|
5394
5422
|
}
|
|
@@ -5451,16 +5479,19 @@ class InteractionManager {
|
|
|
5451
5479
|
handleKeyDown(event, options) {
|
|
5452
5480
|
const isCtrlOrMeta = event.ctrlKey || event.metaKey;
|
|
5453
5481
|
const key = event.code.startsWith("Key") ? event.code.slice(3).toLowerCase() : event.key.toLowerCase();
|
|
5482
|
+
this.navigationManager.handleKeyDown(event);
|
|
5483
|
+
if (this.handleViewportNavigationKey(event)) {
|
|
5484
|
+
return;
|
|
5485
|
+
}
|
|
5486
|
+
if (this.navigationOnly) {
|
|
5487
|
+
return;
|
|
5488
|
+
}
|
|
5454
5489
|
if (isCtrlOrMeta && (key === "z" || key === "y")) {
|
|
5455
5490
|
this.flushPendingPropertyChanges();
|
|
5456
5491
|
}
|
|
5457
5492
|
if (this.historyManager.handleKeyDown(event)) {
|
|
5458
5493
|
return;
|
|
5459
5494
|
}
|
|
5460
|
-
this.navigationManager.handleKeyDown(event);
|
|
5461
|
-
if (this.handleViewportNavigationKey(event)) {
|
|
5462
|
-
return;
|
|
5463
|
-
}
|
|
5464
5495
|
if (this.keymap.deleteKeys.includes(event.key)) {
|
|
5465
5496
|
event.preventDefault();
|
|
5466
5497
|
this.deleteSelection();
|
|
@@ -6164,6 +6195,7 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6164
6195
|
iconEl.style.alignItems = "center";
|
|
6165
6196
|
iconEl.style.justifyContent = "center";
|
|
6166
6197
|
iconEl.style.textAlign = "center";
|
|
6198
|
+
iconEl.style.flexShrink = "0";
|
|
6167
6199
|
if (!icon) {
|
|
6168
6200
|
iconEl.textContent = "";
|
|
6169
6201
|
return iconEl;
|
|
@@ -6171,6 +6203,14 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6171
6203
|
if (typeof icon === "string") {
|
|
6172
6204
|
if (this.isSvgString(icon)) {
|
|
6173
6205
|
iconEl.innerHTML = icon;
|
|
6206
|
+
} else if (this.options.iconToUrl) {
|
|
6207
|
+
const img = document.createElement("img");
|
|
6208
|
+
img.src = this.options.iconToUrl(icon);
|
|
6209
|
+
img.alt = "";
|
|
6210
|
+
img.style.width = "16px";
|
|
6211
|
+
img.style.height = "16px";
|
|
6212
|
+
img.style.objectFit = "contain";
|
|
6213
|
+
iconEl.appendChild(img);
|
|
6174
6214
|
} else {
|
|
6175
6215
|
iconEl.classList.add("material-symbols-outlined");
|
|
6176
6216
|
iconEl.style.fontSize = "16px";
|
|
@@ -6180,6 +6220,14 @@ class ContextMenuManager extends EventEmitter {
|
|
|
6180
6220
|
}
|
|
6181
6221
|
if (icon.type === "svg" || icon.type === "html") {
|
|
6182
6222
|
iconEl.innerHTML = icon.value;
|
|
6223
|
+
} else if (this.options.iconToUrl) {
|
|
6224
|
+
const img = document.createElement("img");
|
|
6225
|
+
img.src = this.options.iconToUrl(icon.value);
|
|
6226
|
+
img.alt = "";
|
|
6227
|
+
img.style.width = "16px";
|
|
6228
|
+
img.style.height = "16px";
|
|
6229
|
+
img.style.objectFit = "contain";
|
|
6230
|
+
iconEl.appendChild(img);
|
|
6183
6231
|
} else {
|
|
6184
6232
|
iconEl.classList.add("material-symbols-outlined");
|
|
6185
6233
|
iconEl.style.fontSize = "16px";
|
|
@@ -6713,6 +6761,30 @@ class DiagramRenderer extends EventEmitter {
|
|
|
6713
6761
|
}
|
|
6714
6762
|
return void 0;
|
|
6715
6763
|
}
|
|
6764
|
+
/**
|
|
6765
|
+
* Update badge hover state and canvas cursor based on pointer position.
|
|
6766
|
+
* Call from mousemove to show hover highlight and pointer cursor over badges.
|
|
6767
|
+
*/
|
|
6768
|
+
updateBadgeHover(worldPoint) {
|
|
6769
|
+
const element = this.getElementAtPoint(worldPoint);
|
|
6770
|
+
let hoveredNodeId = null;
|
|
6771
|
+
let hoveredIndex = -1;
|
|
6772
|
+
const node = element;
|
|
6773
|
+
if (element && typeof node.getBadgeAtPoint === "function") {
|
|
6774
|
+
const badge = node.getBadgeAtPoint(worldPoint);
|
|
6775
|
+
if (badge !== null) {
|
|
6776
|
+
hoveredNodeId = node.id;
|
|
6777
|
+
hoveredIndex = badge.index;
|
|
6778
|
+
}
|
|
6779
|
+
}
|
|
6780
|
+
const cursor = hoveredNodeId !== null ? "pointer" : "";
|
|
6781
|
+
if (this.canvas.style.cursor !== cursor) {
|
|
6782
|
+
this.canvas.style.cursor = cursor;
|
|
6783
|
+
}
|
|
6784
|
+
for (const n of this._nodes.values()) {
|
|
6785
|
+
n.setBadgeHover(n.id === hoveredNodeId ? hoveredIndex : -1);
|
|
6786
|
+
}
|
|
6787
|
+
}
|
|
6716
6788
|
/**
|
|
6717
6789
|
* Mark the diagram as needing re-render
|
|
6718
6790
|
*/
|
|
@@ -8025,11 +8097,11 @@ function tintSvg(svgText, strokeColor, fillColor) {
|
|
|
8025
8097
|
const all = [root, ...Array.from(root.querySelectorAll("*"))];
|
|
8026
8098
|
for (const el of all) {
|
|
8027
8099
|
const stroke = el.getAttribute("stroke");
|
|
8028
|
-
|
|
8100
|
+
const fill = el.getAttribute("fill");
|
|
8101
|
+
if (strokeColor && (stroke === null || stroke.toLowerCase() !== "none")) {
|
|
8029
8102
|
el.setAttribute("stroke", strokeColor);
|
|
8030
8103
|
}
|
|
8031
|
-
|
|
8032
|
-
if (fillColor && fill !== null && fill.toLowerCase() !== "none") {
|
|
8104
|
+
if (fillColor && (fill === null || fill.toLowerCase() !== "none")) {
|
|
8033
8105
|
el.setAttribute("fill", fillColor);
|
|
8034
8106
|
}
|
|
8035
8107
|
const style = el.getAttribute("style");
|
|
@@ -8196,6 +8268,9 @@ class NodeImage {
|
|
|
8196
8268
|
};
|
|
8197
8269
|
}
|
|
8198
8270
|
}
|
|
8271
|
+
const BADGE_SIZE = 15;
|
|
8272
|
+
const BADGE_OFFSET = 4;
|
|
8273
|
+
const BADGE_GAP = 4;
|
|
8199
8274
|
function isValidAnchorId(id) {
|
|
8200
8275
|
return /^(top|right|bottom|left):\d+$/.test(id);
|
|
8201
8276
|
}
|
|
@@ -8217,7 +8292,10 @@ class Node extends Element {
|
|
|
8217
8292
|
styleClass: options.styleClass
|
|
8218
8293
|
});
|
|
8219
8294
|
this._ports = [];
|
|
8295
|
+
this._badges = [];
|
|
8220
8296
|
this._anchorCache = null;
|
|
8297
|
+
this._badgeImageCache = /* @__PURE__ */ new Map();
|
|
8298
|
+
this._hoveredBadgeIndex = -1;
|
|
8221
8299
|
this._defaultSize = { width: options.width, height: options.height };
|
|
8222
8300
|
this._nodeStyle = { ...DEFAULT_NODE_STYLE, ...options.style };
|
|
8223
8301
|
this._showPortsAlways = options.showPortsAlways ?? false;
|
|
@@ -8242,6 +8320,10 @@ class Node extends Element {
|
|
|
8242
8320
|
this.addPort(portOptions);
|
|
8243
8321
|
}
|
|
8244
8322
|
}
|
|
8323
|
+
if (options.badges !== void 0 && options.badges.length > 0) {
|
|
8324
|
+
this._badges = options.badges.map((b) => ({ id: b.id, iconUrl: b.iconUrl }));
|
|
8325
|
+
this.ensureBadgeImagesLoaded();
|
|
8326
|
+
}
|
|
8245
8327
|
}
|
|
8246
8328
|
/**
|
|
8247
8329
|
* Node style
|
|
@@ -8307,6 +8389,46 @@ class Node extends Element {
|
|
|
8307
8389
|
}
|
|
8308
8390
|
this.markDirty();
|
|
8309
8391
|
}
|
|
8392
|
+
/**
|
|
8393
|
+
* Badges shown in top-left corner of node (e.g. interactive property icons)
|
|
8394
|
+
*/
|
|
8395
|
+
get badges() {
|
|
8396
|
+
return this._badges;
|
|
8397
|
+
}
|
|
8398
|
+
set badges(value) {
|
|
8399
|
+
this._badges = Array.isArray(value) ? value.map((b) => ({ id: b.id, iconUrl: b.iconUrl })) : [];
|
|
8400
|
+
this.ensureBadgeImagesLoaded();
|
|
8401
|
+
this.markDirty();
|
|
8402
|
+
}
|
|
8403
|
+
/**
|
|
8404
|
+
* Set which badge index is under the pointer (-1 for none). Used for hover highlight and cursor.
|
|
8405
|
+
*/
|
|
8406
|
+
setBadgeHover(index) {
|
|
8407
|
+
if (this._hoveredBadgeIndex === index) return;
|
|
8408
|
+
this._hoveredBadgeIndex = index;
|
|
8409
|
+
this.markDirty();
|
|
8410
|
+
}
|
|
8411
|
+
/**
|
|
8412
|
+
* Return badge at world point, or null if point is not over a badge.
|
|
8413
|
+
*/
|
|
8414
|
+
getBadgeAtPoint(worldPoint) {
|
|
8415
|
+
if (this._badges.length === 0) {
|
|
8416
|
+
return null;
|
|
8417
|
+
}
|
|
8418
|
+
const bounds = this.getBounds();
|
|
8419
|
+
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
8420
|
+
const localX = worldPoint.x - (contentBounds.x + BADGE_OFFSET);
|
|
8421
|
+
const localY = worldPoint.y - (contentBounds.y + BADGE_OFFSET);
|
|
8422
|
+
for (let i = 0; i < this._badges.length; i++) {
|
|
8423
|
+
const badge = this._badges[i];
|
|
8424
|
+
if (badge === void 0) continue;
|
|
8425
|
+
const x = i * (BADGE_SIZE + BADGE_GAP);
|
|
8426
|
+
if (localX >= x && localX <= x + BADGE_SIZE && localY >= 0 && localY <= BADGE_SIZE) {
|
|
8427
|
+
return { id: badge.id, index: i };
|
|
8428
|
+
}
|
|
8429
|
+
}
|
|
8430
|
+
return null;
|
|
8431
|
+
}
|
|
8310
8432
|
/**
|
|
8311
8433
|
* Add a port to this node
|
|
8312
8434
|
*/
|
|
@@ -8526,6 +8648,7 @@ class Node extends Element {
|
|
|
8526
8648
|
ctx.setLineDash([]);
|
|
8527
8649
|
ctx.lineDashOffset = 0;
|
|
8528
8650
|
let bounds = this.getBounds();
|
|
8651
|
+
this.renderBadges(ctx, bounds);
|
|
8529
8652
|
const iconBoxSize = this._icon ? this.getIconBoxSize() : void 0;
|
|
8530
8653
|
if (this._label) {
|
|
8531
8654
|
this._label.setAutoMaxWidth(this.getLabelContainerBounds(bounds).width);
|
|
@@ -8549,6 +8672,67 @@ class Node extends Element {
|
|
|
8549
8672
|
this.renderLabel(ctx, labelBounds);
|
|
8550
8673
|
this.renderPorts(ctx);
|
|
8551
8674
|
}
|
|
8675
|
+
ensureBadgeImagesLoaded() {
|
|
8676
|
+
for (const badge of this._badges) {
|
|
8677
|
+
const url = badge.iconUrl;
|
|
8678
|
+
if (!url || this._badgeImageCache.has(url)) {
|
|
8679
|
+
continue;
|
|
8680
|
+
}
|
|
8681
|
+
const img = new Image();
|
|
8682
|
+
img.decoding = "async";
|
|
8683
|
+
this._badgeImageCache.set(url, { img, loaded: false });
|
|
8684
|
+
img.onload = () => {
|
|
8685
|
+
const entry = this._badgeImageCache.get(url);
|
|
8686
|
+
if (entry) {
|
|
8687
|
+
entry.loaded = true;
|
|
8688
|
+
this.markDirty();
|
|
8689
|
+
}
|
|
8690
|
+
};
|
|
8691
|
+
img.onerror = () => {
|
|
8692
|
+
this.markDirty();
|
|
8693
|
+
};
|
|
8694
|
+
img.src = url;
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
renderBadges(ctx, bounds) {
|
|
8698
|
+
if (this._badges.length === 0) {
|
|
8699
|
+
return;
|
|
8700
|
+
}
|
|
8701
|
+
const contentBounds = this.getLabelContainerBounds(bounds);
|
|
8702
|
+
const x0 = contentBounds.x + BADGE_OFFSET;
|
|
8703
|
+
const y0 = contentBounds.y + BADGE_OFFSET;
|
|
8704
|
+
const radius = 2;
|
|
8705
|
+
for (let i = 0; i < this._badges.length; i++) {
|
|
8706
|
+
const badge = this._badges[i];
|
|
8707
|
+
if (badge === void 0) continue;
|
|
8708
|
+
const x = x0 + i * (BADGE_SIZE + BADGE_GAP);
|
|
8709
|
+
const isHovered = this._hoveredBadgeIndex === i;
|
|
8710
|
+
if (isHovered) {
|
|
8711
|
+
ctx.save();
|
|
8712
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.08)";
|
|
8713
|
+
ctx.beginPath();
|
|
8714
|
+
ctx.roundRect(x, y0, BADGE_SIZE, BADGE_SIZE, radius);
|
|
8715
|
+
ctx.fill();
|
|
8716
|
+
ctx.restore();
|
|
8717
|
+
}
|
|
8718
|
+
const entry = this._badgeImageCache.get(badge.iconUrl);
|
|
8719
|
+
if (entry?.loaded && entry.img.naturalWidth > 0) {
|
|
8720
|
+
ctx.save();
|
|
8721
|
+
const img = entry.img;
|
|
8722
|
+
const sw = img.naturalWidth;
|
|
8723
|
+
const sh = img.naturalHeight;
|
|
8724
|
+
const scale = Math.min(BADGE_SIZE / sw, BADGE_SIZE / sh, 1);
|
|
8725
|
+
const dw = sw * scale;
|
|
8726
|
+
const dh = sh * scale;
|
|
8727
|
+
const dx = x + (BADGE_SIZE - dw) / 2;
|
|
8728
|
+
const dy = y0 + (BADGE_SIZE - dh) / 2;
|
|
8729
|
+
ctx.imageSmoothingEnabled = true;
|
|
8730
|
+
ctx.imageSmoothingQuality = "high";
|
|
8731
|
+
ctx.drawImage(img, 0, 0, sw, sh, dx, dy, dw, dh);
|
|
8732
|
+
ctx.restore();
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8735
|
+
}
|
|
8552
8736
|
/**
|
|
8553
8737
|
* Minimal size required to fit current contents
|
|
8554
8738
|
*/
|