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