@sequent-org/moodboard 1.4.30 → 1.4.32

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.
Files changed (61) hide show
  1. package/package.json +3 -1
  2. package/src/core/PixiEngine.js +34 -5
  3. package/src/core/bootstrap/CoreInitializer.js +4 -0
  4. package/src/core/commands/CreateConnectorCommand.js +25 -0
  5. package/src/core/commands/GroupMoveCommand.js +2 -2
  6. package/src/core/commands/MoveObjectCommand.js +1 -1
  7. package/src/core/commands/UpdateConnectorCommand.js +38 -0
  8. package/src/core/events/Events.js +1 -0
  9. package/src/mindmap/MindmapCompoundContract.js +1 -0
  10. package/src/moodboard/bootstrap/MoodBoardUiFactory.js +14 -0
  11. package/src/moodboard/lifecycle/MoodBoardDestroyer.js +18 -0
  12. package/src/objects/ConnectorObject.js +85 -0
  13. package/src/objects/DrawingObject.js +47 -0
  14. package/src/objects/MindmapObject.js +21 -3
  15. package/src/objects/NoteObject.js +16 -8
  16. package/src/objects/ObjectFactory.js +3 -1
  17. package/src/objects/ShapeObject.js +1 -1
  18. package/src/services/ConnectorBindingResolver.js +204 -0
  19. package/src/services/ai/AiClient.js +30 -2
  20. package/src/services/ai/ChatSessionController.js +1 -0
  21. package/src/tools/ToolManager.js +3 -0
  22. package/src/tools/manager/PointerGestureController.js +206 -0
  23. package/src/tools/manager/ToolEventRouter.js +10 -0
  24. package/src/tools/manager/ToolManagerGuards.js +3 -1
  25. package/src/tools/manager/ToolManagerLifecycle.js +70 -58
  26. package/src/tools/object-tools/ConnectorTool.js +147 -0
  27. package/src/tools/object-tools/PlacementTool.js +2 -2
  28. package/src/tools/object-tools/connector/ConnectorDragController.js +296 -0
  29. package/src/tools/object-tools/connector/connectorGesture.js +108 -0
  30. package/src/tools/object-tools/placement/GhostController.js +4 -4
  31. package/src/tools/object-tools/placement/PlacementEventsBridge.js +2 -2
  32. package/src/tools/object-tools/placement/PlacementInputRouter.js +5 -5
  33. package/src/tools/object-tools/selection/MindmapInlineEditorController.js +11 -2
  34. package/src/tools/object-tools/selection/SelectInputRouter.js +33 -4
  35. package/src/tools/object-tools/selection/SelectToolLifecycleController.js +12 -0
  36. package/src/tools/object-tools/selection/SelectToolSetup.js +3 -0
  37. package/src/tools/object-tools/selection/TextEditorDomFactory.js +1 -2
  38. package/src/tools/object-tools/selection/TextEditorSyncService.js +4 -4
  39. package/src/tools/object-tools/selection/TextInlineEditorController.js +21 -3
  40. package/src/tools/object-tools/selection/TransformInteractionController.js +4 -6
  41. package/src/ui/HtmlTextLayer.js +212 -5
  42. package/src/ui/animation/HoverLiftController.js +395 -0
  43. package/src/ui/chat/ChatComposer.js +1 -10
  44. package/src/ui/chat/ChatExtendedPromptModal.js +1 -12
  45. package/src/ui/chat/ChatWindow.js +167 -36
  46. package/src/ui/chat/ChatWindowRenderer.js +1 -8
  47. package/src/ui/chat/icons.js +17 -5
  48. package/src/ui/connectors/ConnectionAnchorsLayer.js +231 -0
  49. package/src/ui/connectors/ConnectorLayer.js +251 -0
  50. package/src/ui/handles/HandlesDomRenderer.js +11 -7
  51. package/src/ui/handles/HandlesInteractionController.js +65 -34
  52. package/src/ui/handles/HandlesPositioningService.js +41 -6
  53. package/src/ui/mindmap/MindmapCollapseGraph.js +169 -0
  54. package/src/ui/mindmap/MindmapCollapseLayer.js +380 -0
  55. package/src/ui/mindmap/MindmapConnectionLayer.js +50 -25
  56. package/src/ui/mindmap/MindmapHtmlTextLayer.js +223 -2
  57. package/src/ui/mindmap/MindmapLayoutConfig.js +12 -0
  58. package/src/ui/styles/chat.css +2 -37
  59. package/src/ui/styles/toolbar.css +6 -0
  60. package/src/ui/styles/workspace.css +83 -21
  61. package/src/ui/toolbar/ToolbarPopupsController.js +1 -1
@@ -0,0 +1,380 @@
1
+ import { gsap } from 'gsap';
2
+ import * as PIXI from 'pixi.js';
3
+ import { Events } from '../../core/events/Events.js';
4
+ import {
5
+ getChildrenSidesWithChildren,
6
+ getDescendantIds,
7
+ isHiddenByCollapsedAncestor,
8
+ countVisibleDescendantsForBadge,
9
+ } from './MindmapCollapseGraph.js';
10
+
11
+ const MINDMAP_TYPE = 'mindmap';
12
+ const BTN_SIZE = 20;
13
+
14
+ function asNumber(v, fallback = 0) {
15
+ return Number.isFinite(v) ? v : fallback;
16
+ }
17
+
18
+ function getWorldRect(node) {
19
+ const x = asNumber(node?.position?.x);
20
+ const y = asNumber(node?.position?.y);
21
+ const w = Math.max(1, Math.round(asNumber(node?.width, asNumber(node?.properties?.width, 1))));
22
+ const h = Math.max(1, Math.round(asNumber(node?.height, asNumber(node?.properties?.height, 1))));
23
+ return { x, y, w, h };
24
+ }
25
+
26
+ function anchorWorldPoint(rect, side) {
27
+ if (side === 'right') return { x: rect.x + rect.w, y: rect.y + rect.h / 2 };
28
+ if (side === 'left') return { x: rect.x, y: rect.y + rect.h / 2 };
29
+ if (side === 'bottom') return { x: rect.x + rect.w / 2, y: rect.y + rect.h };
30
+ return { x: rect.x + rect.w / 2, y: rect.y };
31
+ }
32
+
33
+ export class MindmapCollapseLayer {
34
+ constructor(container, eventBus, core) {
35
+ this.container = container;
36
+ this.eventBus = eventBus;
37
+ this.core = core;
38
+ this.layer = null;
39
+ this.subscriptions = [];
40
+ this._eventsAttached = false;
41
+ this.hoveredObjectId = null;
42
+ this._gsapCtx = null;
43
+ /** Map<"nodeId:side", HTMLElement> */
44
+ this._buttons = new Map();
45
+ /** Tracks which keys have the button currently shown */
46
+ this._shown = new Set();
47
+ /** Ключ кнопки, над которой сейчас курсор (чтобы не прятать до клика) */
48
+ this._btnHoverKey = null;
49
+ this._onContainerMove = null;
50
+ this._onContainerLeave = null;
51
+ this._moveRaf = 0;
52
+ this._lastMoveEvent = null;
53
+ }
54
+
55
+ attach() {
56
+ this.layer = document.createElement('div');
57
+ this.layer.className = 'mb-mindmap-collapse-layer';
58
+ Object.assign(this.layer.style, {
59
+ position: 'absolute',
60
+ inset: '0',
61
+ overflow: 'hidden',
62
+ pointerEvents: 'none',
63
+ zIndex: '12',
64
+ });
65
+ this.container.appendChild(this.layer);
66
+ this._gsapCtx = gsap.context(() => {}, this.layer);
67
+ this._attachEvents();
68
+
69
+ // Hover для майндмап-нод нельзя брать из Events.Object.Hover роутера:
70
+ // HTML-текст ноды (pointer-events:auto) перехватывает события и PIXI не
71
+ // получает pointermove над нодой. Поэтому слушаем pointermove на контейнере
72
+ // (события всплывают и от span текста, и от canvas) и резолвим ноду через HitTest.
73
+ this._onContainerMove = (e) => {
74
+ this._lastMoveEvent = e;
75
+ if (this._moveRaf) return;
76
+ this._moveRaf = requestAnimationFrame(() => {
77
+ this._moveRaf = 0;
78
+ this._handlePointerMove(this._lastMoveEvent);
79
+ });
80
+ };
81
+ this._onContainerLeave = () => {
82
+ this.hoveredObjectId = null;
83
+ this._syncVisibility();
84
+ };
85
+ this.container.addEventListener('pointermove', this._onContainerMove);
86
+ this.container.addEventListener('pointerleave', this._onContainerLeave);
87
+
88
+ this._rebuildAll();
89
+ }
90
+
91
+ destroy() {
92
+ this._detachEvents();
93
+ if (this._moveRaf) {
94
+ cancelAnimationFrame(this._moveRaf);
95
+ this._moveRaf = 0;
96
+ }
97
+ if (this._onContainerMove) {
98
+ this.container.removeEventListener('pointermove', this._onContainerMove);
99
+ this._onContainerMove = null;
100
+ }
101
+ if (this._onContainerLeave) {
102
+ this.container.removeEventListener('pointerleave', this._onContainerLeave);
103
+ this._onContainerLeave = null;
104
+ }
105
+ if (this._gsapCtx) {
106
+ this._gsapCtx.revert();
107
+ this._gsapCtx = null;
108
+ }
109
+ if (this.layer) {
110
+ this.layer.remove();
111
+ this.layer = null;
112
+ }
113
+ this._buttons.clear();
114
+ this._shown.clear();
115
+ }
116
+
117
+ _handlePointerMove(e) {
118
+ if (!e || !this.layer) return;
119
+ // Курсор над самой кнопкой — состояние держит btnHover, ничего не пересчитываем.
120
+ if (this.layer.contains(e.target)) return;
121
+
122
+ const view = this.core?.pixi?.app?.view;
123
+ if (!view) return;
124
+ const rect = view.getBoundingClientRect();
125
+ const x = e.clientX - rect.left;
126
+ const y = e.clientY - rect.top;
127
+
128
+ const hitData = { x, y, result: null };
129
+ this.eventBus.emit(Events.Tool.HitTest, hitData);
130
+ const hitId = typeof hitData.result?.object === 'string'
131
+ ? hitData.result.object
132
+ : (hitData.result?.object?.id || null);
133
+
134
+ let mindmapId = null;
135
+ if (hitId) {
136
+ const obj = this._objects().find((o) => o?.id === hitId);
137
+ if (obj?.type === MINDMAP_TYPE) mindmapId = hitId;
138
+ }
139
+
140
+ if (this.hoveredObjectId !== mindmapId) {
141
+ this.hoveredObjectId = mindmapId;
142
+ this._syncVisibility();
143
+ }
144
+ }
145
+
146
+ _attachEvents() {
147
+ if (this._eventsAttached) return;
148
+ const rebuild = () => this._rebuildAll();
149
+ const reposition = () => this._updatePositions();
150
+ const bindings = [
151
+ [Events.Object.Created, rebuild],
152
+ [Events.Object.Deleted, rebuild],
153
+ [Events.Object.Updated, rebuild],
154
+ [Events.Object.StateChanged, rebuild],
155
+ [Events.History.Changed, rebuild],
156
+ [Events.Board.Loaded, rebuild],
157
+ [Events.Tool.DragUpdate, reposition],
158
+ [Events.Tool.DragEnd, reposition],
159
+ [Events.Tool.ResizeUpdate, reposition],
160
+ [Events.Tool.ResizeEnd, reposition],
161
+ [Events.Tool.GroupDragUpdate, reposition],
162
+ [Events.Tool.GroupResizeUpdate, reposition],
163
+ [Events.Tool.PanUpdate, reposition],
164
+ [Events.UI.ZoomPercent, reposition],
165
+ [Events.Object.TransformUpdated, reposition],
166
+ ];
167
+ bindings.forEach(([ev, fn]) => {
168
+ this.eventBus.on(ev, fn);
169
+ this.subscriptions.push([ev, fn]);
170
+ });
171
+ this._eventsAttached = true;
172
+ }
173
+
174
+ _detachEvents() {
175
+ if (!this._eventsAttached) return;
176
+ if (typeof this.eventBus?.off === 'function') {
177
+ this.subscriptions.forEach(([ev, fn]) => this.eventBus.off(ev, fn));
178
+ }
179
+ this.subscriptions = [];
180
+ this._eventsAttached = false;
181
+ }
182
+
183
+ _objects() {
184
+ return this.core?.state?.state?.objects || [];
185
+ }
186
+
187
+ _rebuildAll() {
188
+ if (!this.layer) return;
189
+ const objects = this._objects();
190
+ const mindmaps = objects.filter((o) => o?.type === MINDMAP_TYPE);
191
+
192
+ // Collect needed keys
193
+ const needed = new Set();
194
+ mindmaps.forEach((node) => {
195
+ getChildrenSidesWithChildren(objects, node.id)
196
+ .forEach((side) => needed.add(`${node.id}:${side}`));
197
+ });
198
+
199
+ // Remove stale
200
+ for (const key of this._buttons.keys()) {
201
+ if (!needed.has(key)) {
202
+ this._buttons.get(key)?.remove();
203
+ this._buttons.delete(key);
204
+ this._shown.delete(key);
205
+ }
206
+ }
207
+
208
+ // Create missing
209
+ needed.forEach((key) => {
210
+ if (!this._buttons.has(key)) {
211
+ const colonIdx = key.indexOf(':');
212
+ const nodeId = key.slice(0, colonIdx);
213
+ const side = key.slice(colonIdx + 1);
214
+ this._createButton(nodeId, side, key);
215
+ }
216
+ });
217
+
218
+ this._updatePositions();
219
+ this._syncVisibility();
220
+ }
221
+
222
+ _createButton(nodeId, side, key) {
223
+ const btn = document.createElement('div');
224
+ btn.dataset.nodeId = nodeId;
225
+ btn.dataset.side = side;
226
+ Object.assign(btn.style, {
227
+ position: 'absolute',
228
+ width: `${BTN_SIZE}px`,
229
+ height: `${BTN_SIZE}px`,
230
+ borderRadius: '50%',
231
+ display: 'flex',
232
+ alignItems: 'center',
233
+ justifyContent: 'center',
234
+ cursor: 'pointer',
235
+ pointerEvents: 'auto',
236
+ userSelect: 'none',
237
+ fontSize: '12px',
238
+ fontWeight: '700',
239
+ fontFamily: 'sans-serif',
240
+ lineHeight: '1',
241
+ boxShadow: '0 1px 4px rgba(0,0,0,0.18)',
242
+ visibility: 'hidden',
243
+ opacity: '0',
244
+ });
245
+
246
+ btn.addEventListener('pointerenter', () => {
247
+ this._btnHoverKey = key;
248
+ this._syncVisibility();
249
+ });
250
+ btn.addEventListener('pointerleave', () => {
251
+ if (this._btnHoverKey === key) this._btnHoverKey = null;
252
+ this._syncVisibility();
253
+ });
254
+ btn.addEventListener('pointerdown', (e) => {
255
+ e.stopPropagation();
256
+ e.preventDefault();
257
+ this._toggle(nodeId);
258
+ });
259
+
260
+ this.layer.appendChild(btn);
261
+ this._buttons.set(key, btn);
262
+ gsap.set(btn, { autoAlpha: 0, scale: 0.6 });
263
+ }
264
+
265
+ _applyBtnStyle(btn, nodeId) {
266
+ const objects = this._objects();
267
+ const node = objects.find((o) => o?.id === nodeId);
268
+ const collapsed = node?.properties?.mindmap?.collapsed === true;
269
+ if (collapsed) {
270
+ const count = countVisibleDescendantsForBadge(objects, nodeId);
271
+ btn.textContent = String(count);
272
+ Object.assign(btn.style, {
273
+ background: '#6366f1',
274
+ color: '#fff',
275
+ border: 'none',
276
+ });
277
+ } else {
278
+ btn.textContent = '−';
279
+ Object.assign(btn.style, {
280
+ background: 'rgba(255,255,255,0.94)',
281
+ color: '#374151',
282
+ border: '1.5px solid #d1d5db',
283
+ });
284
+ }
285
+ }
286
+
287
+ _updatePositions() {
288
+ if (!this.layer || !this.core?.pixi) return;
289
+ const worldLayer = this.core.pixi.worldLayer || this.core.pixi.app?.stage;
290
+ const view = this.core.pixi.app?.view;
291
+ if (!worldLayer || !view?.parentElement) return;
292
+
293
+ const containerRect = view.parentElement.getBoundingClientRect();
294
+ const viewRect = view.getBoundingClientRect();
295
+ const offX = viewRect.left - containerRect.left;
296
+ const offY = viewRect.top - containerRect.top;
297
+ const objects = this._objects();
298
+
299
+ this._buttons.forEach((btn, key) => {
300
+ const nodeId = btn.dataset.nodeId;
301
+ const side = btn.dataset.side;
302
+ const node = objects.find((o) => o?.id === nodeId && o?.type === MINDMAP_TYPE);
303
+ if (!node) return;
304
+
305
+ const rect = getWorldRect(node);
306
+ const wp = anchorWorldPoint(rect, side);
307
+ const screen = worldLayer.toGlobal(new PIXI.Point(wp.x, wp.y));
308
+ btn.style.left = `${Math.round(offX + screen.x)}px`;
309
+ btn.style.top = `${Math.round(offY + screen.y)}px`;
310
+ this._applyBtnStyle(btn, nodeId);
311
+ });
312
+ }
313
+
314
+ _syncVisibility() {
315
+ const objects = this._objects();
316
+ this._buttons.forEach((btn, key) => {
317
+ const nodeId = btn.dataset.nodeId;
318
+ const node = objects.find((o) => o?.id === nodeId);
319
+ const collapsed = node?.properties?.mindmap?.collapsed === true;
320
+ const shouldShow = collapsed
321
+ || this.hoveredObjectId === nodeId
322
+ || this._btnHoverKey === key;
323
+
324
+ if (shouldShow && !this._shown.has(key)) {
325
+ this._shown.add(key);
326
+ this._animIn(btn);
327
+ } else if (!shouldShow && this._shown.has(key)) {
328
+ this._shown.delete(key);
329
+ this._animOut(btn);
330
+ }
331
+ });
332
+ }
333
+
334
+ _animIn(btn) {
335
+ if (!this._gsapCtx) return;
336
+ this._gsapCtx.add(() => {
337
+ gsap.fromTo(btn,
338
+ { autoAlpha: 0, scale: 0.6 },
339
+ { autoAlpha: 1, scale: 1, duration: 0.18, ease: 'back.out(2)', overwrite: true }
340
+ );
341
+ });
342
+ }
343
+
344
+ _animOut(btn) {
345
+ if (!this._gsapCtx) return;
346
+ this._gsapCtx.add(() => {
347
+ gsap.to(btn, { autoAlpha: 0, scale: 0.6, duration: 0.12, ease: 'power2.in', overwrite: true });
348
+ });
349
+ }
350
+
351
+ _toggle(nodeId) {
352
+ const objects = this._objects();
353
+ const node = objects.find((o) => o?.id === nodeId && o?.type === MINDMAP_TYPE);
354
+ if (!node) return;
355
+
356
+ if (!node.properties) node.properties = {};
357
+ if (!node.properties.mindmap) node.properties.mindmap = {};
358
+
359
+ const nowCollapsed = !node.properties.mindmap.collapsed;
360
+ node.properties.mindmap.collapsed = nowCollapsed;
361
+
362
+ const descendants = getDescendantIds(objects, nodeId);
363
+ descendants.forEach((descId) => {
364
+ const pixi = this.core?.pixi?.objects?.get?.(descId);
365
+ if (nowCollapsed) {
366
+ if (pixi) pixi.visible = false;
367
+ this.eventBus.emit(Events.Tool.HideObjectText, { objectId: descId });
368
+ } else {
369
+ const stillHidden = isHiddenByCollapsedAncestor(objects, descId);
370
+ if (!stillHidden) {
371
+ if (pixi) pixi.visible = true;
372
+ this.eventBus.emit(Events.Tool.ShowObjectText, { objectId: descId });
373
+ }
374
+ }
375
+ });
376
+
377
+ this.eventBus.emit(Events.Object.Updated, { objectId: nodeId });
378
+ this.core?.state?.markDirty?.();
379
+ }
380
+ }
@@ -1,5 +1,6 @@
1
1
  import * as PIXI from 'pixi.js';
2
2
  import { Events } from '../../core/events/Events.js';
3
+ import { isHiddenByCollapsedAncestor } from './MindmapCollapseGraph.js';
3
4
 
4
5
  const MINDMAP_TYPE = 'mindmap';
5
6
  const SIDE_LEFT = 'left';
@@ -61,20 +62,55 @@ function getChildAttachSide(parentSide) {
61
62
 
62
63
  function getBezierControls(start, end, side) {
63
64
  if (side === SIDE_BOTTOM) {
64
- const spanY = Math.max(30, Math.round(Math.abs(end.y - start.y) * 0.35));
65
+ const spanY = Math.max(30, Math.abs(end.y - start.y) * 0.5);
65
66
  return {
66
67
  cp1: { x: start.x, y: start.y + spanY },
67
68
  cp2: { x: end.x, y: end.y - spanY },
68
69
  };
69
70
  }
70
71
  const dir = side === SIDE_LEFT ? -1 : 1;
71
- const spanX = Math.max(30, Math.round(Math.abs(end.x - start.x) * 0.35));
72
+ const spanX = Math.max(30, Math.abs(end.x - start.x) * 0.5);
72
73
  return {
73
74
  cp1: { x: start.x + spanX * dir, y: start.y },
74
75
  cp2: { x: end.x - spanX * dir, y: end.y },
75
76
  };
76
77
  }
77
78
 
79
+ function cubicPoint(p0, p1, p2, p3, t) {
80
+ const mt = 1 - t;
81
+ const a = mt * mt * mt, b = 3 * mt * mt * t, c = 3 * mt * t * t, d = t * t * t;
82
+ return {
83
+ x: a * p0.x + b * p1.x + c * p2.x + d * p3.x,
84
+ y: a * p0.y + b * p1.y + c * p2.y + d * p3.y,
85
+ };
86
+ }
87
+
88
+ function drawRibbon(g, start, cp1, cp2, end, color, width) {
89
+ const STEPS = 24;
90
+ const half = width / 2;
91
+ const pts = [];
92
+ for (let i = 0; i <= STEPS; i++) {
93
+ pts.push(cubicPoint(start, cp1, cp2, end, i / STEPS));
94
+ }
95
+ const top = [], bottom = [];
96
+ for (let i = 0; i <= STEPS; i++) {
97
+ const p = pts[i];
98
+ const prev = pts[Math.max(0, i - 1)];
99
+ const next = pts[Math.min(STEPS, i + 1)];
100
+ const dx = next.x - prev.x, dy = next.y - prev.y;
101
+ const len = Math.hypot(dx, dy) || 1;
102
+ const nx = -dy / len, ny = dx / len;
103
+ top.push({ x: p.x + nx * half, y: p.y + ny * half });
104
+ bottom.push({ x: p.x - nx * half, y: p.y - ny * half });
105
+ }
106
+ g.beginFill(color, 1);
107
+ g.moveTo(top[0].x, top[0].y);
108
+ for (let i = 1; i <= STEPS; i++) g.lineTo(top[i].x, top[i].y);
109
+ for (let i = STEPS; i >= 0; i--) g.lineTo(bottom[i].x, bottom[i].y);
110
+ g.closePath();
111
+ g.endFill();
112
+ }
113
+
78
114
  function resolveLegacyLink(child, byId, rootByCompoundId) {
79
115
  const childMeta = asMindmapMeta(child);
80
116
  const compoundId = childMeta?.compoundId || null;
@@ -204,6 +240,8 @@ export class MindmapConnectionLayer {
204
240
  this._lastSegments = [];
205
241
 
206
242
  children.forEach((child) => {
243
+ if (isHiddenByCollapsedAncestor(mindmaps, child.id)) return;
244
+
207
245
  const childMeta = asMindmapMeta(child);
208
246
  const { parentId, side } = resolveLegacyLink(child, byId, rootByCompoundId);
209
247
  const parent = parentId ? byId.get(parentId) : null;
@@ -218,30 +256,17 @@ export class MindmapConnectionLayer {
218
256
  const start = nudgeStartOutsideNode(startBase, side);
219
257
  const end = getAnchorPoint(child, getChildAttachSide(side));
220
258
  const { cp1, cp2 } = getBezierControls(start, end, side);
221
- const color = Number(child?.properties?.strokeColor || parent?.properties?.strokeColor || 0x2563EB);
222
-
223
- try {
224
- g.lineStyle({
225
- width: 1,
226
- color,
227
- alpha: 0.95,
228
- alignment: 0,
229
- cap: 'round',
230
- join: 'round',
231
- miterLimit: 2,
232
- });
233
- } catch (_) {
234
- g.lineStyle(1, color, 0.95, 0);
235
- }
236
- g.moveTo(Math.round(start.x), Math.round(start.y));
237
- g.bezierCurveTo(
238
- Math.round(cp1.x),
239
- Math.round(cp1.y),
240
- Math.round(cp2.x),
241
- Math.round(cp2.y),
242
- Math.round(end.x),
243
- Math.round(end.y)
259
+ const color = Number(
260
+ childMeta.branchColor
261
+ ?? parentMeta.branchColor
262
+ ?? child?.properties?.strokeColor
263
+ ?? parent?.properties?.strokeColor
264
+ ?? 0x2563EB
244
265
  );
266
+
267
+ const scale = this.core?.pixi?.worldLayer?.scale?.x || 1;
268
+ const width = 2 / scale;
269
+ drawRibbon(g, start, cp1, cp2, end, color, width);
245
270
  this._lastSegments.push({
246
271
  parentId: parent.id,
247
272
  childId: child.id,