@sequent-org/moodboard 1.4.31 → 1.4.33
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/package.json +5 -1
- package/src/assets/fonts/inter/inter-cyrillic-400-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-cyrillic-500-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-latin-400-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-latin-500-normal.woff2 +0 -0
- package/src/assets/icons/attachments.svg +3 -1
- package/src/assets/icons/comments.svg +2 -2
- package/src/assets/icons/connector.svg +6 -0
- package/src/assets/icons/emoji.svg +6 -1
- package/src/assets/icons/frame.svg +4 -1
- package/src/assets/icons/image.svg +5 -1
- package/src/assets/icons/laser.svg +1 -0
- package/src/assets/icons/lasso.svg +5 -0
- package/src/assets/icons/mindmap.svg +10 -2
- package/src/assets/icons/note.svg +4 -1
- package/src/assets/icons/pan.svg +5 -2
- package/src/assets/icons/pencil.svg +4 -1
- package/src/assets/icons/reactions.svg +5 -0
- package/src/assets/icons/redo.svg +3 -2
- package/src/assets/icons/select.svg +2 -8
- package/src/assets/icons/shapes.svg +5 -1
- package/src/assets/icons/text-add.svg +15 -1
- package/src/assets/icons/undo.svg +3 -2
- package/src/assets/reactions/1f44d.svg +20 -0
- package/src/assets/reactions/1f44e.svg +20 -0
- package/src/assets/reactions/2705.svg +20 -0
- package/src/assets/reactions/274c.svg +19 -0
- package/src/assets/reactions/2753.svg +20 -0
- package/src/assets/reactions/2764.svg +22 -0
- package/src/assets/reactions/2b50.svg +19 -0
- package/src/assets/reactions/plus-one.svg +25 -0
- package/src/core/PixiEngine.js +23 -0
- package/src/core/bootstrap/CoreInitializer.js +43 -0
- package/src/core/commands/GroupDeleteCommand.js +13 -1
- package/src/core/commands/UpdateShapeStyleCommand.js +121 -0
- package/src/core/commands/UpdateTextStyleCommand.js +17 -6
- package/src/core/commands/index.js +3 -0
- package/src/core/events/Events.js +22 -0
- package/src/core/flows/LayerAndViewportFlow.js +1 -0
- package/src/core/flows/ObjectLifecycleFlow.js +155 -7
- package/src/core/index.js +28 -1
- package/src/grid/CrossGridZoomPhases.js +3 -3
- package/src/initNoBundler.js +1 -1
- package/src/moodboard/DataManager.js +28 -0
- package/src/moodboard/MoodBoard.js +27 -0
- package/src/moodboard/bootstrap/MoodBoardInitializer.js +69 -1
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +22 -4
- package/src/moodboard/integration/MoodBoardEventBindings.js +5 -1
- package/src/moodboard/integration/MoodBoardLoadApi.js +10 -1
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +9 -0
- package/src/objects/ConnectorObject.js +2 -2
- package/src/objects/FrameObject.js +119 -59
- package/src/objects/ShapeObject.js +49 -74
- package/src/objects/shape/ShapeDrawer.js +210 -0
- package/src/services/ConnectorBindingResolver.js +112 -0
- package/src/services/ConnectorRouter.js +210 -0
- package/src/services/comments/CommentService.js +344 -0
- package/src/tools/object-tools/CommentTool.js +85 -0
- package/src/tools/object-tools/DrawingTool.js +110 -10
- package/src/tools/object-tools/LaserPointerTool.js +121 -0
- package/src/tools/object-tools/SelectTool.js +25 -1
- package/src/tools/object-tools/TextTool.js +6 -1
- package/src/tools/object-tools/connector/ConnectorDragController.js +50 -3
- package/src/tools/object-tools/connector/connectorGesture.js +33 -19
- package/src/tools/object-tools/placement/PlacementInputRouter.js +22 -1
- package/src/tools/object-tools/selection/BoxSelectController.js +24 -2
- package/src/tools/object-tools/selection/FrameTitleInlineEditorController.js +139 -0
- package/src/tools/object-tools/selection/InlineEditorController.js +12 -0
- package/src/tools/object-tools/selection/InlineEditorDomFactory.js +36 -0
- package/src/tools/object-tools/selection/LassoSelectController.js +125 -0
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +1 -0
- package/src/tools/object-tools/selection/SelectInputRouter.js +64 -5
- package/src/tools/object-tools/selection/SelectToolLifecycleController.js +11 -1
- package/src/tools/object-tools/selection/SelectToolSetup.js +13 -1
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +46 -12
- package/src/tools/object-tools/selection/TextEditorSyncService.js +1 -0
- package/src/tools/object-tools/selection/TextInlineEditorController.js +65 -6
- package/src/ui/CommentPopover.js +6 -0
- package/src/ui/CommentsBar.js +91 -0
- package/src/ui/ConnectorPropertiesPanel.js +150 -0
- package/src/ui/ContextMenu.js +25 -0
- package/src/ui/DrawingPropertiesPanel.js +362 -0
- package/src/ui/FilePropertiesPanel.js +5 -0
- package/src/ui/FramePropertiesPanel.js +5 -0
- package/src/ui/HtmlTextLayer.js +246 -66
- package/src/ui/NotePropertiesPanel.js +6 -0
- package/src/ui/ShapePropertiesPanel.js +307 -0
- package/src/ui/TextPropertiesPanel.js +100 -1
- package/src/ui/Toolbar.js +25 -2
- package/src/ui/Topbar.js +2 -2
- package/src/ui/animation/HoverLiftController.js +6 -7
- package/src/ui/chat/ChatComposer.js +59 -12
- package/src/ui/chat/ChatExtendedPromptModal.js +1 -12
- package/src/ui/chat/ChatWindow.js +60 -144
- package/src/ui/chat/ChatWindowRenderer.js +1 -8
- package/src/ui/chat/icons.js +0 -4
- package/src/ui/comments/CommentListPanel.js +213 -0
- package/src/ui/comments/CommentPinLayer.js +448 -0
- package/src/ui/comments/CommentThreadPopover.js +539 -0
- package/src/ui/comments/commentFormat.js +32 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelBindings.js +223 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelEventBridge.js +114 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelMapper.js +144 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelRenderer.js +447 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelState.js +61 -0
- package/src/ui/connectors/ConnectionAnchorsLayer.js +1 -0
- package/src/ui/connectors/ConnectorHandlesLayer.js +321 -0
- package/src/ui/connectors/ConnectorLabelLayer.js +334 -0
- package/src/ui/connectors/ConnectorLayer.js +264 -57
- package/src/ui/handles/HandlesDomRenderer.js +5 -13
- package/src/ui/handles/HandlesEventBridge.js +1 -0
- package/src/ui/handles/SingleSelectionHandlesController.js +4 -0
- package/src/ui/mindmap/MindmapCollapseLayer.js +1 -0
- package/src/ui/mindmap/MindmapConnectionLayer.js +1 -0
- package/src/ui/mindmap/MindmapHtmlTextLayer.js +6 -0
- package/src/ui/shape-properties/ShapePropertiesPanelDom.js +533 -0
- package/src/ui/shape-properties/ShapePropertiesPanelSync.js +132 -0
- package/src/ui/styles/chat.css +682 -28
- package/src/ui/styles/index.css +1 -0
- package/src/ui/styles/panels.css +112 -2
- package/src/ui/styles/shape-properties-panel.css +250 -0
- package/src/ui/styles/toolbar.css +7 -2
- package/src/ui/styles/topbar.css +1 -1
- package/src/ui/styles/workspace.css +257 -6
- package/src/ui/text-properties/TextFormatControls.js +88 -0
- package/src/ui/text-properties/TextListRenderer.js +137 -0
- package/src/ui/text-properties/TextPropertiesPanelBindings.js +27 -0
- package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +3 -1
- package/src/ui/text-properties/TextPropertiesPanelMapper.js +56 -0
- package/src/ui/text-properties/TextPropertiesPanelRenderer.js +24 -0
- package/src/ui/text-properties/TextPropertiesPanelState.js +8 -0
- package/src/ui/toolbar/ReactionsPopupController.js +88 -0
- package/src/ui/toolbar/ToolbarActionRouter.js +71 -5
- package/src/ui/toolbar/ToolbarPopupsController.js +120 -118
- package/src/ui/toolbar/ToolbarRenderer.js +9 -1
- package/src/ui/toolbar/ToolbarStateController.js +4 -1
- package/src/utils/iconLoader.js +17 -16
- package/src/utils/markdown.js +14 -0
- package/src/utils/richText.js +125 -0
|
@@ -1,19 +1,60 @@
|
|
|
1
1
|
import * as PIXI from 'pixi.js';
|
|
2
2
|
import { Events } from '../../core/events/Events.js';
|
|
3
3
|
import { ConnectorBindingResolver, distanceToSegment } from '../../services/ConnectorBindingResolver.js';
|
|
4
|
+
import { buildPath, bezierControlPoints, sampleBezier, BEZIER_SAMPLES } from '../../services/ConnectorRouter.js';
|
|
4
5
|
|
|
5
6
|
const HIT_TEST_SCREEN_PX = 8;
|
|
6
|
-
const ARROW_LEN
|
|
7
|
-
const ARROW_HALF
|
|
8
|
-
const DASH_LEN
|
|
9
|
-
const GAP_LEN
|
|
7
|
+
const ARROW_LEN = 12;
|
|
8
|
+
const ARROW_HALF = 5;
|
|
9
|
+
const DASH_LEN = 8;
|
|
10
|
+
const GAP_LEN = 5;
|
|
11
|
+
const ELBOW_RADIUS = 8;
|
|
12
|
+
const CIRCLE_R = 4;
|
|
13
|
+
const DIAMOND_HALF = 5;
|
|
14
|
+
|
|
15
|
+
/** Сколько пикселей отступить от кончика маркера, чтобы линия не заходила внутрь него. */
|
|
16
|
+
function getHeadSetback(kind) {
|
|
17
|
+
if (kind === 'arrow') return ARROW_LEN;
|
|
18
|
+
if (kind === 'triangle') return ARROW_LEN;
|
|
19
|
+
if (kind === 'circle') return CIRCLE_R * 2;
|
|
20
|
+
if (kind === 'diamond') return DIAMOND_HALF * 2;
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
10
23
|
|
|
11
24
|
function asArray(value) {
|
|
12
25
|
return Array.isArray(value) ? value : [];
|
|
13
26
|
}
|
|
14
27
|
|
|
28
|
+
/** Возвращает центр объекта (world) или свободную точку терминала. */
|
|
29
|
+
function getObjectCenter(terminal, target) {
|
|
30
|
+
if (!terminal?.boundId) {
|
|
31
|
+
return { x: terminal?.point?.x ?? 0, y: terminal?.point?.y ?? 0 };
|
|
32
|
+
}
|
|
33
|
+
if (!target) return { x: 0, y: 0 };
|
|
34
|
+
const left = target.position?.x ?? 0;
|
|
35
|
+
const top = target.position?.y ?? 0;
|
|
36
|
+
const w = target.width ?? target.properties?.width ?? 0;
|
|
37
|
+
const h = target.height ?? target.properties?.height ?? 0;
|
|
38
|
+
return { x: left + w / 2, y: top + h / 2 };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Нормализует HeadKind: boolean (обратная совместимость) → строка. */
|
|
42
|
+
function normalizeHeadKind(value) {
|
|
43
|
+
if (value === true) return 'arrow';
|
|
44
|
+
if (value === false) return 'none';
|
|
45
|
+
return typeof value === 'string' ? value : 'none';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeHead(head) {
|
|
49
|
+
if (!head) return { start: 'none', end: 'arrow' };
|
|
50
|
+
return {
|
|
51
|
+
start: normalizeHeadKind(head.start),
|
|
52
|
+
end: normalizeHeadKind(head.end),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
15
56
|
/**
|
|
16
|
-
*
|
|
57
|
+
* Пунктирная линия по одному отрезку.
|
|
17
58
|
* @param {PIXI.Graphics} g
|
|
18
59
|
*/
|
|
19
60
|
function drawDashedLine(g, x1, y1, x2, y2) {
|
|
@@ -42,39 +83,141 @@ function drawDashedLine(g, x1, y1, x2, y2) {
|
|
|
42
83
|
}
|
|
43
84
|
|
|
44
85
|
/**
|
|
45
|
-
* Рисует
|
|
46
|
-
*
|
|
86
|
+
* Рисует наконечник стрелки в tipPt, направление fromPt→tipPt.
|
|
87
|
+
*
|
|
47
88
|
* @param {PIXI.Graphics} g
|
|
48
|
-
* @param {{ x:
|
|
49
|
-
* @param {{ x:
|
|
50
|
-
* @param {number} color
|
|
89
|
+
* @param {{ x:number, y:number }} fromPt предпоследняя точка
|
|
90
|
+
* @param {{ x:number, y:number }} tipPt кончик
|
|
91
|
+
* @param {number} color PIXI-цвет
|
|
92
|
+
* @param {string} kind HeadKind: 'none'|'arrow'|'triangle'|'circle'|'diamond'
|
|
93
|
+
* @param {number} lineWidth толщина линии коннектора (для согласованной толщины наконечника)
|
|
51
94
|
*/
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
const
|
|
95
|
+
function drawHead(g, fromPt, tipPt, color, kind, lineWidth = 2) {
|
|
96
|
+
if (kind === 'none') return;
|
|
97
|
+
const dx = tipPt.x - fromPt.x;
|
|
98
|
+
const dy = tipPt.y - fromPt.y;
|
|
55
99
|
const len = Math.hypot(dx, dy);
|
|
56
100
|
if (len < 1e-6) return;
|
|
57
101
|
const ux = dx / len;
|
|
58
102
|
const uy = dy / len;
|
|
59
|
-
// перпендикуляр
|
|
60
103
|
const px = -uy;
|
|
61
104
|
const py = ux;
|
|
62
|
-
|
|
63
|
-
const by = to.y - uy * ARROW_LEN;
|
|
105
|
+
|
|
64
106
|
g.lineStyle(0);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
107
|
+
|
|
108
|
+
if (kind === 'arrow') {
|
|
109
|
+
// Единый штрих крыло→кончик→крыло: round-join даёт чистый острый кончик,
|
|
110
|
+
// round-cap — аккуратные концы крыльев. Толщина = толщине линии.
|
|
111
|
+
const bx = tipPt.x - ux * ARROW_LEN;
|
|
112
|
+
const by = tipPt.y - uy * ARROW_LEN;
|
|
113
|
+
const w = Math.max(2, lineWidth + 0.5);
|
|
114
|
+
try {
|
|
115
|
+
g.lineStyle({ width: w, color, alpha: 1, cap: 'round', join: 'round' });
|
|
116
|
+
} catch (_) {
|
|
117
|
+
g.lineStyle(w, color, 1);
|
|
118
|
+
}
|
|
119
|
+
g.moveTo(Math.round(bx + px * ARROW_HALF), Math.round(by + py * ARROW_HALF));
|
|
120
|
+
g.lineTo(Math.round(tipPt.x), Math.round(tipPt.y));
|
|
121
|
+
g.lineTo(Math.round(bx - px * ARROW_HALF), Math.round(by - py * ARROW_HALF));
|
|
122
|
+
g.lineStyle(0);
|
|
123
|
+
} else if (kind === 'triangle') {
|
|
124
|
+
const bx = tipPt.x - ux * ARROW_LEN;
|
|
125
|
+
const by = tipPt.y - uy * ARROW_LEN;
|
|
126
|
+
g.beginFill(color, 1);
|
|
127
|
+
g.drawPolygon([
|
|
128
|
+
Math.round(tipPt.x), Math.round(tipPt.y),
|
|
129
|
+
Math.round(bx + px * ARROW_HALF), Math.round(by + py * ARROW_HALF),
|
|
130
|
+
Math.round(bx - px * ARROW_HALF), Math.round(by - py * ARROW_HALF),
|
|
131
|
+
]);
|
|
132
|
+
g.endFill();
|
|
133
|
+
} else if (kind === 'circle') {
|
|
134
|
+
const cx = Math.round(tipPt.x - ux * CIRCLE_R);
|
|
135
|
+
const cy = Math.round(tipPt.y - uy * CIRCLE_R);
|
|
136
|
+
g.beginFill(color, 1);
|
|
137
|
+
g.drawCircle(cx, cy, CIRCLE_R);
|
|
138
|
+
g.endFill();
|
|
139
|
+
} else if (kind === 'diamond') {
|
|
140
|
+
// Ромб: вершина в tipPt, тыл на расстоянии 2×DIAMOND_HALF
|
|
141
|
+
const mx = tipPt.x - ux * DIAMOND_HALF;
|
|
142
|
+
const my = tipPt.y - uy * DIAMOND_HALF;
|
|
143
|
+
g.beginFill(color, 1);
|
|
144
|
+
g.drawPolygon([
|
|
145
|
+
Math.round(tipPt.x), Math.round(tipPt.y),
|
|
146
|
+
Math.round(mx + px * DIAMOND_HALF), Math.round(my + py * DIAMOND_HALF),
|
|
147
|
+
Math.round(tipPt.x - ux * 2 * DIAMOND_HALF), Math.round(tipPt.y - uy * 2 * DIAMOND_HALF),
|
|
148
|
+
Math.round(mx - px * DIAMOND_HALF), Math.round(my - py * DIAMOND_HALF),
|
|
149
|
+
]);
|
|
150
|
+
g.endFill();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Сплошная ломаная; для elbow скругляет углы дугой quadraticCurveTo.
|
|
157
|
+
*
|
|
158
|
+
* @param {PIXI.Graphics} g
|
|
159
|
+
* @param {Array<{x:number,y:number}>} pts
|
|
160
|
+
* @param {boolean} isElbow включить скругление углов
|
|
161
|
+
*/
|
|
162
|
+
function drawPolylineSolid(g, pts, isElbow) {
|
|
163
|
+
if (pts.length < 2) return;
|
|
164
|
+
g.moveTo(pts[0].x, pts[0].y);
|
|
165
|
+
for (let i = 1; i < pts.length - 1; i++) {
|
|
166
|
+
const prev = pts[i - 1];
|
|
167
|
+
const curr = pts[i];
|
|
168
|
+
const next = pts[i + 1];
|
|
169
|
+
if (!isElbow) {
|
|
170
|
+
g.lineTo(curr.x, curr.y);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const dxIn = curr.x - prev.x;
|
|
174
|
+
const dyIn = curr.y - prev.y;
|
|
175
|
+
const lenIn = Math.hypot(dxIn, dyIn);
|
|
176
|
+
const dxOut = next.x - curr.x;
|
|
177
|
+
const dyOut = next.y - curr.y;
|
|
178
|
+
const lenOut = Math.hypot(dxOut, dyOut);
|
|
179
|
+
if (lenIn < 1e-6 || lenOut < 1e-6) {
|
|
180
|
+
g.lineTo(curr.x, curr.y);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const r = Math.min(ELBOW_RADIUS, lenIn / 2, lenOut / 2);
|
|
184
|
+
const iux = dxIn / lenIn;
|
|
185
|
+
const iuy = dyIn / lenIn;
|
|
186
|
+
const oux = dxOut / lenOut;
|
|
187
|
+
const ouy = dyOut / lenOut;
|
|
188
|
+
g.lineTo(Math.round(curr.x - iux * r), Math.round(curr.y - iuy * r));
|
|
189
|
+
g.quadraticCurveTo(curr.x, curr.y,
|
|
190
|
+
Math.round(curr.x + oux * r), Math.round(curr.y + ouy * r));
|
|
191
|
+
}
|
|
192
|
+
g.lineTo(pts[pts.length - 1].x, pts[pts.length - 1].y);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Пунктирная ломаная по массиву точек. */
|
|
196
|
+
function drawPolylineDash(g, pts) {
|
|
197
|
+
for (let i = 1; i < pts.length; i++) {
|
|
198
|
+
drawDashedLine(g, pts[i - 1].x, pts[i - 1].y, pts[i].x, pts[i].y);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Рисует кубическую кривую Безье через bezierCurveTo (или пунктирную аппроксимацию). */
|
|
203
|
+
function drawBezierCurve(g, start, end, isDash, startDir = null, endDir = null) {
|
|
204
|
+
const { cp1, cp2 } = bezierControlPoints(start, end, startDir, endDir);
|
|
205
|
+
if (isDash) {
|
|
206
|
+
const pts = [];
|
|
207
|
+
for (let i = 0; i <= BEZIER_SAMPLES; i++) {
|
|
208
|
+
pts.push(sampleBezier(start, cp1, cp2, end, i / BEZIER_SAMPLES));
|
|
209
|
+
}
|
|
210
|
+
drawPolylineDash(g, pts);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
g.moveTo(start.x, start.y);
|
|
214
|
+
g.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
|
|
72
215
|
}
|
|
73
216
|
|
|
74
217
|
/**
|
|
75
218
|
* ConnectorLayer — слой рендера универсальных коннекторов.
|
|
76
219
|
*
|
|
77
|
-
* Паттерн:
|
|
220
|
+
* Паттерн: один PIXI.Graphics, полная перерисовка на события.
|
|
78
221
|
* Рисует connector-объекты из state.objects в worldLayer.
|
|
79
222
|
* Резолвинг end-point: ConnectorBindingResolver.resolve() двумя проходами
|
|
80
223
|
* (грубый → точный) для корректной проекции на кромку при isExact=false.
|
|
@@ -90,7 +233,7 @@ export class ConnectorLayer {
|
|
|
90
233
|
this.graphics = null;
|
|
91
234
|
this.subscriptions = [];
|
|
92
235
|
this._eventsAttached = false;
|
|
93
|
-
/** @type {Array<{ id: string,
|
|
236
|
+
/** @type {Array<{ id: string, points: Array<{x:number,y:number}> }>} */
|
|
94
237
|
this._lastSegments = [];
|
|
95
238
|
}
|
|
96
239
|
|
|
@@ -132,6 +275,7 @@ export class ConnectorLayer {
|
|
|
132
275
|
[Events.Tool.GroupResizeUpdate, () => this.updateAll()],
|
|
133
276
|
[Events.Tool.RotateUpdate, () => this.updateAll()],
|
|
134
277
|
[Events.Tool.PanUpdate, () => this.updateAll()],
|
|
278
|
+
[Events.Viewport.Changed, () => this.updateAll()],
|
|
135
279
|
[Events.UI.ZoomPercent, () => this.updateAll()],
|
|
136
280
|
[Events.History.Changed, () => this.updateAll()],
|
|
137
281
|
[Events.Board.Loaded, () => this.updateAll()],
|
|
@@ -166,7 +310,7 @@ export class ConnectorLayer {
|
|
|
166
310
|
}
|
|
167
311
|
|
|
168
312
|
if (!this.graphics) {
|
|
169
|
-
this.graphics
|
|
313
|
+
this.graphics = new PIXI.Graphics();
|
|
170
314
|
this.graphics.name = 'connector-layer';
|
|
171
315
|
this.graphics.zIndex = 3;
|
|
172
316
|
const world = this.core?.pixi?.worldLayer || this.core?.pixi?.app?.stage;
|
|
@@ -179,71 +323,134 @@ export class ConnectorLayer {
|
|
|
179
323
|
this._lastSegments = [];
|
|
180
324
|
|
|
181
325
|
connectors.forEach((connector) => {
|
|
182
|
-
const style
|
|
183
|
-
const startTerm
|
|
184
|
-
const endTerm
|
|
326
|
+
const style = connector?.properties?.style ?? {};
|
|
327
|
+
const startTerm = connector?.properties?.start;
|
|
328
|
+
const endTerm = connector?.properties?.end;
|
|
185
329
|
if (!startTerm || !endTerm) return;
|
|
186
330
|
|
|
187
331
|
const startTarget = startTerm.boundId ? (byId.get(startTerm.boundId) ?? null) : null;
|
|
188
332
|
const endTarget = endTerm.boundId ? (byId.get(endTerm.boundId) ?? null) : null;
|
|
189
333
|
|
|
190
|
-
// Двухпроходное резолвание для корректной проекции isExact=false:
|
|
191
|
-
// проход 1 — грубые точки (без взаимной информации)
|
|
192
|
-
const roughStart = ConnectorBindingResolver.resolve(startTerm, startTarget, null);
|
|
193
|
-
const roughEnd = ConnectorBindingResolver.resolve(endTerm, endTarget, null);
|
|
194
|
-
// проход 2 — уточнение с кромочной проекцией
|
|
195
|
-
const start = ConnectorBindingResolver.resolve(startTerm, startTarget, roughEnd);
|
|
196
|
-
const end = ConnectorBindingResolver.resolve(endTerm, endTarget, start);
|
|
197
|
-
|
|
198
|
-
const sx = Math.round(start.x);
|
|
199
|
-
const sy = Math.round(start.y);
|
|
200
|
-
const ex = Math.round(end.x);
|
|
201
|
-
const ey = Math.round(end.y);
|
|
202
|
-
|
|
203
334
|
const color = style.stroke ?? 0x2563EB;
|
|
204
335
|
const width = style.width ?? 2;
|
|
205
336
|
const isDash = !!style.dash;
|
|
206
|
-
const
|
|
337
|
+
const route = style.route ?? 'straight';
|
|
338
|
+
const head = normalizeHead(style.head);
|
|
339
|
+
|
|
340
|
+
let sx, sy, ex, ey, startDir = null, endDir = null;
|
|
341
|
+
|
|
342
|
+
if (route === 'elbow' || route === 'bezier') {
|
|
343
|
+
// Для elbow/bezier — резолв через грань с перпендикулярным выходом
|
|
344
|
+
const startCenter = getObjectCenter(startTerm, startTarget);
|
|
345
|
+
const endCenter = getObjectCenter(endTerm, endTarget);
|
|
346
|
+
const startDesc = ConnectorBindingResolver.resolveWithSide(startTerm, startTarget, endCenter);
|
|
347
|
+
const endDesc = ConnectorBindingResolver.resolveWithSide(endTerm, endTarget, startCenter);
|
|
348
|
+
sx = Math.round(startDesc.point.x);
|
|
349
|
+
sy = Math.round(startDesc.point.y);
|
|
350
|
+
ex = Math.round(endDesc.point.x);
|
|
351
|
+
ey = Math.round(endDesc.point.y);
|
|
352
|
+
startDir = startDesc.dir;
|
|
353
|
+
endDir = endDesc.dir;
|
|
354
|
+
} else {
|
|
355
|
+
// straight: двухпроходный резолв (кромочная проекция по лучу центр-центр)
|
|
356
|
+
const roughStart = ConnectorBindingResolver.resolve(startTerm, startTarget, null);
|
|
357
|
+
const roughEnd = ConnectorBindingResolver.resolve(endTerm, endTarget, null);
|
|
358
|
+
const start = ConnectorBindingResolver.resolve(startTerm, startTarget, roughEnd);
|
|
359
|
+
const end = ConnectorBindingResolver.resolve(endTerm, endTarget, start);
|
|
360
|
+
sx = Math.round(start.x);
|
|
361
|
+
sy = Math.round(start.y);
|
|
362
|
+
ex = Math.round(end.x);
|
|
363
|
+
ey = Math.round(end.y);
|
|
364
|
+
}
|
|
207
365
|
|
|
208
366
|
try {
|
|
209
|
-
g.lineStyle({ width, color, alpha: 1, alignment: 0, cap: 'round', join: 'round' });
|
|
367
|
+
g.lineStyle({ width, color, alpha: 1, alignment: 0.5, cap: 'round', join: 'round' });
|
|
210
368
|
} catch (_) {
|
|
211
|
-
g.lineStyle(width, color, 1, 0);
|
|
369
|
+
g.lineStyle(width, color, 1, 0.5);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const pts = buildPath({ x: sx, y: sy }, { x: ex, y: ey }, route, startDir, endDir);
|
|
373
|
+
|
|
374
|
+
// Для рисования линии: укорачиваем концы ровно до основания маркера,
|
|
375
|
+
// чтобы толстый stroke не заходил внутрь наконечника.
|
|
376
|
+
// Оригинальный pts используется только для drawHead (кончик остаётся точным).
|
|
377
|
+
const drawPts = pts.slice();
|
|
378
|
+
if (drawPts.length >= 2) {
|
|
379
|
+
if (head.end !== 'none') {
|
|
380
|
+
const n = drawPts.length;
|
|
381
|
+
const tp = drawPts[n - 1];
|
|
382
|
+
const fp = drawPts[n - 2];
|
|
383
|
+
const sb = getHeadSetback(head.end);
|
|
384
|
+
const dx = tp.x - fp.x;
|
|
385
|
+
const dy = tp.y - fp.y;
|
|
386
|
+
const len = Math.hypot(dx, dy);
|
|
387
|
+
if (sb > 0 && len > sb) {
|
|
388
|
+
drawPts[n - 1] = {
|
|
389
|
+
x: Math.round(tp.x - (dx / len) * sb),
|
|
390
|
+
y: Math.round(tp.y - (dy / len) * sb),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (head.start !== 'none') {
|
|
395
|
+
const tp = drawPts[0];
|
|
396
|
+
const fp = drawPts[1];
|
|
397
|
+
const sb = getHeadSetback(head.start);
|
|
398
|
+
const dx = tp.x - fp.x;
|
|
399
|
+
const dy = tp.y - fp.y;
|
|
400
|
+
const len = Math.hypot(dx, dy);
|
|
401
|
+
if (sb > 0 && len > sb) {
|
|
402
|
+
drawPts[0] = {
|
|
403
|
+
x: Math.round(tp.x - (dx / len) * sb),
|
|
404
|
+
y: Math.round(tp.y - (dy / len) * sb),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
212
408
|
}
|
|
213
409
|
|
|
214
|
-
|
|
215
|
-
|
|
410
|
+
const drawStart = drawPts[0];
|
|
411
|
+
const drawEnd = drawPts[drawPts.length - 1];
|
|
412
|
+
|
|
413
|
+
if (route === 'bezier') {
|
|
414
|
+
drawBezierCurve(g, drawStart, drawEnd, isDash, startDir, endDir);
|
|
415
|
+
} else if (isDash) {
|
|
416
|
+
drawPolylineDash(g, drawPts);
|
|
216
417
|
} else {
|
|
217
|
-
g
|
|
218
|
-
g.lineTo(ex, ey);
|
|
418
|
+
drawPolylineSolid(g, drawPts, route === 'elbow');
|
|
219
419
|
}
|
|
220
420
|
|
|
221
|
-
if (head
|
|
222
|
-
|
|
421
|
+
if (head.end !== 'none' && pts.length >= 2) {
|
|
422
|
+
drawHead(g, pts[pts.length - 2], pts[pts.length - 1], color, head.end, width);
|
|
423
|
+
}
|
|
424
|
+
if (head.start !== 'none' && pts.length >= 2) {
|
|
425
|
+
drawHead(g, pts[1], pts[0], color, head.start, width);
|
|
426
|
+
}
|
|
223
427
|
|
|
224
|
-
this._lastSegments.push({ id: connector.id,
|
|
428
|
+
this._lastSegments.push({ id: connector.id, points: pts });
|
|
225
429
|
});
|
|
226
430
|
}
|
|
227
431
|
|
|
228
432
|
/**
|
|
229
433
|
* Возвращает id ближайшего коннектора, если worldPoint в пределах порога.
|
|
230
434
|
* Порог задан в экранных пикселях, пересчитывается в world через текущий scale.
|
|
435
|
+
* Проверяет каждую пару соседних точек сохранённого пути (ломаная/аппроксимация).
|
|
231
436
|
*
|
|
232
437
|
* @param {{ x: number, y: number }} worldPoint
|
|
233
438
|
* @returns {string|null}
|
|
234
439
|
*/
|
|
235
440
|
hitTest(worldPoint) {
|
|
236
441
|
if (this._lastSegments.length === 0) return null;
|
|
237
|
-
// worldLayer.scale.x = zoom; 1 screen px = 1/scale world units
|
|
238
442
|
const scale = this.core?.pixi?.worldLayer?.scale?.x ?? 1;
|
|
239
443
|
const worldThreshold = HIT_TEST_SCREEN_PX / scale;
|
|
240
444
|
let closest = null;
|
|
241
445
|
let minDist = worldThreshold;
|
|
242
446
|
for (const seg of this._lastSegments) {
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
447
|
+
const pts = seg.points;
|
|
448
|
+
for (let i = 1; i < pts.length; i++) {
|
|
449
|
+
const d = distanceToSegment(worldPoint, pts[i - 1], pts[i]);
|
|
450
|
+
if (d < minDist) {
|
|
451
|
+
minDist = d;
|
|
452
|
+
closest = seg.id;
|
|
453
|
+
}
|
|
247
454
|
}
|
|
248
455
|
}
|
|
249
456
|
return closest;
|
|
@@ -68,13 +68,9 @@ function relayoutMindmapBranchLevel({ core, eventBus, parentId, side }) {
|
|
|
68
68
|
});
|
|
69
69
|
if (siblings.length === 0) return;
|
|
70
70
|
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const worldScale = worldLayer?.scale?.x || 1;
|
|
75
|
-
const baseGapWorld = Math.max(1, Math.round((10 * rendererRes) / worldScale));
|
|
76
|
-
const gapWorld = Math.max(1, Math.round(baseGapWorld * MINDMAP_CHILD_GAP_MULTIPLIER));
|
|
77
|
-
const verticalGapWorld = Math.max(1, Math.round(baseGapWorld * MINDMAP_CHILD_VERTICAL_GAP_MULTIPLIER));
|
|
71
|
+
const baseGapWorld = 10;
|
|
72
|
+
const gapWorld = baseGapWorld * MINDMAP_CHILD_GAP_MULTIPLIER;
|
|
73
|
+
const verticalGapWorld = Math.max(1, baseGapWorld * MINDMAP_CHILD_VERTICAL_GAP_MULTIPLIER);
|
|
78
74
|
|
|
79
75
|
const byParentBySide = new Map();
|
|
80
76
|
const childrenByParent = new Map();
|
|
@@ -1349,12 +1345,8 @@ export class HandlesDomRenderer {
|
|
|
1349
1345
|
|
|
1350
1346
|
if (isMindmapTarget) {
|
|
1351
1347
|
const emitChildMindmapFromSource = (direction) => {
|
|
1352
|
-
const
|
|
1353
|
-
const
|
|
1354
|
-
const rendererRes = app?.renderer?.resolution || 1;
|
|
1355
|
-
const worldScale = worldLayer?.scale?.x || 1;
|
|
1356
|
-
const baseGapWorld = Math.max(1, Math.round((10 * rendererRes) / worldScale));
|
|
1357
|
-
const gapWorld = Math.max(1, Math.round(baseGapWorld * MINDMAP_CHILD_GAP_MULTIPLIER));
|
|
1348
|
+
const baseGapWorld = 10;
|
|
1349
|
+
const gapWorld = baseGapWorld * MINDMAP_CHILD_GAP_MULTIPLIER;
|
|
1358
1350
|
const childWidth = Math.max(1, Math.round(MINDMAP_LAYOUT.width * MINDMAP_CHILD_WIDTH_FACTOR));
|
|
1359
1351
|
const childHeight = Math.max(1, Math.round(MINDMAP_LAYOUT.height * MINDMAP_CHILD_HEIGHT_FACTOR));
|
|
1360
1352
|
const childPaddingX = Math.max(1, Math.round(MINDMAP_LAYOUT.paddingX * MINDMAP_CHILD_PADDING_FACTOR));
|
|
@@ -113,6 +113,7 @@ export class HandlesEventBridge {
|
|
|
113
113
|
}],
|
|
114
114
|
[Events.UI.ZoomPercent, () => this.host.update()],
|
|
115
115
|
[Events.Tool.PanUpdate, () => this.host.update()],
|
|
116
|
+
[Events.Viewport.Changed, () => this.host.update()],
|
|
116
117
|
[Events.History.Changed, (data) => {
|
|
117
118
|
if (data?.lastUndone || data?.lastRedone) {
|
|
118
119
|
this.host._endGroupRotationPreview();
|
|
@@ -15,6 +15,10 @@ export class SingleSelectionHandlesController {
|
|
|
15
15
|
this.host.hide();
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
+
if (mb.type === 'connector') {
|
|
19
|
+
this.host.hide();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
18
22
|
|
|
19
23
|
const worldBounds = this.host.positioningService.getSingleSelectionWorldBounds(id, pixi);
|
|
20
24
|
this.host._showBounds(worldBounds, id);
|
|
@@ -161,6 +161,7 @@ export class MindmapCollapseLayer {
|
|
|
161
161
|
[Events.Tool.GroupDragUpdate, reposition],
|
|
162
162
|
[Events.Tool.GroupResizeUpdate, reposition],
|
|
163
163
|
[Events.Tool.PanUpdate, reposition],
|
|
164
|
+
[Events.Viewport.Changed, reposition],
|
|
164
165
|
[Events.UI.ZoomPercent, reposition],
|
|
165
166
|
[Events.Object.TransformUpdated, reposition],
|
|
166
167
|
];
|
|
@@ -182,6 +182,7 @@ export class MindmapConnectionLayer {
|
|
|
182
182
|
[Events.Tool.GroupResizeUpdate, () => this.updateAll()],
|
|
183
183
|
[Events.Tool.RotateUpdate, () => this.updateAll()],
|
|
184
184
|
[Events.Tool.PanUpdate, () => this.updateAll()],
|
|
185
|
+
[Events.Viewport.Changed, () => this.updateAll()],
|
|
185
186
|
[Events.UI.ZoomPercent, () => this.updateAll()],
|
|
186
187
|
[Events.History.Changed, () => this.updateAll()],
|
|
187
188
|
[Events.Board.Loaded, () => this.updateAll()],
|
|
@@ -114,6 +114,8 @@ export class MindmapHtmlTextLayer {
|
|
|
114
114
|
|
|
115
115
|
this.eventBus.on(Events.UI.ZoomPercent, () => this.updateAll());
|
|
116
116
|
this.eventBus.on(Events.Tool.PanUpdate, () => this.updateAll());
|
|
117
|
+
this._onViewportChanged = () => this.updateAll();
|
|
118
|
+
this.eventBus.on(Events.Viewport.Changed, this._onViewportChanged);
|
|
117
119
|
this.eventBus.on(Events.UI.TextEditEnd, ({ objectId }) => {
|
|
118
120
|
if (objectId && this.idToEl.has(objectId)) this._scheduleAutoFit(objectId);
|
|
119
121
|
});
|
|
@@ -178,6 +180,10 @@ export class MindmapHtmlTextLayer {
|
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
destroy() {
|
|
183
|
+
if (this._onViewportChanged && this.eventBus) {
|
|
184
|
+
this.eventBus.off(Events.Viewport.Changed, this._onViewportChanged);
|
|
185
|
+
this._onViewportChanged = null;
|
|
186
|
+
}
|
|
181
187
|
for (const [id, state] of this._hoverStates) {
|
|
182
188
|
gsap.killTweensOf(state);
|
|
183
189
|
this._detachPixiHover(id);
|