@sequent-org/moodboard 1.2.119 → 1.3.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 (122) hide show
  1. package/package.json +11 -1
  2. package/src/assets/icons/rotate-icon.svg +1 -1
  3. package/src/core/HistoryManager.js +16 -16
  4. package/src/core/KeyboardManager.js +48 -539
  5. package/src/core/PixiEngine.js +9 -9
  6. package/src/core/SaveManager.js +56 -31
  7. package/src/core/bootstrap/CoreInitializer.js +65 -0
  8. package/src/core/commands/DeleteObjectCommand.js +8 -0
  9. package/src/core/commands/GroupDeleteCommand.js +75 -0
  10. package/src/core/commands/GroupRotateCommand.js +6 -0
  11. package/src/core/commands/UpdateContentCommand.js +52 -0
  12. package/src/core/commands/UpdateFramePropertiesCommand.js +98 -0
  13. package/src/core/commands/UpdateFrameTypeCommand.js +85 -0
  14. package/src/core/commands/UpdateNoteStyleCommand.js +88 -0
  15. package/src/core/commands/UpdateTextStyleCommand.js +90 -0
  16. package/src/core/commands/index.js +6 -0
  17. package/src/core/events/Events.js +6 -0
  18. package/src/core/flows/ClipboardFlow.js +553 -0
  19. package/src/core/flows/LayerAndViewportFlow.js +283 -0
  20. package/src/core/flows/ObjectLifecycleFlow.js +336 -0
  21. package/src/core/flows/SaveFlow.js +34 -0
  22. package/src/core/flows/TransformFlow.js +277 -0
  23. package/src/core/flows/TransformFlowResizeHelpers.js +83 -0
  24. package/src/core/index.js +41 -1773
  25. package/src/core/keyboard/KeyboardClipboardImagePaste.js +190 -0
  26. package/src/core/keyboard/KeyboardContextGuards.js +35 -0
  27. package/src/core/keyboard/KeyboardEventRouter.js +92 -0
  28. package/src/core/keyboard/KeyboardSelectionActions.js +103 -0
  29. package/src/core/keyboard/KeyboardShortcutMap.js +31 -0
  30. package/src/core/keyboard/KeyboardToolSwitching.js +26 -0
  31. package/src/core/rendering/ObjectRenderer.js +3 -7
  32. package/src/grid/BaseGrid.js +26 -0
  33. package/src/grid/CrossGrid.js +7 -6
  34. package/src/grid/DotGrid.js +89 -33
  35. package/src/grid/DotGridZoomPhases.js +42 -0
  36. package/src/grid/LineGrid.js +22 -21
  37. package/src/moodboard/MoodBoard.js +31 -532
  38. package/src/moodboard/bootstrap/MoodBoardInitializer.js +47 -0
  39. package/src/moodboard/bootstrap/MoodBoardManagersFactory.js +38 -0
  40. package/src/moodboard/bootstrap/MoodBoardUiFactory.js +109 -0
  41. package/src/moodboard/integration/MoodBoardEventBindings.js +65 -0
  42. package/src/moodboard/integration/MoodBoardLoadApi.js +82 -0
  43. package/src/moodboard/integration/MoodBoardScreenshotApi.js +33 -0
  44. package/src/moodboard/integration/MoodBoardScreenshotCanvas.js +98 -0
  45. package/src/moodboard/lifecycle/MoodBoardDestroyer.js +97 -0
  46. package/src/objects/FileObject.js +17 -6
  47. package/src/objects/FrameObject.js +50 -10
  48. package/src/objects/NoteObject.js +5 -4
  49. package/src/services/BoardService.js +42 -2
  50. package/src/services/FrameService.js +83 -42
  51. package/src/services/ResizePolicyService.js +152 -0
  52. package/src/services/SettingsApplier.js +7 -2
  53. package/src/services/ZoomPanController.js +35 -9
  54. package/src/tools/ToolManager.js +30 -537
  55. package/src/tools/board-tools/PanTool.js +5 -11
  56. package/src/tools/manager/ToolActivationController.js +49 -0
  57. package/src/tools/manager/ToolEventRouter.js +396 -0
  58. package/src/tools/manager/ToolManagerGuards.js +33 -0
  59. package/src/tools/manager/ToolManagerLifecycle.js +110 -0
  60. package/src/tools/manager/ToolRegistry.js +33 -0
  61. package/src/tools/object-tools/DrawingTool.js +48 -14
  62. package/src/tools/object-tools/PlacementTool.js +50 -1049
  63. package/src/tools/object-tools/PlacementToolV2.js +88 -0
  64. package/src/tools/object-tools/SelectTool.js +174 -2681
  65. package/src/tools/object-tools/placement/GhostController.js +504 -0
  66. package/src/tools/object-tools/placement/PlacementCoordinateResolver.js +20 -0
  67. package/src/tools/object-tools/placement/PlacementEventsBridge.js +91 -0
  68. package/src/tools/object-tools/placement/PlacementInputRouter.js +267 -0
  69. package/src/tools/object-tools/placement/PlacementPayloadFactory.js +111 -0
  70. package/src/tools/object-tools/placement/PlacementSessionStore.js +18 -0
  71. package/src/tools/object-tools/selection/BoxSelectController.js +0 -5
  72. package/src/tools/object-tools/selection/CloneFlowController.js +71 -0
  73. package/src/tools/object-tools/selection/CoordinateMapper.js +10 -0
  74. package/src/tools/object-tools/selection/CursorController.js +78 -0
  75. package/src/tools/object-tools/selection/FileNameInlineEditorController.js +184 -0
  76. package/src/tools/object-tools/selection/HitTestService.js +102 -0
  77. package/src/tools/object-tools/selection/InlineEditorController.js +24 -0
  78. package/src/tools/object-tools/selection/InlineEditorDomFactory.js +50 -0
  79. package/src/tools/object-tools/selection/InlineEditorListenersRegistry.js +14 -0
  80. package/src/tools/object-tools/selection/InlineEditorPositioningService.js +25 -0
  81. package/src/tools/object-tools/selection/NoteInlineEditorController.js +113 -0
  82. package/src/tools/object-tools/selection/SelectInputRouter.js +267 -0
  83. package/src/tools/object-tools/selection/SelectToolLifecycleController.js +128 -0
  84. package/src/tools/object-tools/selection/SelectToolSetup.js +134 -0
  85. package/src/tools/object-tools/selection/SelectionOverlayService.js +81 -0
  86. package/src/tools/object-tools/selection/SelectionStateController.js +91 -0
  87. package/src/tools/object-tools/selection/TextEditorDomFactory.js +65 -0
  88. package/src/tools/object-tools/selection/TextEditorInteractionController.js +266 -0
  89. package/src/tools/object-tools/selection/TextEditorLifecycleRegistry.js +90 -0
  90. package/src/tools/object-tools/selection/TextEditorPositioningService.js +158 -0
  91. package/src/tools/object-tools/selection/TextEditorSyncService.js +110 -0
  92. package/src/tools/object-tools/selection/TextInlineEditorController.js +457 -0
  93. package/src/tools/object-tools/selection/TransformInteractionController.js +466 -0
  94. package/src/ui/FilePropertiesPanel.js +61 -32
  95. package/src/ui/FramePropertiesPanel.js +176 -101
  96. package/src/ui/HtmlHandlesLayer.js +121 -999
  97. package/src/ui/MapPanel.js +12 -7
  98. package/src/ui/NotePropertiesPanel.js +17 -2
  99. package/src/ui/TextPropertiesPanel.js +124 -738
  100. package/src/ui/Toolbar.js +71 -1180
  101. package/src/ui/Topbar.js +23 -25
  102. package/src/ui/ZoomPanel.js +16 -5
  103. package/src/ui/handles/GroupSelectionHandlesController.js +29 -0
  104. package/src/ui/handles/HandlesDomRenderer.js +278 -0
  105. package/src/ui/handles/HandlesEventBridge.js +102 -0
  106. package/src/ui/handles/HandlesInteractionController.js +772 -0
  107. package/src/ui/handles/HandlesPositioningService.js +206 -0
  108. package/src/ui/handles/SingleSelectionHandlesController.js +22 -0
  109. package/src/ui/styles/toolbar.css +2 -0
  110. package/src/ui/styles/workspace.css +13 -6
  111. package/src/ui/text-properties/TextPropertiesPanelBindings.js +92 -0
  112. package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +77 -0
  113. package/src/ui/text-properties/TextPropertiesPanelMapper.js +173 -0
  114. package/src/ui/text-properties/TextPropertiesPanelRenderer.js +434 -0
  115. package/src/ui/text-properties/TextPropertiesPanelState.js +39 -0
  116. package/src/ui/toolbar/ToolbarActionRouter.js +193 -0
  117. package/src/ui/toolbar/ToolbarDialogsController.js +186 -0
  118. package/src/ui/toolbar/ToolbarPopupsController.js +662 -0
  119. package/src/ui/toolbar/ToolbarRenderer.js +97 -0
  120. package/src/ui/toolbar/ToolbarStateController.js +79 -0
  121. package/src/ui/toolbar/ToolbarTooltipController.js +52 -0
  122. package/src/utils/emojiLoaderNoBundler.js +1 -1
@@ -1,25 +1,23 @@
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
- import { SettingsApplier } from '../services/SettingsApplier.js';
22
1
  import { GridFactory } from '../grid/GridFactory.js';
2
+ import {
3
+ initCoreMoodBoard as initializeCoreMoodBoard,
4
+ initializeMoodBoard,
5
+ } from './bootstrap/MoodBoardInitializer.js';
6
+ import {
7
+ createWorkspaceManager,
8
+ } from './bootstrap/MoodBoardManagersFactory.js';
9
+ import { bindSaveCallbacks } from './integration/MoodBoardEventBindings.js';
10
+ import {
11
+ getCsrfToken as getMoodBoardCsrfToken,
12
+ loadExistingBoard as loadExistingMoodBoard,
13
+ loadFromApi as loadMoodBoardFromApi,
14
+ } from './integration/MoodBoardLoadApi.js';
15
+ import {
16
+ createCombinedScreenshot as createMoodBoardCombinedScreenshot,
17
+ exportScreenshot as exportMoodBoardScreenshot,
18
+ wrapText as wrapMoodBoardText,
19
+ } from './integration/MoodBoardScreenshotApi.js';
20
+ import { destroyMoodBoard, safeDestroy } from './lifecycle/MoodBoardDestroyer.js';
23
21
 
24
22
  /**
25
23
  * Готовый MoodBoard с UI - главный класс пакета
@@ -59,7 +57,7 @@ export class MoodBoard {
59
57
  this.contextMenu = null;
60
58
 
61
59
  // Менеджеры
62
- this.workspaceManager = new WorkspaceManager(this.container, this.options);
60
+ createWorkspaceManager(this);
63
61
  this.dataManager = null;
64
62
  this.actionHandler = null;
65
63
 
@@ -70,207 +68,14 @@ export class MoodBoard {
70
68
  * Инициализация рабочего пространства
71
69
  */
72
70
  async init() {
73
- try {
74
- // Добавляем корневой класс к контейнеру для изоляции стилей
75
- if (this.container) {
76
- this.container.classList.add('moodboard-root');
77
- }
78
-
79
- // Создаем HTML структуру
80
- const { workspace, toolbar, canvas, topbar } = this.workspaceManager.createWorkspaceStructure();
81
- this.workspaceElement = workspace;
82
- this.toolbarContainer = toolbar;
83
- this.canvasContainer = canvas;
84
- this.topbarContainer = topbar;
85
-
86
- // Инициализируем CoreMoodBoard
87
- await this.initCoreMoodBoard();
88
-
89
- // Настройки (единая точка применения)
90
- this.settingsApplier = new SettingsApplier(
91
- this.coreMoodboard.eventBus,
92
- this.coreMoodboard.pixi,
93
- this.coreMoodboard.boardService || null
94
- );
95
- // Делаем доступным для других подсистем
96
- this.coreMoodboard.settingsApplier = this.settingsApplier;
97
-
98
- // Создаем менеджеры
99
- this.dataManager = new DataManager(this.coreMoodboard);
100
- this.actionHandler = new ActionHandler(this.dataManager, this.workspaceManager);
101
-
102
- // Инициализируем UI
103
- this.initToolbar();
104
- this.initTopbar();
105
- this.initZoombar();
106
- this.initMapbar();
107
- this.initContextMenu();
108
- // HTML-слои: сверхчёткий текст и единые ручки
109
- this.htmlTextLayer = new HtmlTextLayer(this.canvasContainer, this.coreMoodboard.eventBus, this.coreMoodboard);
110
- this.htmlTextLayer.attach();
111
- this.htmlHandlesLayer = new HtmlHandlesLayer(this.canvasContainer, this.coreMoodboard.eventBus, this.coreMoodboard);
112
- this.htmlHandlesLayer.attach();
113
-
114
- // Устанавливаем глобальные свойства для доступа к слоям
115
- if (typeof window !== 'undefined') {
116
- window.moodboardHtmlTextLayer = this.htmlTextLayer;
117
- window.moodboardHtmlHandlesLayer = this.htmlHandlesLayer;
118
- }
119
- // Поповер для комментариев
120
- this.commentPopover = new CommentPopover(this.canvasContainer, this.coreMoodboard.eventBus, this.coreMoodboard);
121
- this.commentPopover.attach();
122
- // Панель свойств текста
123
- this.textPropertiesPanel = new TextPropertiesPanel(this.canvasContainer, this.coreMoodboard.eventBus, this.coreMoodboard);
124
- this.textPropertiesPanel.attach();
125
-
126
- // Панель свойств фрейма
127
- this.framePropertiesPanel = new FramePropertiesPanel(this.coreMoodboard.eventBus, this.canvasContainer, this.coreMoodboard);
128
-
129
- // Панель свойств записки
130
- this.notePropertiesPanel = new NotePropertiesPanel(this.coreMoodboard.eventBus, this.canvasContainer, this.coreMoodboard);
131
- this.filePropertiesPanel = new FilePropertiesPanel(this.coreMoodboard.eventBus, this.canvasContainer, this.coreMoodboard);
132
-
133
- // Направляющие линии выравнивания
134
- this.alignmentGuides = new AlignmentGuides(
135
- this.coreMoodboard.eventBus,
136
- this.coreMoodboard.pixi.app,
137
- () => this.coreMoodboard.state.getObjects()
138
- );
139
-
140
- // Сервис загрузки изображений
141
- this.imageUploadService = new ImageUploadService(this.coreMoodboard.apiClient);
142
-
143
- // Предоставляем доступ к сервису через core
144
- this.coreMoodboard.imageUploadService = this.imageUploadService;
145
- // Передаем ссылку на topbar в апплаер настроек для синхронизации UI
146
- if (this.settingsApplier && this.topbar) {
147
- this.settingsApplier.setUI({ topbar: this.topbar });
148
- }
149
-
150
- // Настраиваем коллбеки событий
151
- this.setupEventCallbacks();
152
-
153
- // Автоматически загружаем данные если включено
154
- if (this.options.autoLoad) {
155
- await this.loadExistingBoard();
156
- }
157
-
158
- } catch (error) {
159
- console.error('MoodBoard init failed:', error);
160
- throw error;
161
- }
71
+ await initializeMoodBoard(this);
162
72
  }
163
73
 
164
74
  /**
165
75
  * Инициализирует CoreMoodBoard
166
76
  */
167
77
  async initCoreMoodBoard() {
168
- const canvasSize = this.workspaceManager.getCanvasSize();
169
-
170
- const moodboardOptions = {
171
- boardId: this.options.boardId || 'workspace-board',
172
- width: canvasSize.width,
173
- height: canvasSize.height,
174
- // Цвет фона по умолчанию: #f7fbff (светлый голубовато-белый)
175
- backgroundColor: this.options.theme === 'dark' ? 0x2a2a2a : 0xF7FBFF,
176
- // Передаем только настройки эндпоинтов для автосохранения
177
- saveEndpoint: this.options.saveEndpoint,
178
- loadEndpoint: this.options.loadEndpoint
179
- };
180
-
181
- this.coreMoodboard = new CoreMoodBoard(this.canvasContainer, moodboardOptions);
182
- await this.coreMoodboard.init();
183
- }
184
-
185
- /**
186
- * Инициализирует панель инструментов
187
- */
188
- initToolbar() {
189
- this.toolbar = new Toolbar(
190
- this.toolbarContainer,
191
- this.coreMoodboard.eventBus,
192
- this.options.theme,
193
- {
194
- emojiBasePath: this.options.emojiBasePath || null
195
- }
196
- );
197
-
198
- // Добавляем функцию для отладки иконок в window
199
- if (typeof window !== 'undefined') {
200
- window.reloadIcon = (iconName) => this.toolbar.reloadToolbarIcon(iconName);
201
- }
202
-
203
- // Инициализируем индикатор сохранения (с фиксированными настройками)
204
- this.saveStatus = new SaveStatus(
205
- this.workspaceElement,
206
- this.coreMoodboard.eventBus
207
- );
208
-
209
- // Подписываемся на события тулбара
210
- this.coreMoodboard.eventBus.on(Events.UI.ToolbarAction, (action) => {
211
- this.actionHandler.handleToolbarAction(action);
212
- });
213
- }
214
-
215
- initTopbar() {
216
- this.topbar = new Topbar(
217
- this.topbarContainer,
218
- this.coreMoodboard.eventBus,
219
- this.options.theme
220
- );
221
-
222
- // Инициализация цвета кнопки "краска" по текущему фону канваса
223
- try {
224
- const app = this.coreMoodboard?.pixi?.app;
225
- const colorInt = (app?.renderer?.background && app.renderer.background.color) || app?.renderer?.backgroundColor;
226
- if (typeof colorInt === 'number') {
227
- const boardHex = `#${colorInt.toString(16).padStart(6, '0')}`;
228
- const btnHex = this.topbar.mapBoardToBtnHex(boardHex);
229
- this.topbar.setPaintButtonHex(btnHex || '#B3E5FC');
230
- }
231
- } catch (_) {}
232
-
233
- // Смена фона доски по выбору цвета в топбаре
234
- this.coreMoodboard.eventBus.on(Events.UI.PaintPick, ({ color }) => {
235
- if (!color) return;
236
- // Централизованное применение через SettingsApplier,
237
- // чтобы гарантировать эмит события для автосохранения
238
- if (this.settingsApplier && typeof this.settingsApplier.set === 'function') {
239
- this.settingsApplier.set({ backgroundColor: color });
240
- } else {
241
- // Fallback на случай отсутствия аплаера (не должен случаться)
242
- const hex = (typeof color === 'string' && color.startsWith('#'))
243
- ? parseInt(color.slice(1), 16)
244
- : color;
245
- if (this.coreMoodboard?.pixi?.app?.renderer) {
246
- this.coreMoodboard.pixi.app.renderer.backgroundColor = hex;
247
- }
248
- this.coreMoodboard.eventBus.emit(Events.Grid.BoardDataChanged, { settings: { backgroundColor: color } });
249
- }
250
- });
251
- }
252
-
253
- initZoombar() {
254
- // Рисуем панель зума в правом нижнем углу (внутри workspace контейнера)
255
- this.zoombar = new ZoomPanel(
256
- this.workspaceElement,
257
- this.coreMoodboard.eventBus
258
- );
259
- }
260
-
261
- initMapbar() {
262
- // Рисуем панель карты в правом нижнем углу (внутри workspace контейнера)
263
- this.mapbar = new MapPanel(
264
- this.workspaceElement,
265
- this.coreMoodboard.eventBus
266
- );
267
- }
268
-
269
- initContextMenu() {
270
- this.contextMenu = new ContextMenu(
271
- this.canvasContainer,
272
- this.coreMoodboard.eventBus
273
- );
78
+ await initializeCoreMoodBoard(this);
274
79
  }
275
80
 
276
81
  /**
@@ -362,69 +167,7 @@ export class MoodBoard {
362
167
  * Загрузка существующей доски с сервера
363
168
  */
364
169
  async loadExistingBoard() {
365
- try {
366
- const boardId = this.options.boardId;
367
-
368
- if (!boardId || !this.options.apiUrl) {
369
- console.log('📦 MoodBoard: нет boardId или apiUrl, загружаем пустую доску');
370
- this.dataManager.loadData(this.data || { objects: [] });
371
-
372
- // Вызываем коллбек onLoad
373
- if (typeof this.options.onLoad === 'function') {
374
- this.options.onLoad({ success: true, data: this.data || { objects: [] } });
375
- }
376
- return;
377
- }
378
-
379
- console.log(`📦 MoodBoard: загружаем доску ${boardId} с ${this.options.apiUrl}`);
380
-
381
- // Формируем URL для загрузки
382
- const loadUrl = this.options.apiUrl.endsWith('/')
383
- ? `${this.options.apiUrl}load/${boardId}`
384
- : `${this.options.apiUrl}/load/${boardId}`;
385
-
386
- // Загружаем с сервера через fetch
387
- const response = await fetch(loadUrl, {
388
- method: 'GET',
389
- headers: {
390
- 'Content-Type': 'application/json',
391
- 'X-CSRF-TOKEN': this.getCsrfToken()
392
- }
393
- });
394
-
395
- if (!response.ok) {
396
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
397
- }
398
-
399
- const boardData = await response.json();
400
-
401
- if (boardData && boardData.data) {
402
- console.log('✅ MoodBoard: данные загружены с сервера', boardData.data);
403
- this.dataManager.loadData(boardData.data);
404
-
405
- // Вызываем коллбек onLoad
406
- if (typeof this.options.onLoad === 'function') {
407
- this.options.onLoad({ success: true, data: boardData.data });
408
- }
409
- } else {
410
- console.log('📦 MoodBoard: нет данных с сервера, загружаем пустую доску');
411
- this.dataManager.loadData(this.data || { objects: [] });
412
-
413
- // Вызываем коллбек onLoad
414
- if (typeof this.options.onLoad === 'function') {
415
- this.options.onLoad({ success: true, data: this.data || { objects: [] } });
416
- }
417
- }
418
-
419
- } catch (error) {
420
- console.warn('⚠️ MoodBoard: ошибка загрузки доски, создаем новую:', error.message);
421
- this.dataManager.loadData(this.data || { objects: [] });
422
-
423
- // Вызываем коллбек onLoad с ошибкой
424
- if (typeof this.options.onLoad === 'function') {
425
- this.options.onLoad({ success: false, error: error.message, data: this.data || { objects: [] } });
426
- }
427
- }
170
+ await loadExistingMoodBoard(this);
428
171
  }
429
172
 
430
173
  /**
@@ -433,299 +176,55 @@ export class MoodBoard {
433
176
  * @param {string} name - имя объекта для логирования
434
177
  */
435
178
  _safeDestroy(obj, name) {
436
- if (obj) {
437
- try {
438
- if (typeof obj.destroy === 'function') {
439
- obj.destroy();
440
- } else {
441
- console.warn(`Объект ${name} не имеет метода destroy()`);
442
- }
443
- } catch (error) {
444
- console.error(`Ошибка при уничтожении ${name}:`, error);
445
- }
446
- }
179
+ safeDestroy(obj, name);
447
180
  }
448
181
 
449
182
  /**
450
183
  * Очистка ресурсов
451
184
  */
452
185
  destroy() {
453
- // Предотвращаем повторное уничтожение
454
- if (this.destroyed) {
455
- console.warn('MoodBoard уже был уничтожен');
456
- return;
457
- }
458
-
459
- // Устанавливаем флаг уничтожения
460
- this.destroyed = true;
461
-
462
- // Уничтожаем UI компоненты с безопасными проверками
463
- this._safeDestroy(this.toolbar, 'toolbar');
464
- this.toolbar = null;
465
-
466
- this._safeDestroy(this.saveStatus, 'saveStatus');
467
- this.saveStatus = null;
468
-
469
- this._safeDestroy(this.textPropertiesPanel, 'textPropertiesPanel');
470
- this.textPropertiesPanel = null;
471
-
472
- this._safeDestroy(this.framePropertiesPanel, 'framePropertiesPanel');
473
- this.framePropertiesPanel = null;
474
-
475
- this._safeDestroy(this.notePropertiesPanel, 'notePropertiesPanel');
476
- this.notePropertiesPanel = null;
477
-
478
- this._safeDestroy(this.alignmentGuides, 'alignmentGuides');
479
- this.alignmentGuides = null;
480
-
481
- // HTML-слои (текст и ручки) также нужно корректно уничтожать,
482
- // чтобы удалить DOM и отписаться от глобальных слушателей resize/DPR
483
- this._safeDestroy(this.htmlTextLayer, 'htmlTextLayer');
484
- this.htmlTextLayer = null;
485
-
486
- this._safeDestroy(this.htmlHandlesLayer, 'htmlHandlesLayer');
487
- this.htmlHandlesLayer = null;
488
-
489
- this._safeDestroy(this.commentPopover, 'commentPopover');
490
- this.commentPopover = null;
491
-
492
- this._safeDestroy(this.contextMenu, 'contextMenu');
493
- this.contextMenu = null;
494
-
495
- this._safeDestroy(this.zoombar, 'zoombar');
496
- this.zoombar = null;
497
-
498
- this._safeDestroy(this.mapbar, 'mapbar');
499
- this.mapbar = null;
500
-
501
- // Уничтожаем ядро
502
- this._safeDestroy(this.coreMoodboard, 'coreMoodboard');
503
- this.coreMoodboard = null;
504
-
505
- // Уничтожаем workspace
506
- this._safeDestroy(this.workspaceManager, 'workspaceManager');
507
- this.workspaceManager = null;
508
-
509
- // Очищаем ссылки на менеджеры
510
- this.dataManager = null;
511
- this.actionHandler = null;
512
-
513
- // Удаляем корневой класс и очищаем ссылку на контейнер
514
- if (this.container) {
515
- this.container.classList.remove('moodboard-root');
516
- }
517
- this.container = null;
518
-
519
- // Сбрасываем глобальные ссылки на слои, если они устанавливались
520
- if (typeof window !== 'undefined') {
521
- if (window.moodboardHtmlTextLayer === this.htmlTextLayer) {
522
- window.moodboardHtmlTextLayer = null;
523
- }
524
- if (window.moodboardHtmlHandlesLayer === this.htmlHandlesLayer) {
525
- window.moodboardHtmlHandlesLayer = null;
526
- }
527
- }
528
-
529
- // Вызываем коллбек onDestroy
530
- if (typeof this.options.onDestroy === 'function') {
531
- try {
532
- this.options.onDestroy();
533
- } catch (error) {
534
- console.warn('⚠️ Ошибка в коллбеке onDestroy:', error);
535
- }
536
- }
186
+ destroyMoodBoard(this);
537
187
  }
538
188
 
539
189
  /**
540
190
  * Настройка коллбеков событий
541
191
  */
542
192
  setupEventCallbacks() {
543
- if (!this.coreMoodboard || !this.coreMoodboard.eventBus) return;
544
-
545
- // Коллбек для успешного сохранения
546
- if (typeof this.options.onSave === 'function') {
547
- this.coreMoodboard.eventBus.on('save:success', (data) => {
548
- try {
549
- // Создаем объединенный скриншот с HTML текстом
550
- let screenshot = null;
551
- if (this.coreMoodboard.pixi && this.coreMoodboard.pixi.app && this.coreMoodboard.pixi.app.view) {
552
- screenshot = this.createCombinedScreenshot('image/jpeg', 0.6);
553
- }
554
-
555
- this.options.onSave({
556
- success: true,
557
- data: data,
558
- screenshot: screenshot,
559
- boardId: this.options.boardId
560
- });
561
- } catch (error) {
562
- console.warn('⚠️ Ошибка в коллбеке onSave:', error);
563
- }
564
- });
565
-
566
- // Коллбек для ошибки сохранения
567
- this.coreMoodboard.eventBus.on('save:error', (data) => {
568
- try {
569
- this.options.onSave({
570
- success: false,
571
- error: data.error,
572
- boardId: this.options.boardId
573
- });
574
- } catch (error) {
575
- console.warn('⚠️ Ошибка в коллбеке onSave:', error);
576
- }
577
- });
578
- }
193
+ bindSaveCallbacks(this);
579
194
  }
580
195
 
581
196
  /**
582
197
  * Получение CSRF токена из всех возможных источников
583
198
  */
584
199
  getCsrfToken() {
585
- return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ||
586
- window.csrfToken ||
587
- this.options.csrfToken ||
588
- '';
200
+ return getMoodBoardCsrfToken(this);
589
201
  }
590
202
 
591
203
  /**
592
204
  * Публичный метод для загрузки данных из API
593
205
  */
594
206
  async loadFromApi(boardId = null) {
595
- const targetBoardId = boardId || this.options.boardId;
596
- if (!targetBoardId) {
597
- throw new Error('boardId не указан');
598
- }
599
-
600
- // Временно меняем boardId для загрузки
601
- const originalBoardId = this.options.boardId;
602
- this.options.boardId = targetBoardId;
603
-
604
- try {
605
- await this.loadExistingBoard();
606
- } finally {
607
- // Восстанавливаем оригинальный boardId
608
- this.options.boardId = originalBoardId;
609
- }
207
+ await loadMoodBoardFromApi(this, boardId);
610
208
  }
611
209
 
612
210
  /**
613
211
  * Публичный метод для экспорта скриншота с HTML текстом
614
212
  */
615
213
  exportScreenshot(format = 'image/jpeg', quality = 0.6) {
616
- return this.createCombinedScreenshot(format, quality);
214
+ return exportMoodBoardScreenshot(this, format, quality);
617
215
  }
618
216
 
619
217
  /**
620
218
  * Разбивает текст на строки с учетом ширины элемента (имитирует HTML word-break: break-word)
621
219
  */
622
220
  wrapText(ctx, text, maxWidth) {
623
- const lines = [];
624
-
625
- if (!text || maxWidth <= 0) {
626
- return [text];
627
- }
628
-
629
- // Разбиваем по символам если не помещается (имитирует word-break: break-word)
630
- let currentLine = '';
631
-
632
- for (let i = 0; i < text.length; i++) {
633
- const char = text[i];
634
- const testLine = currentLine + char;
635
- const metrics = ctx.measureText(testLine);
636
-
637
- if (metrics.width > maxWidth && currentLine !== '') {
638
- // Текущая строка не помещается, сохраняем предыдущую
639
- lines.push(currentLine);
640
- currentLine = char;
641
- } else {
642
- currentLine = testLine;
643
- }
644
- }
645
-
646
- // Добавляем последнюю строку
647
- if (currentLine) {
648
- lines.push(currentLine);
649
- }
650
-
651
- return lines.length > 0 ? lines : [text];
221
+ return wrapMoodBoardText(ctx, text, maxWidth);
652
222
  }
653
223
 
654
224
  /**
655
225
  * Создает объединенный скриншот: PIXI canvas + HTML текстовые элементы
656
226
  */
657
227
  createCombinedScreenshot(format = 'image/jpeg', quality = 0.6) {
658
- if (!this.coreMoodboard || !this.coreMoodboard.pixi || !this.coreMoodboard.pixi.app || !this.coreMoodboard.pixi.app.view) {
659
- throw new Error('Canvas не найден');
660
- }
661
-
662
- try {
663
- // Получаем PIXI canvas
664
- const pixiCanvas = this.coreMoodboard.pixi.app.view;
665
- const pixiWidth = pixiCanvas.width;
666
- const pixiHeight = pixiCanvas.height;
667
-
668
- // Создаем временный canvas для объединения
669
- const combinedCanvas = document.createElement('canvas');
670
- combinedCanvas.width = pixiWidth;
671
- combinedCanvas.height = pixiHeight;
672
- const ctx = combinedCanvas.getContext('2d');
673
-
674
- // 1. Рисуем PIXI canvas как основу
675
- ctx.drawImage(pixiCanvas, 0, 0);
676
-
677
- // 2. Рисуем HTML текстовые элементы поверх
678
- const textElements = document.querySelectorAll('.mb-text');
679
-
680
- textElements.forEach((textEl, index) => {
681
- try {
682
- // Получаем стили и позицию элемента
683
- const computedStyle = window.getComputedStyle(textEl);
684
- const text = textEl.textContent || '';
685
-
686
- // Проверяем видимость
687
- if (computedStyle.visibility === 'hidden' || computedStyle.opacity === '0' || !text.trim()) {
688
- return;
689
- }
690
-
691
- // Используем CSS позицию (абсолютная позиция)
692
- const left = parseInt(textEl.style.left) || 0;
693
- const top = parseInt(textEl.style.top) || 0;
694
-
695
- // Настраиваем стили текста
696
- const fontSize = parseInt(computedStyle.fontSize) || 18;
697
- const fontFamily = computedStyle.fontFamily || 'Arial, sans-serif';
698
- const color = computedStyle.color || '#000000';
699
-
700
- ctx.font = `${fontSize}px ${fontFamily}`;
701
- ctx.fillStyle = color;
702
- ctx.textAlign = 'left';
703
- ctx.textBaseline = 'top';
704
-
705
- // Получаем размеры элемента
706
- const elementWidth = parseInt(textEl.style.width) || 182;
707
-
708
- // Разбиваем текст на строки и рисуем каждую строку
709
- const lines = this.wrapText(ctx, text, elementWidth);
710
- const lineHeight = fontSize * 1.3; // Межстрочный интервал
711
-
712
- lines.forEach((line, lineIndex) => {
713
- const yPos = top + (lineIndex * lineHeight) + 2;
714
- ctx.fillText(line, left, yPos);
715
- });
716
- } catch (error) {
717
- console.warn(`⚠️ Ошибка при рисовании текста ${index + 1}:`, error);
718
- }
719
- });
720
-
721
- // 3. Экспортируем объединенный результат
722
- return combinedCanvas.toDataURL(format, quality);
723
-
724
- } catch (error) {
725
- console.warn('⚠️ Ошибка при создании объединенного скриншота, используем только PIXI canvas:', error);
726
- // Fallback: только PIXI canvas
727
- const canvas = this.coreMoodboard.pixi.app.view;
728
- return canvas.toDataURL(format, quality);
729
- }
228
+ return createMoodBoardCombinedScreenshot(this, format, quality);
730
229
  }
731
230
  }
@@ -0,0 +1,47 @@
1
+ import { CoreMoodBoard } from '../../core/index.js';
2
+ import { createMoodBoardManagers, wireMoodBoardServices } from './MoodBoardManagersFactory.js';
3
+ import { createMoodBoardUi } from './MoodBoardUiFactory.js';
4
+ import { bindSaveCallbacks } from '../integration/MoodBoardEventBindings.js';
5
+
6
+ export async function initCoreMoodBoard(board) {
7
+ const canvasSize = board.workspaceManager.getCanvasSize();
8
+
9
+ const moodboardOptions = {
10
+ boardId: board.options.boardId || 'workspace-board',
11
+ width: canvasSize.width,
12
+ height: canvasSize.height,
13
+ backgroundColor: board.options.theme === 'dark' ? 0x2a2a2a : 0xF7FBFF,
14
+ saveEndpoint: board.options.saveEndpoint,
15
+ loadEndpoint: board.options.loadEndpoint,
16
+ };
17
+
18
+ board.coreMoodboard = new CoreMoodBoard(board.canvasContainer, moodboardOptions);
19
+ await board.coreMoodboard.init();
20
+ }
21
+
22
+ export async function initializeMoodBoard(board) {
23
+ try {
24
+ if (board.container) {
25
+ board.container.classList.add('moodboard-root');
26
+ }
27
+
28
+ const { workspace, toolbar, canvas, topbar } = board.workspaceManager.createWorkspaceStructure();
29
+ board.workspaceElement = workspace;
30
+ board.toolbarContainer = toolbar;
31
+ board.canvasContainer = canvas;
32
+ board.topbarContainer = topbar;
33
+
34
+ await initCoreMoodBoard(board);
35
+ createMoodBoardManagers(board);
36
+ createMoodBoardUi(board);
37
+ wireMoodBoardServices(board);
38
+ bindSaveCallbacks(board);
39
+
40
+ if (board.options.autoLoad) {
41
+ await board.loadExistingBoard();
42
+ }
43
+ } catch (error) {
44
+ console.error('MoodBoard init failed:', error);
45
+ throw error;
46
+ }
47
+ }