@sequent-org/moodboard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/package.json +44 -0
  2. package/src/assets/icons/README.md +105 -0
  3. package/src/assets/icons/attachments.svg +3 -0
  4. package/src/assets/icons/clear.svg +5 -0
  5. package/src/assets/icons/comments.svg +3 -0
  6. package/src/assets/icons/emoji.svg +6 -0
  7. package/src/assets/icons/frame.svg +3 -0
  8. package/src/assets/icons/image.svg +3 -0
  9. package/src/assets/icons/note.svg +3 -0
  10. package/src/assets/icons/pan.svg +3 -0
  11. package/src/assets/icons/pencil.svg +3 -0
  12. package/src/assets/icons/redo.svg +3 -0
  13. package/src/assets/icons/select.svg +9 -0
  14. package/src/assets/icons/shapes.svg +3 -0
  15. package/src/assets/icons/text-add.svg +3 -0
  16. package/src/assets/icons/topbar/README.md +39 -0
  17. package/src/assets/icons/topbar/grid-cross.svg +6 -0
  18. package/src/assets/icons/topbar/grid-dot.svg +3 -0
  19. package/src/assets/icons/topbar/grid-line.svg +3 -0
  20. package/src/assets/icons/topbar/grid-off.svg +3 -0
  21. package/src/assets/icons/topbar/paint.svg +3 -0
  22. package/src/assets/icons/undo.svg +3 -0
  23. package/src/core/ApiClient.js +309 -0
  24. package/src/core/EventBus.js +42 -0
  25. package/src/core/HistoryManager.js +261 -0
  26. package/src/core/KeyboardManager.js +710 -0
  27. package/src/core/PixiEngine.js +439 -0
  28. package/src/core/SaveManager.js +381 -0
  29. package/src/core/StateManager.js +64 -0
  30. package/src/core/commands/BaseCommand.js +68 -0
  31. package/src/core/commands/CopyObjectCommand.js +44 -0
  32. package/src/core/commands/CreateObjectCommand.js +46 -0
  33. package/src/core/commands/DeleteObjectCommand.js +146 -0
  34. package/src/core/commands/EditFileNameCommand.js +107 -0
  35. package/src/core/commands/GroupMoveCommand.js +47 -0
  36. package/src/core/commands/GroupReorderZCommand.js +74 -0
  37. package/src/core/commands/GroupResizeCommand.js +37 -0
  38. package/src/core/commands/GroupRotateCommand.js +41 -0
  39. package/src/core/commands/MoveObjectCommand.js +89 -0
  40. package/src/core/commands/PasteObjectCommand.js +103 -0
  41. package/src/core/commands/ReorderZCommand.js +45 -0
  42. package/src/core/commands/ResizeObjectCommand.js +135 -0
  43. package/src/core/commands/RotateObjectCommand.js +70 -0
  44. package/src/core/commands/index.js +14 -0
  45. package/src/core/events/Events.js +147 -0
  46. package/src/core/index.js +1632 -0
  47. package/src/core/rendering/GeometryUtils.js +89 -0
  48. package/src/core/rendering/HitTestManager.js +186 -0
  49. package/src/core/rendering/LayerManager.js +137 -0
  50. package/src/core/rendering/ObjectRenderer.js +363 -0
  51. package/src/core/rendering/PixiRenderer.js +140 -0
  52. package/src/core/rendering/index.js +9 -0
  53. package/src/grid/BaseGrid.js +164 -0
  54. package/src/grid/CrossGrid.js +75 -0
  55. package/src/grid/DotGrid.js +148 -0
  56. package/src/grid/GridFactory.js +173 -0
  57. package/src/grid/LineGrid.js +115 -0
  58. package/src/index.js +2 -0
  59. package/src/moodboard/ActionHandler.js +114 -0
  60. package/src/moodboard/DataManager.js +114 -0
  61. package/src/moodboard/MoodBoard.js +359 -0
  62. package/src/moodboard/WorkspaceManager.js +103 -0
  63. package/src/objects/BaseObject.js +1 -0
  64. package/src/objects/CommentObject.js +115 -0
  65. package/src/objects/DrawingObject.js +114 -0
  66. package/src/objects/EmojiObject.js +98 -0
  67. package/src/objects/FileObject.js +318 -0
  68. package/src/objects/FrameObject.js +127 -0
  69. package/src/objects/ImageObject.js +72 -0
  70. package/src/objects/NoteObject.js +227 -0
  71. package/src/objects/ObjectFactory.js +61 -0
  72. package/src/objects/ShapeObject.js +134 -0
  73. package/src/objects/StampObject.js +0 -0
  74. package/src/objects/StickerObject.js +0 -0
  75. package/src/objects/TextObject.js +123 -0
  76. package/src/services/BoardService.js +85 -0
  77. package/src/services/FileUploadService.js +398 -0
  78. package/src/services/FrameService.js +138 -0
  79. package/src/services/ImageUploadService.js +246 -0
  80. package/src/services/ZOrderManager.js +50 -0
  81. package/src/services/ZoomPanController.js +78 -0
  82. package/src/src.7z +0 -0
  83. package/src/src.zip +0 -0
  84. package/src/src2.zip +0 -0
  85. package/src/tools/AlignmentGuides.js +326 -0
  86. package/src/tools/BaseTool.js +257 -0
  87. package/src/tools/ResizeHandles.js +381 -0
  88. package/src/tools/ToolManager.js +580 -0
  89. package/src/tools/board-tools/PanTool.js +43 -0
  90. package/src/tools/board-tools/ZoomTool.js +393 -0
  91. package/src/tools/object-tools/DrawingTool.js +404 -0
  92. package/src/tools/object-tools/PlacementTool.js +1005 -0
  93. package/src/tools/object-tools/SelectTool.js +2183 -0
  94. package/src/tools/object-tools/TextTool.js +416 -0
  95. package/src/tools/object-tools/selection/BoxSelectController.js +105 -0
  96. package/src/tools/object-tools/selection/GeometryUtils.js +101 -0
  97. package/src/tools/object-tools/selection/GroupDragController.js +61 -0
  98. package/src/tools/object-tools/selection/GroupResizeController.js +90 -0
  99. package/src/tools/object-tools/selection/GroupRotateController.js +61 -0
  100. package/src/tools/object-tools/selection/HandlesSync.js +96 -0
  101. package/src/tools/object-tools/selection/ResizeController.js +68 -0
  102. package/src/tools/object-tools/selection/RotateController.js +58 -0
  103. package/src/tools/object-tools/selection/SelectionModel.js +42 -0
  104. package/src/tools/object-tools/selection/SimpleDragController.js +45 -0
  105. package/src/ui/CommentPopover.js +187 -0
  106. package/src/ui/ContextMenu.js +340 -0
  107. package/src/ui/FilePropertiesPanel.js +298 -0
  108. package/src/ui/FramePropertiesPanel.js +462 -0
  109. package/src/ui/HtmlHandlesLayer.js +778 -0
  110. package/src/ui/HtmlTextLayer.js +279 -0
  111. package/src/ui/MapPanel.js +290 -0
  112. package/src/ui/NotePropertiesPanel.js +502 -0
  113. package/src/ui/SaveStatus.js +250 -0
  114. package/src/ui/TextPropertiesPanel.js +911 -0
  115. package/src/ui/Toolbar.js +1118 -0
  116. package/src/ui/Topbar.js +220 -0
  117. package/src/ui/ZoomPanel.js +116 -0
  118. package/src/ui/styles/workspace.css +854 -0
  119. package/src/utils/colors.js +0 -0
  120. package/src/utils/geometry.js +0 -0
  121. package/src/utils/iconLoader.js +270 -0
  122. package/src/utils/objectIdGenerator.js +17 -0
  123. package/src/utils/topbarIconLoader.js +114 -0
@@ -0,0 +1,326 @@
1
+ import * as PIXI from 'pixi.js';
2
+ import { Events } from '../core/events/Events.js';
3
+
4
+ /**
5
+ * AlignmentGuides - система направляющих линий для выравнивания объектов
6
+ * Показывает пунктирные линии когда объекты выравниваются друг с другом при перетаскивании
7
+ */
8
+ export class AlignmentGuides {
9
+ constructor(eventBus, pixiApp, getObjectsFunction) {
10
+ this.eventBus = eventBus;
11
+ this.pixiApp = pixiApp;
12
+ this.getObjects = getObjectsFunction; // Функция для получения всех объектов
13
+
14
+ // Контейнер для направляющих линий
15
+ this.guidesContainer = new PIXI.Container();
16
+ this.guidesContainer.name = 'alignmentGuides';
17
+ this.guidesContainer.zIndex = 1000; // Выше остальных объектов
18
+
19
+ // Добавляем в слой мира
20
+ if (this.pixiApp.stage.getChildByName('worldLayer')) {
21
+ this.pixiApp.stage.getChildByName('worldLayer').addChild(this.guidesContainer);
22
+ } else {
23
+ this.pixiApp.stage.addChild(this.guidesContainer);
24
+ }
25
+
26
+ // Текущие направляющие
27
+ this.activeGuides = [];
28
+
29
+ // Порог срабатывания (пиксели)
30
+ this.snapThreshold = 3;
31
+
32
+ // Состояние перетаскивания
33
+ this.isDragging = false;
34
+ this.currentDragObject = null;
35
+ this.currentDragObjects = [];
36
+
37
+ this._attachEvents();
38
+
39
+ console.log('AlignmentGuides: Инициализированы направляющие линии выравнивания');
40
+ }
41
+
42
+ _attachEvents() {
43
+ // Одиночное перетаскивание
44
+ this.eventBus.on(Events.Tool.DragStart, (data) => {
45
+ console.log('AlignmentGuides: Начало перетаскивания объекта:', data.object);
46
+ this.isDragging = true;
47
+ this.currentDragObject = data.object;
48
+ this.currentDragObjects = [];
49
+ });
50
+
51
+ this.eventBus.on(Events.Tool.DragUpdate, (data) => {
52
+ if (this.isDragging && this.currentDragObject) {
53
+ this._updateGuides(data.object, data.position);
54
+ }
55
+ });
56
+
57
+ this.eventBus.on(Events.Tool.DragEnd, () => {
58
+ this.isDragging = false;
59
+ this.currentDragObject = null;
60
+ this.currentDragObjects = [];
61
+ this._clearGuides();
62
+ });
63
+
64
+ // Групповое перетаскивание
65
+ this.eventBus.on(Events.Tool.GroupDragStart, (data) => {
66
+ this.isDragging = true;
67
+ this.currentDragObject = null;
68
+ this.currentDragObjects = data.objects || [];
69
+ });
70
+
71
+ this.eventBus.on(Events.Tool.GroupDragUpdate, (data) => {
72
+ if (this.isDragging && this.currentDragObjects.length > 0) {
73
+ this._updateGroupGuides(data.objects, data.delta);
74
+ }
75
+ });
76
+
77
+ this.eventBus.on(Events.Tool.GroupDragEnd, () => {
78
+ this.isDragging = false;
79
+ this.currentDragObject = null;
80
+ this.currentDragObjects = [];
81
+ this._clearGuides();
82
+ });
83
+ }
84
+
85
+ _updateGuides(dragObjectId, newPosition) {
86
+ // Очищаем старые направляющие
87
+ this._clearGuides();
88
+
89
+ if (!this.getObjects) return;
90
+
91
+ const allObjects = this.getObjects();
92
+ const dragObject = allObjects.find(obj => obj.id === dragObjectId);
93
+ if (!dragObject) {
94
+ console.warn('AlignmentGuides: Перетаскиваемый объект не найден:', dragObjectId);
95
+ return;
96
+ }
97
+
98
+ // Вычисляем границы перетаскиваемого объекта
99
+ const dragBounds = this._getObjectBounds(dragObject, newPosition);
100
+ if (!dragBounds) {
101
+ console.warn('AlignmentGuides: Не удалось получить границы перетаскиваемого объекта');
102
+ return;
103
+ }
104
+
105
+ // Ищем совпадения с другими объектами
106
+ const guides = [];
107
+
108
+ for (const obj of allObjects) {
109
+ if (obj.id === dragObjectId) continue; // Пропускаем сам перетаскиваемый объект
110
+
111
+ const objBounds = this._getObjectBounds(obj);
112
+ if (!objBounds) continue;
113
+
114
+ // Проверяем совпадения по горизонтали
115
+ const horizontalGuides = this._checkHorizontalAlignment(dragBounds, objBounds);
116
+ guides.push(...horizontalGuides);
117
+
118
+ // Проверяем совпадения по вертикали
119
+ const verticalGuides = this._checkVerticalAlignment(dragBounds, objBounds);
120
+ guides.push(...verticalGuides);
121
+ }
122
+
123
+ // Отображаем найденные направляющие
124
+ if (guides.length > 0) {
125
+ console.log('AlignmentGuides: Найдено направляющих:', guides.length);
126
+ this._showGuides(guides);
127
+ }
128
+ }
129
+
130
+ _updateGroupGuides(dragObjectIds, delta) {
131
+ // Для группового перетаскивания пока упрощенная логика
132
+ // TODO: Реализовать более сложную логику для групп
133
+ this._clearGuides();
134
+ }
135
+
136
+ _getObjectBounds(objectData, customPosition = null) {
137
+ try {
138
+ const position = customPosition || objectData.position || { x: 0, y: 0 };
139
+
140
+ // Размеры могут быть на верхнем уровне объекта или в properties
141
+ const width = objectData.width ||
142
+ (objectData.properties && objectData.properties.width) ||
143
+ 100;
144
+ const height = objectData.height ||
145
+ (objectData.properties && objectData.properties.height) ||
146
+ 100;
147
+
148
+ return {
149
+ left: position.x,
150
+ right: position.x + width,
151
+ top: position.y,
152
+ bottom: position.y + height,
153
+ centerX: position.x + width / 2,
154
+ centerY: position.y + height / 2,
155
+ width: width,
156
+ height: height
157
+ };
158
+ } catch (error) {
159
+ console.warn('AlignmentGuides: Не удалось получить границы объекта:', objectData);
160
+ return null;
161
+ }
162
+ }
163
+
164
+ _checkHorizontalAlignment(dragBounds, objBounds) {
165
+ const guides = [];
166
+
167
+ // Проверяем выравнивание по левому краю
168
+ if (Math.abs(dragBounds.left - objBounds.left) <= this.snapThreshold) {
169
+ guides.push({
170
+ type: 'vertical',
171
+ x: objBounds.left,
172
+ y1: Math.min(dragBounds.top, objBounds.top) - 20,
173
+ y2: Math.max(dragBounds.bottom, objBounds.bottom) + 20
174
+ });
175
+ }
176
+
177
+ // Проверяем выравнивание по правому краю
178
+ if (Math.abs(dragBounds.right - objBounds.right) <= this.snapThreshold) {
179
+ guides.push({
180
+ type: 'vertical',
181
+ x: objBounds.right,
182
+ y1: Math.min(dragBounds.top, objBounds.top) - 20,
183
+ y2: Math.max(dragBounds.bottom, objBounds.bottom) + 20
184
+ });
185
+ }
186
+
187
+ // Проверяем выравнивание по центру по горизонтали
188
+ if (Math.abs(dragBounds.centerX - objBounds.centerX) <= this.snapThreshold) {
189
+ guides.push({
190
+ type: 'vertical',
191
+ x: objBounds.centerX,
192
+ y1: Math.min(dragBounds.top, objBounds.top) - 20,
193
+ y2: Math.max(dragBounds.bottom, objBounds.bottom) + 20
194
+ });
195
+ }
196
+
197
+ return guides;
198
+ }
199
+
200
+ _checkVerticalAlignment(dragBounds, objBounds) {
201
+ const guides = [];
202
+
203
+ // Проверяем выравнивание по верхнему краю
204
+ if (Math.abs(dragBounds.top - objBounds.top) <= this.snapThreshold) {
205
+ guides.push({
206
+ type: 'horizontal',
207
+ y: objBounds.top,
208
+ x1: Math.min(dragBounds.left, objBounds.left) - 20,
209
+ x2: Math.max(dragBounds.right, objBounds.right) + 20
210
+ });
211
+ }
212
+
213
+ // Проверяем выравнивание по нижнему краю
214
+ if (Math.abs(dragBounds.bottom - objBounds.bottom) <= this.snapThreshold) {
215
+ guides.push({
216
+ type: 'horizontal',
217
+ y: objBounds.bottom,
218
+ x1: Math.min(dragBounds.left, objBounds.left) - 20,
219
+ x2: Math.max(dragBounds.right, objBounds.right) + 20
220
+ });
221
+ }
222
+
223
+ // Проверяем выравнивание по центру по вертикали
224
+ if (Math.abs(dragBounds.centerY - objBounds.centerY) <= this.snapThreshold) {
225
+ guides.push({
226
+ type: 'horizontal',
227
+ y: objBounds.centerY,
228
+ x1: Math.min(dragBounds.left, objBounds.left) - 20,
229
+ x2: Math.max(dragBounds.right, objBounds.right) + 20
230
+ });
231
+ }
232
+
233
+ return guides;
234
+ }
235
+
236
+ _showGuides(guides) {
237
+ // Ограничиваем количество направляющих для производительности
238
+ const maxGuides = 10;
239
+ const guidesToShow = guides.slice(0, maxGuides);
240
+
241
+ for (const guide of guidesToShow) {
242
+ const line = this._createGuideLine(guide);
243
+ if (line) {
244
+ this.guidesContainer.addChild(line);
245
+ this.activeGuides.push(line);
246
+ }
247
+ }
248
+ }
249
+
250
+ _createGuideLine(guide) {
251
+ const graphics = new PIXI.Graphics();
252
+
253
+ // Стиль пунктирной линии
254
+ const color = 0xFF6B6B; // Красноватый цвет как в Figma
255
+ const alpha = 0.8;
256
+ const lineWidth = 1;
257
+
258
+ graphics.lineStyle(lineWidth, color, alpha);
259
+
260
+ if (guide.type === 'vertical') {
261
+ // Вертикальная линия
262
+ this._drawDashedLine(graphics, guide.x, guide.y1, guide.x, guide.y2);
263
+ } else if (guide.type === 'horizontal') {
264
+ // Горизонтальная линия
265
+ this._drawDashedLine(graphics, guide.x1, guide.y, guide.x2, guide.y);
266
+ }
267
+
268
+ return graphics;
269
+ }
270
+
271
+ _drawDashedLine(graphics, x1, y1, x2, y2) {
272
+ const dashLength = 5;
273
+ const gapLength = 3;
274
+
275
+ const totalLength = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
276
+ const angle = Math.atan2(y2 - y1, x2 - x1);
277
+
278
+ let currentLength = 0;
279
+ let isDash = true;
280
+
281
+ while (currentLength < totalLength) {
282
+ const segmentLength = isDash ? dashLength : gapLength;
283
+ const endLength = Math.min(currentLength + segmentLength, totalLength);
284
+
285
+ const startX = x1 + currentLength * Math.cos(angle);
286
+ const startY = y1 + currentLength * Math.sin(angle);
287
+ const endX = x1 + endLength * Math.cos(angle);
288
+ const endY = y1 + endLength * Math.sin(angle);
289
+
290
+ if (isDash) {
291
+ graphics.moveTo(startX, startY);
292
+ graphics.lineTo(endX, endY);
293
+ }
294
+
295
+ currentLength = endLength;
296
+ isDash = !isDash;
297
+ }
298
+ }
299
+
300
+ _clearGuides() {
301
+ // Удаляем все активные направляющие
302
+ for (const guide of this.activeGuides) {
303
+ if (guide.parent) {
304
+ guide.parent.removeChild(guide);
305
+ }
306
+ guide.destroy();
307
+ }
308
+ this.activeGuides = [];
309
+ }
310
+
311
+ destroy() {
312
+ this._clearGuides();
313
+
314
+ if (this.guidesContainer && this.guidesContainer.parent) {
315
+ this.guidesContainer.parent.removeChild(this.guidesContainer);
316
+ }
317
+
318
+ // Отписываемся от событий
319
+ this.eventBus.off(Events.Tool.DragStart);
320
+ this.eventBus.off(Events.Tool.DragUpdate);
321
+ this.eventBus.off(Events.Tool.DragEnd);
322
+ this.eventBus.off(Events.Tool.GroupDragStart);
323
+ this.eventBus.off(Events.Tool.GroupDragUpdate);
324
+ this.eventBus.off(Events.Tool.GroupDragEnd);
325
+ }
326
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Базовый класс для всех инструментов MoodBoard
3
+ */
4
+ import { Events } from '../core/events/Events.js';
5
+
6
+ export class BaseTool {
7
+ constructor(name, eventBus) {
8
+ this.name = name;
9
+ this.eventBus = eventBus;
10
+ this.isActive = false;
11
+ this.cursor = 'default';
12
+ this.hotkey = null;
13
+
14
+ // Состояние инструмента
15
+ this.isPressed = false;
16
+ this.startPoint = null;
17
+ this.currentPoint = null;
18
+ }
19
+
20
+ /**
21
+ * Активация инструмента
22
+ */
23
+ activate() {
24
+ this.isActive = true;
25
+ this.onActivate();
26
+ this.setCursor();
27
+ this.eventBus.emit(Events.Tool.Activated, { tool: this.name });
28
+ }
29
+
30
+ /**
31
+ * Деактивация инструмента
32
+ */
33
+ deactivate() {
34
+ this.isActive = false;
35
+ this.onDeactivate();
36
+ this.eventBus.emit(Events.Tool.Deactivated, { tool: this.name });
37
+ }
38
+
39
+ /**
40
+ * Устанавливает курсор для инструмента
41
+ */
42
+ setCursor() {
43
+ if (typeof document !== 'undefined' && document.body) {
44
+ document.body.style.cursor = this.cursor;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Обработчики событий мыши - переопределяются в дочерних классах
50
+ */
51
+
52
+ /**
53
+ * Нажатие кнопки мыши
54
+ * @param {Object} event - событие мыши {x, y, button, target}
55
+ */
56
+ onMouseDown(event) {
57
+ this.isPressed = true;
58
+ this.startPoint = { x: event.x, y: event.y };
59
+ this.currentPoint = { x: event.x, y: event.y };
60
+ }
61
+
62
+ /**
63
+ * Перемещение мыши
64
+ * @param {Object} event - событие мыши {x, y, target}
65
+ */
66
+ onMouseMove(event) {
67
+ this.currentPoint = { x: event.x, y: event.y };
68
+ }
69
+
70
+ /**
71
+ * Отпускание кнопки мыши
72
+ * @param {Object} event - событие мыши {x, y, button, target}
73
+ */
74
+ onMouseUp(event) {
75
+ this.isPressed = false;
76
+ this.startPoint = null;
77
+ this.currentPoint = null;
78
+ }
79
+
80
+ /**
81
+ * Двойной клик
82
+ * @param {Object} event - событие мыши {x, y, target}
83
+ */
84
+ onDoubleClick(event) {
85
+ // Переопределяется в дочерних классах
86
+ }
87
+
88
+ /**
89
+ * Контекстное меню (правая кнопка)
90
+ * @param {Object} event - событие мыши {x, y}
91
+ */
92
+ onContextMenu(event) {
93
+ // Переопределяется в дочерних классах
94
+ }
95
+
96
+ /**
97
+ * Колесико мыши
98
+ * @param {Object} event - событие {x, y, delta, ctrlKey}
99
+ */
100
+ onMouseWheel(event) {
101
+ // Переопределяется в дочерних классах
102
+ }
103
+
104
+ /**
105
+ * Обработчики клавиатуры
106
+ */
107
+
108
+ /**
109
+ * Нажатие клавиши
110
+ * @param {Object} event - событие клавиатуры {key, ctrlKey, shiftKey, altKey}
111
+ */
112
+ onKeyDown(event) {
113
+ // Переопределяется в дочерних классах
114
+ }
115
+
116
+ /**
117
+ * Отпускание клавиши
118
+ * @param {Object} event - событие клавиатуры {key}
119
+ */
120
+ onKeyUp(event) {
121
+ // Переопределяется в дочерних классах
122
+ }
123
+
124
+ /**
125
+ * Методы жизненного цикла инструмента
126
+ */
127
+
128
+ /**
129
+ * Вызывается при активации инструмента
130
+ */
131
+ onActivate() {
132
+ // Переопределяется в дочерних классах
133
+ }
134
+
135
+ /**
136
+ * Вызывается при деактивации инструмента
137
+ */
138
+ onDeactivate() {
139
+ // Переопределяется в дочерних классах
140
+ }
141
+
142
+ /**
143
+ * Вспомогательные методы
144
+ */
145
+
146
+ /**
147
+ * Вычисляет расстояние между двумя точками
148
+ */
149
+ getDistance(point1, point2) {
150
+ const dx = point2.x - point1.x;
151
+ const dy = point2.y - point1.y;
152
+ return Math.sqrt(dx * dx + dy * dy);
153
+ }
154
+
155
+ /**
156
+ * Проверяет, находится ли точка в пределах области
157
+ */
158
+ isPointInBounds(point, bounds) {
159
+ return point.x >= bounds.x &&
160
+ point.x <= bounds.x + bounds.width &&
161
+ point.y >= bounds.y &&
162
+ point.y <= bounds.y + bounds.height;
163
+ }
164
+
165
+ /**
166
+ * Эмитит событие инструмента
167
+ */
168
+ emit(eventName, data) {
169
+ // Поддержка как коротких имён ('hit:test'), так и полных ('tool:hit:test')
170
+ const isQualified = eventName.startsWith('tool:');
171
+ const name = isQualified ? eventName.slice(5) : eventName;
172
+
173
+ // События, ожидающие мутацию объекта (передаем data напрямую)
174
+ const passThrough = new Set([
175
+ 'hit:test',
176
+ 'get:object:position',
177
+ 'get:object:pixi',
178
+ 'get:object:size',
179
+ 'get:object:rotation',
180
+ 'get:all:objects',
181
+ 'find:object:by:position'
182
+ ]);
183
+
184
+ if (passThrough.has(name)) {
185
+ const map = new Map([
186
+ ['hit:test', Events.Tool.HitTest],
187
+ ['get:object:position', Events.Tool.GetObjectPosition],
188
+ ['get:object:pixi', Events.Tool.GetObjectPixi],
189
+ ['get:object:size', Events.Tool.GetObjectSize],
190
+ ['get:object:rotation', Events.Tool.GetObjectRotation],
191
+ ['get:all:objects', Events.Tool.GetAllObjects],
192
+ ['find:object:by:position', Events.Tool.FindObjectByPosition],
193
+ ]);
194
+ const evt = map.get(name) || `tool:${name}`;
195
+ this.eventBus.emit(evt, data);
196
+ return;
197
+ }
198
+
199
+ // Для остальных событий добавляем контекст инструмента
200
+ const eventData = { tool: this.name, ...data };
201
+ if (name.includes('rotate')) {
202
+ console.log(`📡 BaseTool отправляет событие tool:${name}:`, eventData);
203
+ }
204
+ const map2 = new Map([
205
+ ['drag:start', Events.Tool.DragStart],
206
+ ['drag:update', Events.Tool.DragUpdate],
207
+ ['drag:end', Events.Tool.DragEnd],
208
+ ['group:drag:start', Events.Tool.GroupDragStart],
209
+ ['group:drag:update', Events.Tool.GroupDragUpdate],
210
+ ['group:drag:end', Events.Tool.GroupDragEnd],
211
+ ['resize:start', Events.Tool.ResizeStart],
212
+ ['resize:update', Events.Tool.ResizeUpdate],
213
+ ['resize:end', Events.Tool.ResizeEnd],
214
+ ['group:resize:start', Events.Tool.GroupResizeStart],
215
+ ['group:resize:update', Events.Tool.GroupResizeUpdate],
216
+ ['group:resize:end', Events.Tool.GroupResizeEnd],
217
+ ['rotate:update', Events.Tool.RotateUpdate],
218
+ ['rotate:end', Events.Tool.RotateEnd],
219
+ ['group:rotate:start', Events.Tool.GroupRotateStart],
220
+ ['group:rotate:update', Events.Tool.GroupRotateUpdate],
221
+ ['group:rotate:end', Events.Tool.GroupRotateEnd],
222
+ ['duplicate:request', Events.Tool.DuplicateRequest],
223
+ ['context:menu:show', Events.Tool.ContextMenuShow],
224
+ ['objects:delete', Events.Tool.ObjectsDelete],
225
+ ['object:edit', Events.Tool.ObjectEdit],
226
+ ['update:object:content', Events.Tool.UpdateObjectContent],
227
+ ['hide:object:text', Events.Tool.HideObjectText],
228
+ ['show:object:text', Events.Tool.ShowObjectText],
229
+ ['selection:add', Events.Tool.SelectionAdd],
230
+ ['selection:remove', Events.Tool.SelectionRemove],
231
+ ['selection:clear', Events.Tool.SelectionClear],
232
+ ['selection:all', Events.Tool.SelectionAll],
233
+ ]);
234
+ const evt2 = map2.get(name) || `tool:${name}`;
235
+ this.eventBus.emit(evt2, eventData);
236
+ }
237
+
238
+ /**
239
+ * Получает настройки инструмента
240
+ */
241
+ getSettings() {
242
+ return {
243
+ name: this.name,
244
+ cursor: this.cursor,
245
+ hotkey: this.hotkey,
246
+ isActive: this.isActive
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Очистка ресурсов инструмента
252
+ */
253
+ destroy() {
254
+ this.deactivate();
255
+ this.eventBus = null;
256
+ }
257
+ }