@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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { WorkspaceManager } from '../WorkspaceManager.js';
|
|
2
|
+
import { DataManager } from '../DataManager.js';
|
|
3
|
+
import { ActionHandler } from '../ActionHandler.js';
|
|
4
|
+
import { AlignmentGuides } from '../../tools/AlignmentGuides.js';
|
|
5
|
+
import { ImageUploadService } from '../../services/ImageUploadService.js';
|
|
6
|
+
import { SettingsApplier } from '../../services/SettingsApplier.js';
|
|
7
|
+
|
|
8
|
+
export function createWorkspaceManager(board) {
|
|
9
|
+
board.workspaceManager = new WorkspaceManager(board.container, board.options);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createMoodBoardManagers(board) {
|
|
13
|
+
board.settingsApplier = new SettingsApplier(
|
|
14
|
+
board.coreMoodboard.eventBus,
|
|
15
|
+
board.coreMoodboard.pixi,
|
|
16
|
+
board.coreMoodboard.boardService || null
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
board.coreMoodboard.settingsApplier = board.settingsApplier;
|
|
20
|
+
|
|
21
|
+
board.dataManager = new DataManager(board.coreMoodboard);
|
|
22
|
+
board.actionHandler = new ActionHandler(board.dataManager, board.workspaceManager);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function wireMoodBoardServices(board) {
|
|
26
|
+
board.alignmentGuides = new AlignmentGuides(
|
|
27
|
+
board.coreMoodboard.eventBus,
|
|
28
|
+
board.coreMoodboard.pixi.app,
|
|
29
|
+
() => board.coreMoodboard.state.getObjects()
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
board.imageUploadService = new ImageUploadService(board.coreMoodboard.apiClient);
|
|
33
|
+
board.coreMoodboard.imageUploadService = board.imageUploadService;
|
|
34
|
+
|
|
35
|
+
if (board.settingsApplier && board.topbar) {
|
|
36
|
+
board.settingsApplier.setUI({ topbar: board.topbar });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Toolbar } from '../../ui/Toolbar.js';
|
|
2
|
+
import { SaveStatus } from '../../ui/SaveStatus.js';
|
|
3
|
+
import { Topbar } from '../../ui/Topbar.js';
|
|
4
|
+
import { ZoomPanel } from '../../ui/ZoomPanel.js';
|
|
5
|
+
import { MapPanel } from '../../ui/MapPanel.js';
|
|
6
|
+
import { ContextMenu } from '../../ui/ContextMenu.js';
|
|
7
|
+
import { HtmlTextLayer } from '../../ui/HtmlTextLayer.js';
|
|
8
|
+
import { HtmlHandlesLayer } from '../../ui/HtmlHandlesLayer.js';
|
|
9
|
+
import { CommentPopover } from '../../ui/CommentPopover.js';
|
|
10
|
+
import { TextPropertiesPanel } from '../../ui/TextPropertiesPanel.js';
|
|
11
|
+
import { FramePropertiesPanel } from '../../ui/FramePropertiesPanel.js';
|
|
12
|
+
import { NotePropertiesPanel } from '../../ui/NotePropertiesPanel.js';
|
|
13
|
+
import { FilePropertiesPanel } from '../../ui/FilePropertiesPanel.js';
|
|
14
|
+
import { bindToolbarEvents, bindTopbarEvents } from '../integration/MoodBoardEventBindings.js';
|
|
15
|
+
|
|
16
|
+
function initToolbar(board) {
|
|
17
|
+
board.toolbar = new Toolbar(
|
|
18
|
+
board.toolbarContainer,
|
|
19
|
+
board.coreMoodboard.eventBus,
|
|
20
|
+
board.options.theme,
|
|
21
|
+
{
|
|
22
|
+
emojiBasePath: board.options.emojiBasePath || null,
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (typeof window !== 'undefined') {
|
|
27
|
+
window.reloadIcon = (iconName) => board.toolbar.reloadToolbarIcon(iconName);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
board.saveStatus = new SaveStatus(
|
|
31
|
+
board.workspaceElement,
|
|
32
|
+
board.coreMoodboard.eventBus
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
bindToolbarEvents(board);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function initTopbar(board) {
|
|
39
|
+
board.topbar = new Topbar(
|
|
40
|
+
board.topbarContainer,
|
|
41
|
+
board.coreMoodboard.eventBus,
|
|
42
|
+
board.options.theme
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const app = board.coreMoodboard?.pixi?.app;
|
|
47
|
+
const colorInt = (app?.renderer?.background && app.renderer.background.color) || app?.renderer?.backgroundColor;
|
|
48
|
+
if (typeof colorInt === 'number') {
|
|
49
|
+
const boardHex = `#${colorInt.toString(16).padStart(6, '0')}`;
|
|
50
|
+
const btnHex = board.topbar.mapBoardToBtnHex(boardHex);
|
|
51
|
+
board.topbar.setPaintButtonHex(btnHex || '#B3E5FC');
|
|
52
|
+
}
|
|
53
|
+
} catch (_) {}
|
|
54
|
+
|
|
55
|
+
bindTopbarEvents(board);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function initZoombar(board) {
|
|
59
|
+
board.zoombar = new ZoomPanel(
|
|
60
|
+
board.workspaceElement,
|
|
61
|
+
board.coreMoodboard.eventBus
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function initMapbar(board) {
|
|
66
|
+
board.mapbar = new MapPanel(
|
|
67
|
+
board.workspaceElement,
|
|
68
|
+
board.coreMoodboard.eventBus
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function initContextMenu(board) {
|
|
73
|
+
board.contextMenu = new ContextMenu(
|
|
74
|
+
board.canvasContainer,
|
|
75
|
+
board.coreMoodboard.eventBus
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function initHtmlLayersAndPanels(board) {
|
|
80
|
+
board.htmlTextLayer = new HtmlTextLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
81
|
+
board.htmlTextLayer.attach();
|
|
82
|
+
|
|
83
|
+
board.htmlHandlesLayer = new HtmlHandlesLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
84
|
+
board.htmlHandlesLayer.attach();
|
|
85
|
+
|
|
86
|
+
if (typeof window !== 'undefined') {
|
|
87
|
+
window.moodboardHtmlTextLayer = board.htmlTextLayer;
|
|
88
|
+
window.moodboardHtmlHandlesLayer = board.htmlHandlesLayer;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
board.commentPopover = new CommentPopover(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
92
|
+
board.commentPopover.attach();
|
|
93
|
+
|
|
94
|
+
board.textPropertiesPanel = new TextPropertiesPanel(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
95
|
+
board.textPropertiesPanel.attach();
|
|
96
|
+
|
|
97
|
+
board.framePropertiesPanel = new FramePropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
98
|
+
board.notePropertiesPanel = new NotePropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
99
|
+
board.filePropertiesPanel = new FilePropertiesPanel(board.coreMoodboard.eventBus, board.canvasContainer, board.coreMoodboard);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createMoodBoardUi(board) {
|
|
103
|
+
initToolbar(board);
|
|
104
|
+
initTopbar(board);
|
|
105
|
+
initZoombar(board);
|
|
106
|
+
initMapbar(board);
|
|
107
|
+
initContextMenu(board);
|
|
108
|
+
initHtmlLayersAndPanels(board);
|
|
109
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Events } from '../../core/events/Events.js';
|
|
2
|
+
|
|
3
|
+
export function bindToolbarEvents(board) {
|
|
4
|
+
board.coreMoodboard.eventBus.on(Events.UI.ToolbarAction, (action) => {
|
|
5
|
+
board.actionHandler.handleToolbarAction(action);
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function bindTopbarEvents(board) {
|
|
10
|
+
board.coreMoodboard.eventBus.on(Events.UI.PaintPick, ({ color }) => {
|
|
11
|
+
if (!color) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (board.settingsApplier && typeof board.settingsApplier.set === 'function') {
|
|
16
|
+
board.settingsApplier.set({ backgroundColor: color });
|
|
17
|
+
} else {
|
|
18
|
+
const hex = (typeof color === 'string' && color.startsWith('#'))
|
|
19
|
+
? parseInt(color.slice(1), 16)
|
|
20
|
+
: color;
|
|
21
|
+
if (board.coreMoodboard?.pixi?.app?.renderer) {
|
|
22
|
+
board.coreMoodboard.pixi.app.renderer.backgroundColor = hex;
|
|
23
|
+
}
|
|
24
|
+
board.coreMoodboard.eventBus.emit(Events.Grid.BoardDataChanged, { settings: { backgroundColor: color } });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function bindSaveCallbacks(board) {
|
|
30
|
+
if (!board.coreMoodboard || !board.coreMoodboard.eventBus) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof board.options.onSave === 'function') {
|
|
35
|
+
board.coreMoodboard.eventBus.on('save:success', (data) => {
|
|
36
|
+
try {
|
|
37
|
+
let screenshot = null;
|
|
38
|
+
if (board.coreMoodboard.pixi && board.coreMoodboard.pixi.app && board.coreMoodboard.pixi.app.view) {
|
|
39
|
+
screenshot = board.createCombinedScreenshot('image/jpeg', 0.6);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
board.options.onSave({
|
|
43
|
+
success: true,
|
|
44
|
+
data: data,
|
|
45
|
+
screenshot: screenshot,
|
|
46
|
+
boardId: board.options.boardId,
|
|
47
|
+
});
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.warn('⚠️ Ошибка в коллбеке onSave:', error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
board.coreMoodboard.eventBus.on('save:error', (data) => {
|
|
54
|
+
try {
|
|
55
|
+
board.options.onSave({
|
|
56
|
+
success: false,
|
|
57
|
+
error: data.error,
|
|
58
|
+
boardId: board.options.boardId,
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn('⚠️ Ошибка в коллбеке onSave:', error);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function getSeedData(board) {
|
|
2
|
+
return board.data || { objects: [] };
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function invokeOnLoad(board, payload) {
|
|
6
|
+
if (typeof board.options.onLoad === 'function') {
|
|
7
|
+
board.options.onLoad(payload);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getCsrfToken(board) {
|
|
12
|
+
return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
|
|
13
|
+
|| window.csrfToken
|
|
14
|
+
|| board.options.csrfToken
|
|
15
|
+
|| '';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function loadExistingBoard(board) {
|
|
19
|
+
try {
|
|
20
|
+
const boardId = board.options.boardId;
|
|
21
|
+
|
|
22
|
+
if (!boardId || !board.options.apiUrl) {
|
|
23
|
+
console.log('📦 MoodBoard: нет boardId или apiUrl, загружаем пустую доску');
|
|
24
|
+
const seedData = getSeedData(board);
|
|
25
|
+
board.dataManager.loadData(seedData);
|
|
26
|
+
invokeOnLoad(board, { success: true, data: seedData });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`📦 MoodBoard: загружаем доску ${boardId} с ${board.options.apiUrl}`);
|
|
31
|
+
|
|
32
|
+
const loadUrl = board.options.apiUrl.endsWith('/')
|
|
33
|
+
? `${board.options.apiUrl}load/${boardId}`
|
|
34
|
+
: `${board.options.apiUrl}/load/${boardId}`;
|
|
35
|
+
|
|
36
|
+
const response = await fetch(loadUrl, {
|
|
37
|
+
method: 'GET',
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
'X-CSRF-TOKEN': getCsrfToken(board),
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const boardData = await response.json();
|
|
49
|
+
|
|
50
|
+
if (boardData && boardData.data) {
|
|
51
|
+
console.log('✅ MoodBoard: данные загружены с сервера', boardData.data);
|
|
52
|
+
board.dataManager.loadData(boardData.data);
|
|
53
|
+
invokeOnLoad(board, { success: true, data: boardData.data });
|
|
54
|
+
} else {
|
|
55
|
+
console.log('📦 MoodBoard: нет данных с сервера, загружаем пустую доску');
|
|
56
|
+
const seedData = getSeedData(board);
|
|
57
|
+
board.dataManager.loadData(seedData);
|
|
58
|
+
invokeOnLoad(board, { success: true, data: seedData });
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn('⚠️ MoodBoard: ошибка загрузки доски, создаем новую:', error.message);
|
|
62
|
+
const seedData = getSeedData(board);
|
|
63
|
+
board.dataManager.loadData(seedData);
|
|
64
|
+
invokeOnLoad(board, { success: false, error: error.message, data: seedData });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function loadFromApi(board, boardId = null) {
|
|
69
|
+
const targetBoardId = boardId || board.options.boardId;
|
|
70
|
+
if (!targetBoardId) {
|
|
71
|
+
throw new Error('boardId не указан');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const originalBoardId = board.options.boardId;
|
|
75
|
+
board.options.boardId = targetBoardId;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await loadExistingBoard(board);
|
|
79
|
+
} finally {
|
|
80
|
+
board.options.boardId = originalBoardId;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCompositionCanvas,
|
|
3
|
+
drawHtmlTextOverlay,
|
|
4
|
+
drawPixiCanvas,
|
|
5
|
+
getPixiCanvas,
|
|
6
|
+
wrapText,
|
|
7
|
+
} from './MoodBoardScreenshotCanvas.js';
|
|
8
|
+
|
|
9
|
+
export { wrapText };
|
|
10
|
+
|
|
11
|
+
export function createCombinedScreenshot(board, format = 'image/jpeg', quality = 0.6) {
|
|
12
|
+
if (!board.coreMoodboard || !board.coreMoodboard.pixi || !board.coreMoodboard.pixi.app || !board.coreMoodboard.pixi.app.view) {
|
|
13
|
+
throw new Error('Canvas не найден');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const pixiCanvas = getPixiCanvas(board);
|
|
18
|
+
const { canvas, ctx } = createCompositionCanvas(pixiCanvas);
|
|
19
|
+
|
|
20
|
+
drawPixiCanvas(ctx, pixiCanvas);
|
|
21
|
+
drawHtmlTextOverlay(ctx);
|
|
22
|
+
|
|
23
|
+
return canvas.toDataURL(format, quality);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.warn('⚠️ Ошибка при создании объединенного скриншота, используем только PIXI canvas:', error);
|
|
26
|
+
const canvas = getPixiCanvas(board);
|
|
27
|
+
return canvas.toDataURL(format, quality);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function exportScreenshot(board, format = 'image/jpeg', quality = 0.6) {
|
|
32
|
+
return board.createCombinedScreenshot(format, quality);
|
|
33
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export function getPixiCanvas(board) {
|
|
2
|
+
return board.coreMoodboard.pixi.app.view;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function createCompositionCanvas(sourceCanvas) {
|
|
6
|
+
const combinedCanvas = document.createElement('canvas');
|
|
7
|
+
combinedCanvas.width = sourceCanvas.width;
|
|
8
|
+
combinedCanvas.height = sourceCanvas.height;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
canvas: combinedCanvas,
|
|
12
|
+
ctx: combinedCanvas.getContext('2d'),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function drawPixiCanvas(ctx, pixiCanvas) {
|
|
17
|
+
ctx.drawImage(pixiCanvas, 0, 0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function wrapText(ctx, text, maxWidth) {
|
|
21
|
+
const lines = [];
|
|
22
|
+
|
|
23
|
+
if (!text || maxWidth <= 0) {
|
|
24
|
+
return [text];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let currentLine = '';
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
30
|
+
const char = text[i];
|
|
31
|
+
const testLine = currentLine + char;
|
|
32
|
+
const metrics = ctx.measureText(testLine);
|
|
33
|
+
|
|
34
|
+
if (metrics.width > maxWidth && currentLine !== '') {
|
|
35
|
+
lines.push(currentLine);
|
|
36
|
+
currentLine = char;
|
|
37
|
+
} else {
|
|
38
|
+
currentLine = testLine;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (currentLine) {
|
|
43
|
+
lines.push(currentLine);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return lines.length > 0 ? lines : [text];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isDrawableTextElement(textEl, computedStyle) {
|
|
50
|
+
const text = textEl.textContent || '';
|
|
51
|
+
|
|
52
|
+
if (computedStyle.visibility === 'hidden' || computedStyle.opacity === '0' || !text.trim()) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function drawTextElement(ctx, textEl, index) {
|
|
60
|
+
try {
|
|
61
|
+
const computedStyle = window.getComputedStyle(textEl);
|
|
62
|
+
|
|
63
|
+
if (!isDrawableTextElement(textEl, computedStyle)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const text = textEl.textContent || '';
|
|
68
|
+
const left = parseInt(textEl.style.left) || 0;
|
|
69
|
+
const top = parseInt(textEl.style.top) || 0;
|
|
70
|
+
|
|
71
|
+
const fontSize = parseInt(computedStyle.fontSize) || 18;
|
|
72
|
+
const fontFamily = computedStyle.fontFamily || 'Arial, sans-serif';
|
|
73
|
+
const color = computedStyle.color || '#000000';
|
|
74
|
+
|
|
75
|
+
ctx.font = `${fontSize}px ${fontFamily}`;
|
|
76
|
+
ctx.fillStyle = color;
|
|
77
|
+
ctx.textAlign = 'left';
|
|
78
|
+
ctx.textBaseline = 'top';
|
|
79
|
+
|
|
80
|
+
const elementWidth = parseInt(textEl.style.width) || 182;
|
|
81
|
+
const lines = wrapText(ctx, text, elementWidth);
|
|
82
|
+
const lineHeight = fontSize * 1.3;
|
|
83
|
+
|
|
84
|
+
lines.forEach((line, lineIndex) => {
|
|
85
|
+
const yPos = top + (lineIndex * lineHeight) + 2;
|
|
86
|
+
ctx.fillText(line, left, yPos);
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn(`⚠️ Ошибка при рисовании текста ${index + 1}:`, error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function drawHtmlTextOverlay(ctx) {
|
|
94
|
+
const textElements = document.querySelectorAll('.mb-text');
|
|
95
|
+
textElements.forEach((textEl, index) => {
|
|
96
|
+
drawTextElement(ctx, textEl, index);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export function safeDestroy(obj, name) {
|
|
2
|
+
if (obj) {
|
|
3
|
+
try {
|
|
4
|
+
if (typeof obj.destroy === 'function') {
|
|
5
|
+
obj.destroy();
|
|
6
|
+
} else {
|
|
7
|
+
console.warn(`Объект ${name} не имеет метода destroy()`);
|
|
8
|
+
}
|
|
9
|
+
} catch (error) {
|
|
10
|
+
console.error(`Ошибка при уничтожении ${name}:`, error);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function destroyMoodBoard(board) {
|
|
16
|
+
if (board.destroyed) {
|
|
17
|
+
console.warn('MoodBoard уже был уничтожен');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
board.destroyed = true;
|
|
22
|
+
|
|
23
|
+
safeDestroy(board.toolbar, 'toolbar');
|
|
24
|
+
board.toolbar = null;
|
|
25
|
+
|
|
26
|
+
safeDestroy(board.topbar, 'topbar');
|
|
27
|
+
board.topbar = null;
|
|
28
|
+
|
|
29
|
+
safeDestroy(board.saveStatus, 'saveStatus');
|
|
30
|
+
board.saveStatus = null;
|
|
31
|
+
|
|
32
|
+
safeDestroy(board.textPropertiesPanel, 'textPropertiesPanel');
|
|
33
|
+
board.textPropertiesPanel = null;
|
|
34
|
+
|
|
35
|
+
safeDestroy(board.framePropertiesPanel, 'framePropertiesPanel');
|
|
36
|
+
board.framePropertiesPanel = null;
|
|
37
|
+
|
|
38
|
+
safeDestroy(board.notePropertiesPanel, 'notePropertiesPanel');
|
|
39
|
+
board.notePropertiesPanel = null;
|
|
40
|
+
|
|
41
|
+
safeDestroy(board.filePropertiesPanel, 'filePropertiesPanel');
|
|
42
|
+
board.filePropertiesPanel = null;
|
|
43
|
+
|
|
44
|
+
safeDestroy(board.alignmentGuides, 'alignmentGuides');
|
|
45
|
+
board.alignmentGuides = null;
|
|
46
|
+
|
|
47
|
+
// HTML-слои (текст и ручки) также нужно корректно уничтожать,
|
|
48
|
+
// чтобы удалить DOM и отписаться от глобальных слушателей resize/DPR
|
|
49
|
+
safeDestroy(board.htmlTextLayer, 'htmlTextLayer');
|
|
50
|
+
board.htmlTextLayer = null;
|
|
51
|
+
|
|
52
|
+
safeDestroy(board.htmlHandlesLayer, 'htmlHandlesLayer');
|
|
53
|
+
board.htmlHandlesLayer = null;
|
|
54
|
+
|
|
55
|
+
safeDestroy(board.commentPopover, 'commentPopover');
|
|
56
|
+
board.commentPopover = null;
|
|
57
|
+
|
|
58
|
+
safeDestroy(board.contextMenu, 'contextMenu');
|
|
59
|
+
board.contextMenu = null;
|
|
60
|
+
|
|
61
|
+
safeDestroy(board.zoombar, 'zoombar');
|
|
62
|
+
board.zoombar = null;
|
|
63
|
+
|
|
64
|
+
safeDestroy(board.mapbar, 'mapbar');
|
|
65
|
+
board.mapbar = null;
|
|
66
|
+
|
|
67
|
+
safeDestroy(board.coreMoodboard, 'coreMoodboard');
|
|
68
|
+
board.coreMoodboard = null;
|
|
69
|
+
|
|
70
|
+
safeDestroy(board.workspaceManager, 'workspaceManager');
|
|
71
|
+
board.workspaceManager = null;
|
|
72
|
+
|
|
73
|
+
board.dataManager = null;
|
|
74
|
+
board.actionHandler = null;
|
|
75
|
+
|
|
76
|
+
if (board.container) {
|
|
77
|
+
board.container.classList.remove('moodboard-root');
|
|
78
|
+
}
|
|
79
|
+
board.container = null;
|
|
80
|
+
|
|
81
|
+
if (typeof window !== 'undefined') {
|
|
82
|
+
if (window.moodboardHtmlTextLayer === board.htmlTextLayer) {
|
|
83
|
+
window.moodboardHtmlTextLayer = null;
|
|
84
|
+
}
|
|
85
|
+
if (window.moodboardHtmlHandlesLayer === board.htmlHandlesLayer) {
|
|
86
|
+
window.moodboardHtmlHandlesLayer = null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof board.options.onDestroy === 'function') {
|
|
91
|
+
try {
|
|
92
|
+
board.options.onDestroy();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn('⚠️ Ошибка в коллбеке onDestroy:', error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -221,19 +221,30 @@ export class FileObject {
|
|
|
221
221
|
g.lineTo(x + size * 0.8 - cornerSize, y);
|
|
222
222
|
g.endFill();
|
|
223
223
|
|
|
224
|
-
// Текст расширения на иконке
|
|
224
|
+
// Текст расширения на иконке — удаляем старый перед созданием нового (предотвращает утечку при _redraw)
|
|
225
|
+
if (this._extensionText) {
|
|
226
|
+
try {
|
|
227
|
+
this.container.removeChild(this._extensionText);
|
|
228
|
+
} catch (_) {}
|
|
229
|
+
try {
|
|
230
|
+
this._extensionText.destroy();
|
|
231
|
+
} catch (_) {}
|
|
232
|
+
this._extensionText = null;
|
|
233
|
+
}
|
|
225
234
|
if (extension && extension.length <= 4) {
|
|
226
|
-
|
|
235
|
+
this._extensionText = new PIXI.Text(extension.toUpperCase(), {
|
|
227
236
|
fontFamily: 'system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif',
|
|
228
237
|
fontSize: Math.max(8, size * 0.2),
|
|
229
238
|
fill: 0xFFFFFF,
|
|
230
239
|
align: 'center',
|
|
231
240
|
fontWeight: 'bold'
|
|
232
241
|
});
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.container.addChild(
|
|
242
|
+
this._extensionText.anchor.set(0.5, 0.5);
|
|
243
|
+
this._extensionText.x = x + size * 0.4;
|
|
244
|
+
this._extensionText.y = y + size * 0.7;
|
|
245
|
+
this.container.addChild(this._extensionText);
|
|
246
|
+
} else {
|
|
247
|
+
this._extensionText = null;
|
|
237
248
|
}
|
|
238
249
|
}
|
|
239
250
|
|
|
@@ -31,6 +31,7 @@ export class FrameObject {
|
|
|
31
31
|
}
|
|
32
32
|
this.cornerRadius = Number.isFinite(cssCornerRadius) ? cssCornerRadius : 6;
|
|
33
33
|
this.title = this.objectData.title || this.objectData.properties?.title || 'Новый';
|
|
34
|
+
this._borderVisible = true;
|
|
34
35
|
|
|
35
36
|
// Создаем контейнер для фрейма и заголовка
|
|
36
37
|
this.container = new PIXI.Container();
|
|
@@ -58,9 +59,16 @@ export class FrameObject {
|
|
|
58
59
|
|
|
59
60
|
// Подписываемся на события зума для компенсации масштабирования заголовка
|
|
60
61
|
if (this.eventBus) {
|
|
61
|
-
this.
|
|
62
|
+
this._boundOnZoomChange = this._onZoomChange.bind(this);
|
|
63
|
+
this.eventBus.on(Events.UI.ZoomPercent, this._boundOnZoomChange);
|
|
64
|
+
this._boundOnSelectionAdd = this._onSelectionAdd.bind(this);
|
|
65
|
+
this._boundOnSelectionRemove = this._onSelectionRemove.bind(this);
|
|
66
|
+
this._boundOnSelectionClear = this._onSelectionClear.bind(this);
|
|
67
|
+
this.eventBus.on(Events.Tool.SelectionAdd, this._boundOnSelectionAdd);
|
|
68
|
+
this.eventBus.on(Events.Tool.SelectionRemove, this._boundOnSelectionRemove);
|
|
69
|
+
this.eventBus.on(Events.Tool.SelectionClear, this._boundOnSelectionClear);
|
|
62
70
|
}
|
|
63
|
-
|
|
71
|
+
|
|
64
72
|
this._draw(this.width, this.height, this.fillColor);
|
|
65
73
|
// Применяем начальный масштаб и обрезку заголовка
|
|
66
74
|
this._updateTitleScale();
|
|
@@ -87,6 +95,27 @@ export class FrameObject {
|
|
|
87
95
|
this._redrawPreserveTransform(this.width, this.height, this.fillColor);
|
|
88
96
|
}
|
|
89
97
|
|
|
98
|
+
/** Скрыть/показать серую рамку (при выделении скрываем, чтобы не накладывалась на синюю) */
|
|
99
|
+
setBorderVisible(visible) {
|
|
100
|
+
if (this._borderVisible === visible) return;
|
|
101
|
+
this._borderVisible = visible;
|
|
102
|
+
this._redrawPreserveTransform(this.width, this.height, this.fillColor);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_onSelectionAdd(data) {
|
|
106
|
+
const myId = this.objectData?.id ?? this.container?._mb?.objectId;
|
|
107
|
+
if (data?.object === myId) this.setBorderVisible(false);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_onSelectionRemove(data) {
|
|
111
|
+
if (data?.object === (this.objectData?.id ?? this.container?._mb?.objectId)) this.setBorderVisible(true);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_onSelectionClear(data) {
|
|
115
|
+
const myId = this.objectData?.id ?? this.container?._mb?.objectId;
|
|
116
|
+
if (data?.objects?.includes(myId)) this.setBorderVisible(true);
|
|
117
|
+
}
|
|
118
|
+
|
|
90
119
|
/**
|
|
91
120
|
* Установить заголовок фрейма
|
|
92
121
|
* @param {string} title Новый заголовок
|
|
@@ -132,7 +161,7 @@ export class FrameObject {
|
|
|
132
161
|
const pivotX = width / 2;
|
|
133
162
|
const pivotY = height / 2;
|
|
134
163
|
|
|
135
|
-
this._draw(width, height, color);
|
|
164
|
+
this._draw(width, height, color, this._borderVisible);
|
|
136
165
|
|
|
137
166
|
container.pivot.set(pivotX, pivotY);
|
|
138
167
|
container.x = x;
|
|
@@ -145,15 +174,17 @@ export class FrameObject {
|
|
|
145
174
|
|
|
146
175
|
/**
|
|
147
176
|
* Базовая отрисовка
|
|
177
|
+
* @param {boolean} showStroke — рисовать ли серую рамку (скрываем при выделении)
|
|
148
178
|
*/
|
|
149
|
-
_draw(width, height, color) {
|
|
179
|
+
_draw(width, height, color, showStroke = true) {
|
|
150
180
|
const g = this.graphics;
|
|
151
181
|
g.clear();
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
182
|
+
if (showStroke) {
|
|
183
|
+
try {
|
|
184
|
+
g.lineStyle({ width: this.borderWidth, color: this.strokeColor, alpha: 1, alignment: 1 });
|
|
185
|
+
} catch (e) {
|
|
186
|
+
g.lineStyle(this.borderWidth, this.strokeColor, 1);
|
|
187
|
+
}
|
|
157
188
|
}
|
|
158
189
|
g.beginFill(typeof color === 'number' ? color : 0xFFFFFF, 1);
|
|
159
190
|
g.drawRoundedRect(0, 0, Math.max(0, width), Math.max(0, height), this.cornerRadius);
|
|
@@ -282,7 +313,16 @@ export class FrameObject {
|
|
|
282
313
|
*/
|
|
283
314
|
destroy() {
|
|
284
315
|
if (this.eventBus) {
|
|
285
|
-
|
|
316
|
+
if (this._boundOnZoomChange) {
|
|
317
|
+
this.eventBus.off(Events.UI.ZoomPercent, this._boundOnZoomChange);
|
|
318
|
+
this._boundOnZoomChange = null;
|
|
319
|
+
}
|
|
320
|
+
if (this._boundOnSelectionAdd) {
|
|
321
|
+
this.eventBus.off(Events.Tool.SelectionAdd, this._boundOnSelectionAdd);
|
|
322
|
+
this.eventBus.off(Events.Tool.SelectionRemove, this._boundOnSelectionRemove);
|
|
323
|
+
this.eventBus.off(Events.Tool.SelectionClear, this._boundOnSelectionClear);
|
|
324
|
+
this._boundOnSelectionAdd = this._boundOnSelectionRemove = this._boundOnSelectionClear = null;
|
|
325
|
+
}
|
|
286
326
|
}
|
|
287
327
|
if (this.container) {
|
|
288
328
|
this.container.destroy({ children: true });
|
|
@@ -186,10 +186,11 @@ export class NoteObject {
|
|
|
186
186
|
if (!size) return;
|
|
187
187
|
let w = Math.max(80, size.width || this.width);
|
|
188
188
|
let h = Math.max(60, size.height || this.height);
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.
|
|
189
|
+
// Политика квадратного resize применяется на уровне gesture/flow.
|
|
190
|
+
// Здесь важно уважать уже нормализованный размер, иначе group-resize
|
|
191
|
+
// начинает расходиться с рассчитанной рамкой группы.
|
|
192
|
+
this.width = w;
|
|
193
|
+
this.height = h;
|
|
193
194
|
|
|
194
195
|
this._redraw();
|
|
195
196
|
this._updateTextPosition();
|