@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,114 @@
1
+ /**
2
+ * Управляет загрузкой и сохранением данных MoodBoard
3
+ */
4
+ export class DataManager {
5
+ constructor(coreMoodboard) {
6
+ this.coreMoodboard = coreMoodboard;
7
+ }
8
+
9
+ /**
10
+ * Загружает данные в MoodBoard
11
+ */
12
+ loadData(data) {
13
+ if (!data) return;
14
+
15
+
16
+
17
+ // Очищаем доску перед загрузкой
18
+ this.clearBoard();
19
+
20
+ // Загружаем объекты
21
+ if (data.objects && Array.isArray(data.objects)) {
22
+
23
+
24
+ data.objects.forEach((objectData, index) => {
25
+ try {
26
+ // Используем полные данные объекта, включая ID
27
+ const createdObject = this.coreMoodboard.createObjectFromData(objectData);
28
+
29
+ } catch (error) {
30
+ console.error(`❌ Ошибка загрузки объекта ${index + 1}:`, error, objectData);
31
+ }
32
+ });
33
+ }
34
+
35
+ // Загружаем viewport
36
+ if (data.viewport) {
37
+ this.loadViewport(data.viewport);
38
+ }
39
+
40
+
41
+ }
42
+
43
+ /**
44
+ * Загружает настройки viewport (позиция и зум)
45
+ */
46
+ loadViewport(viewport) {
47
+ // TODO: Реализовать установку viewport
48
+
49
+
50
+ // Здесь будет код для установки позиции и зума canvas
51
+ // this.coreMoodboard.setViewport(viewport.x, viewport.y, viewport.zoom);
52
+ }
53
+
54
+ /**
55
+ * Экспортирует данные доски
56
+ */
57
+ exportBoardData() {
58
+ if (!this.coreMoodboard) {
59
+ return null;
60
+ }
61
+
62
+ const data = this.coreMoodboard.boardData;
63
+
64
+ // Создаем событие для внешнего использования
65
+ this.coreMoodboard.eventBus.emit('board:export', data);
66
+
67
+ return data;
68
+ }
69
+
70
+ /**
71
+ * Очищает все объекты на доске
72
+ */
73
+ clearBoard() {
74
+ if (!this.coreMoodboard) return;
75
+
76
+ const objects = this.coreMoodboard.objects || [];
77
+ objects.forEach(obj => this.coreMoodboard.deleteObject(obj.id));
78
+
79
+
80
+ return objects.length;
81
+ }
82
+
83
+ /**
84
+ * Создает объект на доске
85
+ */
86
+ createObject(type, position, properties = {}, extraData = {}) {
87
+ if (!this.coreMoodboard) return null;
88
+
89
+ return this.coreMoodboard.createObject(type, position, properties, extraData);
90
+ }
91
+
92
+ /**
93
+ * Удаляет объект с доски
94
+ */
95
+ deleteObject(objectId) {
96
+ if (!this.coreMoodboard) return;
97
+
98
+ this.coreMoodboard.deleteObject(objectId);
99
+ }
100
+
101
+ /**
102
+ * Получает все объекты доски
103
+ */
104
+ get objects() {
105
+ return this.coreMoodboard ? this.coreMoodboard.objects : [];
106
+ }
107
+
108
+ /**
109
+ * Получает данные доски
110
+ */
111
+ get boardData() {
112
+ return this.coreMoodboard ? this.coreMoodboard.boardData : null;
113
+ }
114
+ }
@@ -0,0 +1,359 @@
1
+ import { CoreMoodBoard } from '../core/index.js';
2
+ import { Events } from '../core/events/Events.js';
3
+ import { Toolbar } from '../ui/Toolbar.js';
4
+ import { SaveStatus } from '../ui/SaveStatus.js';
5
+ import { Topbar } from '../ui/Topbar.js';
6
+ import { ZoomPanel } from '../ui/ZoomPanel.js';
7
+ import { MapPanel } from '../ui/MapPanel.js';
8
+ import { ContextMenu } from '../ui/ContextMenu.js';
9
+ import { WorkspaceManager } from './WorkspaceManager.js';
10
+ import { DataManager } from './DataManager.js';
11
+ import { ActionHandler } from './ActionHandler.js';
12
+ import { HtmlTextLayer } from '../ui/HtmlTextLayer.js';
13
+ import { HtmlHandlesLayer } from '../ui/HtmlHandlesLayer.js';
14
+ import { CommentPopover } from '../ui/CommentPopover.js';
15
+ import { TextPropertiesPanel } from '../ui/TextPropertiesPanel.js';
16
+ import { FramePropertiesPanel } from '../ui/FramePropertiesPanel.js';
17
+ import { NotePropertiesPanel } from '../ui/NotePropertiesPanel.js';
18
+ import { FilePropertiesPanel } from '../ui/FilePropertiesPanel.js';
19
+ import { AlignmentGuides } from '../tools/AlignmentGuides.js';
20
+ import { ImageUploadService } from '../services/ImageUploadService.js';
21
+
22
+ /**
23
+ * Готовый MoodBoard с UI - главный класс пакета
24
+ */
25
+ export class MoodBoard {
26
+ constructor(container, options = {}, data = null) {
27
+ this.containerSelector = container;
28
+ this.container = typeof container === 'string'
29
+ ? document.querySelector(container)
30
+ : container;
31
+
32
+ if (!this.container) {
33
+ throw new Error('Container not found');
34
+ }
35
+
36
+ // Настройки по умолчанию
37
+ this.options = {
38
+ theme: 'light',
39
+ ...options
40
+ };
41
+
42
+ this.data = data;
43
+
44
+ // Основные компоненты
45
+ this.coreMoodboard = null;
46
+ this.toolbar = null;
47
+ this.saveStatus = null;
48
+ this.contextMenu = null;
49
+
50
+ // Менеджеры
51
+ this.workspaceManager = new WorkspaceManager(this.container, this.options);
52
+ this.dataManager = null;
53
+ this.actionHandler = null;
54
+
55
+ this.init();
56
+ }
57
+
58
+ /**
59
+ * Инициализация рабочего пространства
60
+ */
61
+ async init() {
62
+ try {
63
+ // Создаем HTML структуру
64
+ const { workspace, toolbar, canvas, topbar } = this.workspaceManager.createWorkspaceStructure();
65
+ this.workspaceElement = workspace;
66
+ this.toolbarContainer = toolbar;
67
+ this.canvasContainer = canvas;
68
+ this.topbarContainer = topbar;
69
+
70
+ // Инициализируем CoreMoodBoard
71
+ await this.initCoreMoodBoard();
72
+
73
+ // Создаем менеджеры
74
+ this.dataManager = new DataManager(this.coreMoodboard);
75
+ this.actionHandler = new ActionHandler(this.dataManager, this.workspaceManager);
76
+
77
+ // Инициализируем UI
78
+ this.initToolbar();
79
+ this.initTopbar();
80
+ this.initZoombar();
81
+ this.initMapbar();
82
+ this.initContextMenu();
83
+ // HTML-слои: сверхчёткий текст и единые ручки
84
+ this.htmlTextLayer = new HtmlTextLayer(this.canvasContainer, this.coreMoodboard.eventBus, this.coreMoodboard);
85
+ this.htmlTextLayer.attach();
86
+ this.htmlHandlesLayer = new HtmlHandlesLayer(this.canvasContainer, this.coreMoodboard.eventBus, this.coreMoodboard);
87
+ this.htmlHandlesLayer.attach();
88
+ // Поповер для комментариев
89
+ this.commentPopover = new CommentPopover(this.canvasContainer, this.coreMoodboard.eventBus, this.coreMoodboard);
90
+ this.commentPopover.attach();
91
+ // Панель свойств текста
92
+ this.textPropertiesPanel = new TextPropertiesPanel(this.canvasContainer, this.coreMoodboard.eventBus, this.coreMoodboard);
93
+ this.textPropertiesPanel.attach();
94
+
95
+ // Панель свойств фрейма
96
+ this.framePropertiesPanel = new FramePropertiesPanel(this.coreMoodboard.eventBus, this.canvasContainer, this.coreMoodboard);
97
+
98
+ // Панель свойств записки
99
+ this.notePropertiesPanel = new NotePropertiesPanel(this.coreMoodboard.eventBus, this.canvasContainer, this.coreMoodboard);
100
+ this.filePropertiesPanel = new FilePropertiesPanel(this.coreMoodboard.eventBus, this.canvasContainer, this.coreMoodboard);
101
+
102
+ // Направляющие линии выравнивания
103
+ this.alignmentGuides = new AlignmentGuides(
104
+ this.coreMoodboard.eventBus,
105
+ this.coreMoodboard.pixi.app,
106
+ () => this.coreMoodboard.state.getObjects()
107
+ );
108
+
109
+ // Сервис загрузки изображений
110
+ this.imageUploadService = new ImageUploadService(this.coreMoodboard.apiClient);
111
+
112
+ // Предоставляем доступ к сервису через core
113
+ this.coreMoodboard.imageUploadService = this.imageUploadService;
114
+
115
+ // Загружаем данные (сначала пробуем загрузить с сервера, потом дефолтные)
116
+ await this.loadExistingBoard();
117
+
118
+ console.log('MoodBoard initialized');
119
+ } catch (error) {
120
+ console.error('MoodBoard init failed:', error);
121
+ throw error;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Инициализирует CoreMoodBoard
127
+ */
128
+ async initCoreMoodBoard() {
129
+ const canvasSize = this.workspaceManager.getCanvasSize();
130
+
131
+ const moodboardOptions = {
132
+ boardId: this.options.boardId || 'workspace-board',
133
+ width: canvasSize.width,
134
+ height: canvasSize.height,
135
+ // Цвет фона по умолчанию: #f7fbff (светлый голубовато-белый)
136
+ backgroundColor: this.options.theme === 'dark' ? 0x2a2a2a : 0xF7FBFF,
137
+ // Передаем только настройки эндпоинтов для автосохранения
138
+ saveEndpoint: this.options.saveEndpoint,
139
+ loadEndpoint: this.options.loadEndpoint
140
+ };
141
+
142
+ this.coreMoodboard = new CoreMoodBoard(this.canvasContainer, moodboardOptions);
143
+ await this.coreMoodboard.init();
144
+ }
145
+
146
+ /**
147
+ * Инициализирует панель инструментов
148
+ */
149
+ initToolbar() {
150
+ this.toolbar = new Toolbar(
151
+ this.toolbarContainer,
152
+ this.coreMoodboard.eventBus,
153
+ this.options.theme
154
+ );
155
+
156
+ // Добавляем функцию для отладки иконок в window
157
+ if (typeof window !== 'undefined') {
158
+ window.reloadIcon = (iconName) => this.toolbar.reloadToolbarIcon(iconName);
159
+ }
160
+
161
+ // Инициализируем индикатор сохранения (с фиксированными настройками)
162
+ this.saveStatus = new SaveStatus(
163
+ this.workspaceElement,
164
+ this.coreMoodboard.eventBus
165
+ );
166
+
167
+ // Подписываемся на события тулбара
168
+ this.coreMoodboard.eventBus.on(Events.UI.ToolbarAction, (action) => {
169
+ this.actionHandler.handleToolbarAction(action);
170
+ });
171
+ }
172
+
173
+ initTopbar() {
174
+ this.topbar = new Topbar(
175
+ this.topbarContainer,
176
+ this.coreMoodboard.eventBus,
177
+ this.options.theme
178
+ );
179
+
180
+ // Смена фона доски по выбору цвета в топбаре
181
+ this.coreMoodboard.eventBus.on(Events.UI.PaintPick, ({ color }) => {
182
+ if (!color) return;
183
+ const hex = typeof color === 'string' && color.startsWith('#')
184
+ ? parseInt(color.slice(1), 16)
185
+ : color;
186
+ if (this.coreMoodboard?.pixi?.app?.renderer) {
187
+ this.coreMoodboard.pixi.app.renderer.backgroundColor = hex;
188
+ }
189
+ });
190
+ }
191
+
192
+ initZoombar() {
193
+ // Рисуем панель зума поверх холста (в том же контейнере, что и topbar)
194
+ this.zoombar = new ZoomPanel(
195
+ this.topbarContainer,
196
+ this.coreMoodboard.eventBus
197
+ );
198
+ }
199
+
200
+ initMapbar() {
201
+ // Рисуем панель карты в правом нижнем углу (внутри workspace контейнера)
202
+ this.mapbar = new MapPanel(
203
+ this.workspaceElement,
204
+ this.coreMoodboard.eventBus
205
+ );
206
+ }
207
+
208
+ initContextMenu() {
209
+ this.contextMenu = new ContextMenu(
210
+ this.canvasContainer,
211
+ this.coreMoodboard.eventBus
212
+ );
213
+ }
214
+
215
+ /**
216
+ * Изменение темы
217
+ */
218
+ setTheme(theme) {
219
+ this.options.theme = theme;
220
+
221
+ // Обновляем тему в менеджерах
222
+ this.workspaceManager.updateTheme(theme);
223
+
224
+ if (this.toolbar) {
225
+ this.toolbar.setTheme(theme);
226
+ }
227
+
228
+ // Обновляем цвет фона MoodBoard
229
+ if (this.coreMoodboard && this.coreMoodboard.pixi) {
230
+ this.coreMoodboard.pixi.app.renderer.backgroundColor =
231
+ theme === 'dark' ? 0x2a2a2a : 0xF5F5F5;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Получение данных доски
237
+ */
238
+ get boardData() {
239
+ return this.dataManager ? this.dataManager.boardData : null;
240
+ }
241
+
242
+ /**
243
+ * Получение объектов доски
244
+ */
245
+ get objects() {
246
+ return this.dataManager ? this.dataManager.objects : [];
247
+ }
248
+
249
+ /**
250
+ * Создание объекта программно
251
+ */
252
+ createObject(type, position, properties = {}) {
253
+ return this.actionHandler ? this.actionHandler.createObject(type, position, properties) : null;
254
+ }
255
+
256
+ /**
257
+ * Удаление объекта программно
258
+ */
259
+ deleteObject(objectId) {
260
+ if (this.actionHandler) {
261
+ this.actionHandler.deleteObject(objectId);
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Очистка доски программно
267
+ */
268
+ clearBoard() {
269
+ return this.actionHandler ? this.actionHandler.clearBoard() : 0;
270
+ }
271
+
272
+ /**
273
+ * Экспорт данных программно
274
+ */
275
+ exportBoard() {
276
+ return this.actionHandler ? this.actionHandler.exportBoard() : null;
277
+ }
278
+
279
+ /**
280
+ * Загрузка существующей доски с сервера
281
+ */
282
+ async loadExistingBoard() {
283
+ try {
284
+ const boardId = this.options.boardId;
285
+
286
+ if (!boardId || !this.options.loadEndpoint) {
287
+ this.dataManager.loadData(this.data);
288
+ return;
289
+ }
290
+
291
+ // Пытаемся загрузить с сервера
292
+ const boardData = await this.coreMoodboard.saveManager.loadBoardData(boardId);
293
+
294
+ if (boardData && boardData.objects) {
295
+ // Восстанавливаем URL изображений и файлов перед загрузкой (если метод доступен)
296
+ let restoredData = boardData;
297
+ if (this.coreMoodboard.apiClient && typeof this.coreMoodboard.apiClient.restoreObjectUrls === 'function') {
298
+ try {
299
+ restoredData = await this.coreMoodboard.apiClient.restoreObjectUrls(boardData);
300
+ } catch (error) {
301
+ console.warn('Не удалось восстановить URL объектов:', error);
302
+ restoredData = boardData; // Используем исходные данные
303
+ }
304
+ }
305
+ this.dataManager.loadData(restoredData);
306
+ } else {
307
+ this.dataManager.loadData(this.data);
308
+ }
309
+
310
+ } catch (error) {
311
+ console.warn('⚠️ Ошибка загрузки доски, создаем новую:', error.message);
312
+ console.debug('ApiClient доступен:', !!this.coreMoodboard.apiClient);
313
+ console.debug('Метод restoreObjectUrls доступен:', !!(this.coreMoodboard.apiClient && typeof this.coreMoodboard.apiClient.restoreObjectUrls === 'function'));
314
+ // Если загрузка не удалась, используем дефолтные данные
315
+ this.dataManager.loadData(this.data);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Очистка ресурсов
321
+ */
322
+ destroy() {
323
+ if (this.toolbar) {
324
+ this.toolbar.destroy();
325
+ }
326
+
327
+ if (this.saveStatus) {
328
+ this.saveStatus.destroy();
329
+ }
330
+
331
+ if (this.textPropertiesPanel) {
332
+ this.textPropertiesPanel.destroy();
333
+ }
334
+
335
+ if (this.framePropertiesPanel) {
336
+ this.framePropertiesPanel.destroy();
337
+ }
338
+
339
+ if (this.notePropertiesPanel) {
340
+ this.notePropertiesPanel.destroy();
341
+ }
342
+
343
+ if (this.alignmentGuides) {
344
+ this.alignmentGuides.destroy();
345
+ }
346
+
347
+ if (this.commentPopover) {
348
+ this.commentPopover.destroy();
349
+ }
350
+
351
+ if (this.coreMoodboard) {
352
+ this.coreMoodboard.destroy();
353
+ }
354
+
355
+ if (this.workspaceManager) {
356
+ this.workspaceManager.destroy();
357
+ }
358
+ }
359
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Управляет HTML структурой рабочего пространства MoodBoard
3
+ */
4
+ export class WorkspaceManager {
5
+ constructor(container, options = {}) {
6
+ this.container = container;
7
+ this.options = options;
8
+ this.workspaceElement = null;
9
+ this.toolbarContainer = null;
10
+ this.canvasContainer = null;
11
+ this.topbarContainer = null;
12
+ }
13
+
14
+ /**
15
+ * Создает HTML структуру рабочего пространства
16
+ */
17
+ createWorkspaceStructure() {
18
+ // Очищаем контейнер
19
+ this.container.innerHTML = '';
20
+
21
+ // Создаем основной элемент workspace
22
+ this.workspaceElement = document.createElement('div');
23
+ this.workspaceElement.className = `moodboard-workspace moodboard-workspace--${this.options.theme}`;
24
+
25
+ // Создаем контейнер для тулбара
26
+ this.toolbarContainer = document.createElement('div');
27
+ this.toolbarContainer.className = 'moodboard-workspace__toolbar';
28
+
29
+ // Создаем контейнер для canvas
30
+ this.canvasContainer = document.createElement('div');
31
+ this.canvasContainer.className = 'moodboard-workspace__canvas';
32
+ this.canvasContainer.id = 'moodboard-canvas-' + Date.now();
33
+
34
+ // Создаем контейнер для верхней панели
35
+ this.topbarContainer = document.createElement('div');
36
+ this.topbarContainer.className = 'moodboard-workspace__topbar';
37
+
38
+ // Собираем структуру (toolbar поверх canvas)
39
+ this.workspaceElement.appendChild(this.canvasContainer);
40
+ this.workspaceElement.appendChild(this.toolbarContainer);
41
+ this.workspaceElement.appendChild(this.topbarContainer);
42
+ this.container.appendChild(this.workspaceElement);
43
+
44
+ return {
45
+ workspace: this.workspaceElement,
46
+ toolbar: this.toolbarContainer,
47
+ canvas: this.canvasContainer,
48
+ topbar: this.topbarContainer
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Обновляет тему рабочего пространства
54
+ */
55
+ updateTheme(theme) {
56
+ if (this.workspaceElement) {
57
+ this.workspaceElement.className = `moodboard-workspace moodboard-workspace--${theme}`;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Показывает уведомление
63
+ */
64
+ showNotification(message) {
65
+ if (!this.workspaceElement) return;
66
+
67
+ const notification = document.createElement('div');
68
+ notification.className = 'moodboard-notification';
69
+ notification.textContent = message;
70
+
71
+ this.workspaceElement.appendChild(notification);
72
+
73
+ setTimeout(() => {
74
+ notification.remove();
75
+ }, 3000);
76
+ }
77
+
78
+ /**
79
+ * Получение размеров canvas контейнера
80
+ */
81
+ getCanvasSize() {
82
+ if (!this.canvasContainer) {
83
+ return { width: 800, height: 600 };
84
+ }
85
+
86
+ return {
87
+ width: this.canvasContainer.clientWidth,
88
+ height: this.canvasContainer.clientHeight
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Очистка ресурсов
94
+ */
95
+ destroy() {
96
+ if (this.workspaceElement) {
97
+ this.workspaceElement.remove();
98
+ this.workspaceElement = null;
99
+ }
100
+ this.toolbarContainer = null;
101
+ this.canvasContainer = null;
102
+ }
103
+ }
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,115 @@
1
+ import * as PIXI from 'pixi.js';
2
+
3
+ /**
4
+ * CommentObject — круглый маркер комментария с равнобедренным хвостиком в юго-западной области
5
+ * Свойства (properties):
6
+ * - fill?: number — цвет заливки формы
7
+ * - stroke?: number — цвет обводки (по умолчанию тот же, что и fill)
8
+ */
9
+ export class CommentObject {
10
+ constructor(objectData = {}) {
11
+ this.objectData = objectData;
12
+ this.width = objectData.width || objectData.properties?.width || 72;
13
+ this.height = objectData.height || objectData.properties?.height || 72;
14
+ // Цвет по умолчанию — #B388FF
15
+ const props = objectData.properties || {};
16
+ this.fill = (typeof props.fill === 'number') ? props.fill : 0xB388FF;
17
+ this.stroke = (typeof props.stroke === 'number') ? props.stroke : this.fill;
18
+
19
+ // Основная графика формы
20
+ this.graphics = new PIXI.Graphics();
21
+ this.graphics._mb = {
22
+ ...(this.graphics._mb || {}),
23
+ type: 'comment',
24
+ properties: { ...objectData.properties }
25
+ };
26
+
27
+ // Текстовая метка внутри (буква "A")
28
+ this.label = new PIXI.Text('A', {
29
+ fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial',
30
+ fontSize: 24,
31
+ fontWeight: '700',
32
+ fill: 0xFAFAFA,
33
+ align: 'center',
34
+ resolution: (typeof window !== 'undefined' && window.devicePixelRatio) ? window.devicePixelRatio : 1
35
+ });
36
+ this.label.anchor.set(0.5);
37
+ this.graphics.addChild(this.label);
38
+
39
+ this._redraw();
40
+ }
41
+
42
+ getPixi() {
43
+ return this.graphics;
44
+ }
45
+
46
+ updateSize(size) {
47
+ if (!size) return;
48
+ this.width = Math.max(12, size.width || this.width);
49
+ this.height = Math.max(12, size.height || this.height);
50
+ this._redraw();
51
+ }
52
+
53
+ _redraw() {
54
+ const g = this.graphics;
55
+ const w = this.width;
56
+ const h = this.height;
57
+ g.clear();
58
+
59
+ // Круг
60
+ const cx = w / 2;
61
+ const cy = h / 2;
62
+ const r = Math.max(6, Math.min(w, h) / 2 - 4);
63
+
64
+ // Помощник: точка на окружности с учётом экранной оси Y (вниз положительно)
65
+ const pOnCircle = (deg, radius) => {
66
+ const rad = (deg * Math.PI) / 180;
67
+ return {
68
+ x: cx + radius * Math.cos(rad),
69
+ y: cy - radius * Math.sin(rad)
70
+ };
71
+ };
72
+
73
+ // Хвостик: равнобедренный треугольник, направленный на 225° (юго-запад)
74
+ const dir = 225; // центральное направление
75
+ const baseHalf = 16; // половина угла основания
76
+ const baseR = r * 0.94; // основание внутри круга, чтобы скрывалось
77
+ const apexExtra = r * 0.45; // длина хвостика за пределы круга
78
+
79
+ const b1 = pOnCircle(dir - baseHalf, baseR);
80
+ const b2 = pOnCircle(dir + baseHalf, baseR);
81
+ let apex = pOnCircle(dir, r + apexExtra);
82
+ // Гарантируем, что вершина остаётся в пределах bbox объекта
83
+ apex.x = Math.max(2, Math.min(w - 2, apex.x));
84
+ apex.y = Math.max(2, Math.min(h - 2, apex.y));
85
+
86
+ // 1) Рисуем треугольник хвостика (без обводки)
87
+ g.beginFill(this.fill, 1);
88
+ g.moveTo(b1.x, b1.y);
89
+ g.lineTo(apex.x, apex.y);
90
+ g.lineTo(b2.x, b2.y);
91
+ g.closePath();
92
+ g.endFill();
93
+
94
+ // 2) Круг поверх
95
+ g.lineStyle(2, this.stroke, 1);
96
+ g.beginFill(this.fill, 1);
97
+ g.drawCircle(cx, cy, r);
98
+ g.endFill();
99
+
100
+ // 3) Внутренний круг (на ~20% меньше)
101
+ const innerR = Math.max(1, r * 0.8);
102
+ g.lineStyle(0);
103
+ g.beginFill(0x424242, 1);
104
+ g.drawCircle(cx, cy, innerR);
105
+ g.endFill();
106
+
107
+ // 4) Текстовая метка в центре
108
+ const targetFontSize = Math.round(innerR * 0.9);
109
+ this.label.style.fill = 0xFAFAFA;
110
+ this.label.style.fontSize = targetFontSize;
111
+ this.label.position.set(cx, cy);
112
+ }
113
+ }
114
+
115
+