@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.
- package/package.json +11 -1
- package/src/assets/icons/rotate-icon.svg +1 -1
- package/src/core/HistoryManager.js +16 -16
- package/src/core/KeyboardManager.js +48 -539
- package/src/core/PixiEngine.js +9 -9
- package/src/core/SaveManager.js +56 -31
- package/src/core/bootstrap/CoreInitializer.js +65 -0
- package/src/core/commands/DeleteObjectCommand.js +8 -0
- package/src/core/commands/GroupDeleteCommand.js +75 -0
- package/src/core/commands/GroupRotateCommand.js +6 -0
- package/src/core/commands/UpdateContentCommand.js +52 -0
- package/src/core/commands/UpdateFramePropertiesCommand.js +98 -0
- package/src/core/commands/UpdateFrameTypeCommand.js +85 -0
- package/src/core/commands/UpdateNoteStyleCommand.js +88 -0
- package/src/core/commands/UpdateTextStyleCommand.js +90 -0
- package/src/core/commands/index.js +6 -0
- package/src/core/events/Events.js +6 -0
- package/src/core/flows/ClipboardFlow.js +553 -0
- package/src/core/flows/LayerAndViewportFlow.js +283 -0
- package/src/core/flows/ObjectLifecycleFlow.js +336 -0
- package/src/core/flows/SaveFlow.js +34 -0
- package/src/core/flows/TransformFlow.js +277 -0
- package/src/core/flows/TransformFlowResizeHelpers.js +83 -0
- package/src/core/index.js +41 -1773
- package/src/core/keyboard/KeyboardClipboardImagePaste.js +190 -0
- package/src/core/keyboard/KeyboardContextGuards.js +35 -0
- package/src/core/keyboard/KeyboardEventRouter.js +92 -0
- package/src/core/keyboard/KeyboardSelectionActions.js +103 -0
- package/src/core/keyboard/KeyboardShortcutMap.js +31 -0
- package/src/core/keyboard/KeyboardToolSwitching.js +26 -0
- package/src/core/rendering/ObjectRenderer.js +3 -7
- package/src/grid/BaseGrid.js +26 -0
- package/src/grid/CrossGrid.js +7 -6
- package/src/grid/DotGrid.js +89 -33
- package/src/grid/DotGridZoomPhases.js +42 -0
- package/src/grid/LineGrid.js +22 -21
- package/src/moodboard/MoodBoard.js +31 -532
- package/src/moodboard/bootstrap/MoodBoardInitializer.js +47 -0
- package/src/moodboard/bootstrap/MoodBoardManagersFactory.js +38 -0
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +109 -0
- package/src/moodboard/integration/MoodBoardEventBindings.js +65 -0
- package/src/moodboard/integration/MoodBoardLoadApi.js +82 -0
- package/src/moodboard/integration/MoodBoardScreenshotApi.js +33 -0
- package/src/moodboard/integration/MoodBoardScreenshotCanvas.js +98 -0
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +97 -0
- package/src/objects/FileObject.js +17 -6
- package/src/objects/FrameObject.js +50 -10
- package/src/objects/NoteObject.js +5 -4
- package/src/services/BoardService.js +42 -2
- package/src/services/FrameService.js +83 -42
- package/src/services/ResizePolicyService.js +152 -0
- package/src/services/SettingsApplier.js +7 -2
- package/src/services/ZoomPanController.js +35 -9
- package/src/tools/ToolManager.js +30 -537
- package/src/tools/board-tools/PanTool.js +5 -11
- package/src/tools/manager/ToolActivationController.js +49 -0
- package/src/tools/manager/ToolEventRouter.js +396 -0
- package/src/tools/manager/ToolManagerGuards.js +33 -0
- package/src/tools/manager/ToolManagerLifecycle.js +110 -0
- package/src/tools/manager/ToolRegistry.js +33 -0
- package/src/tools/object-tools/DrawingTool.js +48 -14
- package/src/tools/object-tools/PlacementTool.js +50 -1049
- package/src/tools/object-tools/PlacementToolV2.js +88 -0
- package/src/tools/object-tools/SelectTool.js +174 -2681
- package/src/tools/object-tools/placement/GhostController.js +504 -0
- package/src/tools/object-tools/placement/PlacementCoordinateResolver.js +20 -0
- package/src/tools/object-tools/placement/PlacementEventsBridge.js +91 -0
- package/src/tools/object-tools/placement/PlacementInputRouter.js +267 -0
- package/src/tools/object-tools/placement/PlacementPayloadFactory.js +111 -0
- package/src/tools/object-tools/placement/PlacementSessionStore.js +18 -0
- package/src/tools/object-tools/selection/BoxSelectController.js +0 -5
- package/src/tools/object-tools/selection/CloneFlowController.js +71 -0
- package/src/tools/object-tools/selection/CoordinateMapper.js +10 -0
- package/src/tools/object-tools/selection/CursorController.js +78 -0
- package/src/tools/object-tools/selection/FileNameInlineEditorController.js +184 -0
- package/src/tools/object-tools/selection/HitTestService.js +102 -0
- package/src/tools/object-tools/selection/InlineEditorController.js +24 -0
- package/src/tools/object-tools/selection/InlineEditorDomFactory.js +50 -0
- package/src/tools/object-tools/selection/InlineEditorListenersRegistry.js +14 -0
- package/src/tools/object-tools/selection/InlineEditorPositioningService.js +25 -0
- package/src/tools/object-tools/selection/NoteInlineEditorController.js +113 -0
- package/src/tools/object-tools/selection/SelectInputRouter.js +267 -0
- package/src/tools/object-tools/selection/SelectToolLifecycleController.js +128 -0
- package/src/tools/object-tools/selection/SelectToolSetup.js +134 -0
- package/src/tools/object-tools/selection/SelectionOverlayService.js +81 -0
- package/src/tools/object-tools/selection/SelectionStateController.js +91 -0
- package/src/tools/object-tools/selection/TextEditorDomFactory.js +65 -0
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +266 -0
- package/src/tools/object-tools/selection/TextEditorLifecycleRegistry.js +90 -0
- package/src/tools/object-tools/selection/TextEditorPositioningService.js +158 -0
- package/src/tools/object-tools/selection/TextEditorSyncService.js +110 -0
- package/src/tools/object-tools/selection/TextInlineEditorController.js +457 -0
- package/src/tools/object-tools/selection/TransformInteractionController.js +466 -0
- package/src/ui/FilePropertiesPanel.js +61 -32
- package/src/ui/FramePropertiesPanel.js +176 -101
- package/src/ui/HtmlHandlesLayer.js +121 -999
- package/src/ui/MapPanel.js +12 -7
- package/src/ui/NotePropertiesPanel.js +17 -2
- package/src/ui/TextPropertiesPanel.js +124 -738
- package/src/ui/Toolbar.js +71 -1180
- package/src/ui/Topbar.js +23 -25
- package/src/ui/ZoomPanel.js +16 -5
- package/src/ui/handles/GroupSelectionHandlesController.js +29 -0
- package/src/ui/handles/HandlesDomRenderer.js +278 -0
- package/src/ui/handles/HandlesEventBridge.js +102 -0
- package/src/ui/handles/HandlesInteractionController.js +772 -0
- package/src/ui/handles/HandlesPositioningService.js +206 -0
- package/src/ui/handles/SingleSelectionHandlesController.js +22 -0
- package/src/ui/styles/toolbar.css +2 -0
- package/src/ui/styles/workspace.css +13 -6
- package/src/ui/text-properties/TextPropertiesPanelBindings.js +92 -0
- package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +77 -0
- package/src/ui/text-properties/TextPropertiesPanelMapper.js +173 -0
- package/src/ui/text-properties/TextPropertiesPanelRenderer.js +434 -0
- package/src/ui/text-properties/TextPropertiesPanelState.js +39 -0
- package/src/ui/toolbar/ToolbarActionRouter.js +193 -0
- package/src/ui/toolbar/ToolbarDialogsController.js +186 -0
- package/src/ui/toolbar/ToolbarPopupsController.js +662 -0
- package/src/ui/toolbar/ToolbarRenderer.js +97 -0
- package/src/ui/toolbar/ToolbarStateController.js +79 -0
- package/src/ui/toolbar/ToolbarTooltipController.js +52 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|