@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,911 @@
|
|
|
1
|
+
import { Events } from '../core/events/Events.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TextPropertiesPanel — всплывающая панель свойств для текстовых объектов
|
|
5
|
+
*/
|
|
6
|
+
export class TextPropertiesPanel {
|
|
7
|
+
constructor(container, eventBus, core) {
|
|
8
|
+
this.container = container;
|
|
9
|
+
this.eventBus = eventBus;
|
|
10
|
+
this.core = core;
|
|
11
|
+
this.layer = null;
|
|
12
|
+
this.panel = null;
|
|
13
|
+
this.currentId = null;
|
|
14
|
+
this.isTextEditing = false; // Флаг режима редактирования текста
|
|
15
|
+
|
|
16
|
+
this._onDocMouseDown = this._onDocMouseDown.bind(this);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
attach() {
|
|
20
|
+
this.layer = document.createElement('div');
|
|
21
|
+
this.layer.className = 'text-properties-layer';
|
|
22
|
+
Object.assign(this.layer.style, {
|
|
23
|
+
position: 'absolute',
|
|
24
|
+
inset: '0',
|
|
25
|
+
pointerEvents: 'none',
|
|
26
|
+
zIndex: 20 // Меньше чем у комментариев, но выше основного контента
|
|
27
|
+
});
|
|
28
|
+
this.container.appendChild(this.layer);
|
|
29
|
+
|
|
30
|
+
// Подписки на события
|
|
31
|
+
this.eventBus.on(Events.Tool.SelectionAdd, () => this.updateFromSelection());
|
|
32
|
+
this.eventBus.on(Events.Tool.SelectionRemove, () => this.updateFromSelection());
|
|
33
|
+
this.eventBus.on(Events.Tool.SelectionClear, () => this.hide());
|
|
34
|
+
this.eventBus.on(Events.Tool.DragUpdate, () => this.reposition());
|
|
35
|
+
this.eventBus.on(Events.Tool.GroupDragUpdate, () => this.reposition());
|
|
36
|
+
this.eventBus.on(Events.Tool.ResizeUpdate, () => this.reposition());
|
|
37
|
+
this.eventBus.on(Events.Tool.RotateUpdate, () => this.reposition());
|
|
38
|
+
this.eventBus.on(Events.UI.ZoomPercent, () => this.reposition());
|
|
39
|
+
this.eventBus.on(Events.Tool.PanUpdate, () => this.reposition());
|
|
40
|
+
this.eventBus.on(Events.Object.Deleted, ({ objectId }) => {
|
|
41
|
+
if (this.currentId && objectId === this.currentId) this.hide();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Скрываем панель во время редактирования текста
|
|
45
|
+
this.eventBus.on(Events.UI.TextEditStart, () => {
|
|
46
|
+
this.isTextEditing = true;
|
|
47
|
+
this.hide();
|
|
48
|
+
});
|
|
49
|
+
this.eventBus.on(Events.UI.TextEditEnd, () => {
|
|
50
|
+
this.isTextEditing = false;
|
|
51
|
+
// Небольшая задержка, чтобы не появлялась сразу после завершения редактирования
|
|
52
|
+
setTimeout(() => this.updateFromSelection(), 100);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
destroy() {
|
|
57
|
+
this.hide();
|
|
58
|
+
if (this.layer) this.layer.remove();
|
|
59
|
+
this.layer = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
updateFromSelection() {
|
|
63
|
+
// Не показываем панель во время редактирования текста
|
|
64
|
+
if (this.isTextEditing) {
|
|
65
|
+
this.hide();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Показываем только для одиночного выделения текстового объекта
|
|
70
|
+
const ids = this.core?.selectTool ? Array.from(this.core.selectTool.selectedObjects || []) : [];
|
|
71
|
+
if (!ids || ids.length !== 1) {
|
|
72
|
+
this.hide();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const id = ids[0];
|
|
77
|
+
const pixi = this.core?.pixi?.objects?.get ? this.core.pixi.objects.get(id) : null;
|
|
78
|
+
if (!pixi) {
|
|
79
|
+
this.hide();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const mb = pixi._mb || {};
|
|
84
|
+
if (mb.type !== 'text') {
|
|
85
|
+
this.hide();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.currentId = id;
|
|
90
|
+
this.showFor(id);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
showFor(id) {
|
|
94
|
+
if (!this.layer) return;
|
|
95
|
+
|
|
96
|
+
if (!this.panel) {
|
|
97
|
+
this.panel = this._createPanel();
|
|
98
|
+
this.layer.appendChild(this.panel);
|
|
99
|
+
document.addEventListener('mousedown', this._onDocMouseDown, true);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.panel.style.display = 'flex';
|
|
103
|
+
this.reposition();
|
|
104
|
+
|
|
105
|
+
// Обновляем контролы в соответствии с текущими свойствами объекта
|
|
106
|
+
this._updateControlsFromObject();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
hide() {
|
|
110
|
+
this.currentId = null;
|
|
111
|
+
if (this.panel) {
|
|
112
|
+
this.panel.style.display = 'none';
|
|
113
|
+
}
|
|
114
|
+
this._hideColorDropdown(); // Закрываем выпадающую панель цветов
|
|
115
|
+
this._hideBgColorDropdown(); // Закрываем выпадающую панель фона
|
|
116
|
+
document.removeEventListener('mousedown', this._onDocMouseDown, true);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_createPanel() {
|
|
120
|
+
const panel = document.createElement('div');
|
|
121
|
+
panel.className = 'text-properties-panel';
|
|
122
|
+
Object.assign(panel.style, {
|
|
123
|
+
position: 'absolute',
|
|
124
|
+
pointerEvents: 'auto',
|
|
125
|
+
display: 'flex',
|
|
126
|
+
flexDirection: 'row',
|
|
127
|
+
alignItems: 'center',
|
|
128
|
+
gap: '12px',
|
|
129
|
+
padding: '8px 16px',
|
|
130
|
+
backgroundColor: 'white',
|
|
131
|
+
border: '1px solid #e0e0e0',
|
|
132
|
+
borderRadius: '8px',
|
|
133
|
+
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
|
134
|
+
fontSize: '14px',
|
|
135
|
+
fontFamily: 'Arial, sans-serif',
|
|
136
|
+
minWidth: '550px',
|
|
137
|
+
height: '44px'
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Создаем контролы
|
|
141
|
+
this._createFontControls(panel);
|
|
142
|
+
|
|
143
|
+
return panel;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_createFontControls(panel) {
|
|
147
|
+
// Лейбл для шрифта
|
|
148
|
+
const fontLabel = document.createElement('span');
|
|
149
|
+
fontLabel.textContent = 'Шрифт:';
|
|
150
|
+
fontLabel.style.fontSize = '12px';
|
|
151
|
+
fontLabel.style.color = '#666';
|
|
152
|
+
fontLabel.style.fontWeight = '500';
|
|
153
|
+
panel.appendChild(fontLabel);
|
|
154
|
+
|
|
155
|
+
// Выпадающий список шрифтов
|
|
156
|
+
this.fontSelect = document.createElement('select');
|
|
157
|
+
this.fontSelect.className = 'font-select';
|
|
158
|
+
Object.assign(this.fontSelect.style, {
|
|
159
|
+
border: '1px solid #ddd',
|
|
160
|
+
borderRadius: '4px',
|
|
161
|
+
padding: '4px 8px',
|
|
162
|
+
fontSize: '13px',
|
|
163
|
+
backgroundColor: 'white',
|
|
164
|
+
cursor: 'pointer',
|
|
165
|
+
minWidth: '140px'
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Список популярных шрифтов
|
|
169
|
+
const fonts = [
|
|
170
|
+
{ value: 'Arial, sans-serif', name: 'Arial' },
|
|
171
|
+
{ value: 'Helvetica, sans-serif', name: 'Helvetica' },
|
|
172
|
+
{ value: 'Georgia, serif', name: 'Georgia' },
|
|
173
|
+
{ value: 'Times New Roman, serif', name: 'Times New Roman' },
|
|
174
|
+
{ value: 'Courier New, monospace', name: 'Courier New' },
|
|
175
|
+
{ value: 'Verdana, sans-serif', name: 'Verdana' },
|
|
176
|
+
{ value: 'Tahoma, sans-serif', name: 'Tahoma' },
|
|
177
|
+
{ value: 'Impact, sans-serif', name: 'Impact' },
|
|
178
|
+
{ value: 'Comic Sans MS, cursive', name: 'Comic Sans MS' },
|
|
179
|
+
{ value: 'Trebuchet MS, sans-serif', name: 'Trebuchet MS' }
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
fonts.forEach(font => {
|
|
183
|
+
const option = document.createElement('option');
|
|
184
|
+
option.value = font.value;
|
|
185
|
+
option.textContent = font.name;
|
|
186
|
+
option.style.fontFamily = font.value;
|
|
187
|
+
this.fontSelect.appendChild(option);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Обработчик изменения шрифта
|
|
191
|
+
this.fontSelect.addEventListener('change', (e) => {
|
|
192
|
+
this._changeFontFamily(e.target.value);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
panel.appendChild(this.fontSelect);
|
|
196
|
+
|
|
197
|
+
// Лейбл для размера
|
|
198
|
+
const sizeLabel = document.createElement('span');
|
|
199
|
+
sizeLabel.textContent = 'Размер:';
|
|
200
|
+
sizeLabel.style.fontSize = '12px';
|
|
201
|
+
sizeLabel.style.color = '#666';
|
|
202
|
+
sizeLabel.style.fontWeight = '500';
|
|
203
|
+
sizeLabel.style.marginLeft = '8px';
|
|
204
|
+
panel.appendChild(sizeLabel);
|
|
205
|
+
|
|
206
|
+
// Выпадающий список размеров шрифта
|
|
207
|
+
this.fontSizeSelect = document.createElement('select');
|
|
208
|
+
this.fontSizeSelect.className = 'font-size-select';
|
|
209
|
+
Object.assign(this.fontSizeSelect.style, {
|
|
210
|
+
border: '1px solid #ddd',
|
|
211
|
+
borderRadius: '4px',
|
|
212
|
+
padding: '4px 8px',
|
|
213
|
+
fontSize: '13px',
|
|
214
|
+
backgroundColor: 'white',
|
|
215
|
+
cursor: 'pointer',
|
|
216
|
+
minWidth: '70px'
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Популярные размеры шрифта
|
|
220
|
+
const fontSizes = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 60, 72];
|
|
221
|
+
|
|
222
|
+
fontSizes.forEach(size => {
|
|
223
|
+
const option = document.createElement('option');
|
|
224
|
+
option.value = size;
|
|
225
|
+
option.textContent = `${size}px`;
|
|
226
|
+
this.fontSizeSelect.appendChild(option);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Обработчик изменения размера шрифта
|
|
230
|
+
this.fontSizeSelect.addEventListener('change', (e) => {
|
|
231
|
+
this._changeFontSize(parseInt(e.target.value));
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
panel.appendChild(this.fontSizeSelect);
|
|
235
|
+
|
|
236
|
+
// Лейбл для цвета
|
|
237
|
+
const colorLabel = document.createElement('span');
|
|
238
|
+
colorLabel.textContent = 'Цвет:';
|
|
239
|
+
colorLabel.style.fontSize = '12px';
|
|
240
|
+
colorLabel.style.color = '#666';
|
|
241
|
+
colorLabel.style.fontWeight = '500';
|
|
242
|
+
colorLabel.style.marginLeft = '8px';
|
|
243
|
+
panel.appendChild(colorLabel);
|
|
244
|
+
|
|
245
|
+
// Создаем компактный селектор цвета текста
|
|
246
|
+
this._createCompactColorSelector(panel);
|
|
247
|
+
|
|
248
|
+
// Лейбл для выделения
|
|
249
|
+
const bgColorLabel = document.createElement('span');
|
|
250
|
+
bgColorLabel.textContent = 'Выделение:';
|
|
251
|
+
bgColorLabel.style.fontSize = '12px';
|
|
252
|
+
bgColorLabel.style.color = '#666';
|
|
253
|
+
bgColorLabel.style.fontWeight = '500';
|
|
254
|
+
bgColorLabel.style.marginLeft = '8px';
|
|
255
|
+
panel.appendChild(bgColorLabel);
|
|
256
|
+
|
|
257
|
+
// Создаем компактный селектор цвета фона
|
|
258
|
+
this._createCompactBackgroundSelector(panel);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_createCompactColorSelector(panel) {
|
|
262
|
+
// Контейнер для селектора цвета
|
|
263
|
+
const colorSelectorContainer = document.createElement('div');
|
|
264
|
+
colorSelectorContainer.style.cssText = `
|
|
265
|
+
position: relative;
|
|
266
|
+
display: inline-block;
|
|
267
|
+
margin-left: 4px;
|
|
268
|
+
`;
|
|
269
|
+
|
|
270
|
+
// Кнопка показывающая текущий цвет
|
|
271
|
+
this.currentColorButton = document.createElement('button');
|
|
272
|
+
this.currentColorButton.type = 'button';
|
|
273
|
+
this.currentColorButton.title = 'Выбрать цвет';
|
|
274
|
+
this.currentColorButton.style.cssText = `
|
|
275
|
+
width: 32px;
|
|
276
|
+
height: 24px;
|
|
277
|
+
border: 2px solid #ddd;
|
|
278
|
+
border-radius: 4px;
|
|
279
|
+
background-color: #000000;
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
margin: 0;
|
|
282
|
+
padding: 0;
|
|
283
|
+
display: block;
|
|
284
|
+
box-sizing: border-box;
|
|
285
|
+
position: relative;
|
|
286
|
+
`;
|
|
287
|
+
|
|
288
|
+
// Создаем выпадающую панель с цветами
|
|
289
|
+
this.colorDropdown = document.createElement('div');
|
|
290
|
+
this.colorDropdown.style.cssText = `
|
|
291
|
+
position: absolute;
|
|
292
|
+
top: 100%;
|
|
293
|
+
left: 0;
|
|
294
|
+
background: white;
|
|
295
|
+
border: 1px solid #ddd;
|
|
296
|
+
border-radius: 6px;
|
|
297
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
298
|
+
padding: 8px;
|
|
299
|
+
display: none;
|
|
300
|
+
z-index: 10000;
|
|
301
|
+
min-width: 200px;
|
|
302
|
+
`;
|
|
303
|
+
|
|
304
|
+
// Создаем сетку цветов
|
|
305
|
+
this._createColorGrid(this.colorDropdown);
|
|
306
|
+
|
|
307
|
+
// Обработчик клика по кнопке
|
|
308
|
+
this.currentColorButton.addEventListener('click', (e) => {
|
|
309
|
+
e.stopPropagation();
|
|
310
|
+
this._toggleColorDropdown();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Закрываем панель при клике вне её
|
|
314
|
+
document.addEventListener('click', (e) => {
|
|
315
|
+
if (!colorSelectorContainer.contains(e.target)) {
|
|
316
|
+
this._hideColorDropdown();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
colorSelectorContainer.appendChild(this.currentColorButton);
|
|
321
|
+
colorSelectorContainer.appendChild(this.colorDropdown);
|
|
322
|
+
panel.appendChild(colorSelectorContainer);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
_createColorGrid(container) {
|
|
326
|
+
// Популярные цвета для текста
|
|
327
|
+
const presetColors = [
|
|
328
|
+
{ color: '#000000', name: 'Черный' },
|
|
329
|
+
{ color: '#333333', name: 'Темно-серый' },
|
|
330
|
+
{ color: '#666666', name: 'Серый' },
|
|
331
|
+
{ color: '#999999', name: 'Светло-серый' },
|
|
332
|
+
{ color: '#ffffff', name: 'Белый' },
|
|
333
|
+
{ color: '#ff0000', name: 'Красный' },
|
|
334
|
+
{ color: '#00ff00', name: 'Зеленый' },
|
|
335
|
+
{ color: '#0000ff', name: 'Синий' },
|
|
336
|
+
{ color: '#ffff00', name: 'Желтый' },
|
|
337
|
+
{ color: '#ff00ff', name: 'Фиолетовый' },
|
|
338
|
+
{ color: '#00ffff', name: 'Голубой' },
|
|
339
|
+
{ color: '#ffa500', name: 'Оранжевый' }
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
// Сетка заготовленных цветов
|
|
343
|
+
const presetsGrid = document.createElement('div');
|
|
344
|
+
presetsGrid.style.cssText = `
|
|
345
|
+
display: grid;
|
|
346
|
+
grid-template-columns: repeat(6, 1fr);
|
|
347
|
+
gap: 4px;
|
|
348
|
+
margin-bottom: 8px;
|
|
349
|
+
`;
|
|
350
|
+
|
|
351
|
+
presetColors.forEach(preset => {
|
|
352
|
+
const colorButton = document.createElement('button');
|
|
353
|
+
colorButton.type = 'button';
|
|
354
|
+
colorButton.title = preset.name;
|
|
355
|
+
colorButton.style.cssText = `
|
|
356
|
+
width: 24px;
|
|
357
|
+
height: 24px;
|
|
358
|
+
border: 1px solid #ddd;
|
|
359
|
+
border-radius: 3px;
|
|
360
|
+
background-color: ${preset.color};
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
margin: 0;
|
|
363
|
+
padding: 0;
|
|
364
|
+
display: block;
|
|
365
|
+
box-sizing: border-box;
|
|
366
|
+
${preset.color === '#ffffff' ? 'border-color: #ccc;' : ''}
|
|
367
|
+
`;
|
|
368
|
+
|
|
369
|
+
colorButton.addEventListener('click', () => {
|
|
370
|
+
this._selectColor(preset.color);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
presetsGrid.appendChild(colorButton);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
container.appendChild(presetsGrid);
|
|
377
|
+
|
|
378
|
+
// Разделитель
|
|
379
|
+
const separator = document.createElement('div');
|
|
380
|
+
separator.style.cssText = `
|
|
381
|
+
height: 1px;
|
|
382
|
+
background: #eee;
|
|
383
|
+
margin: 8px 0;
|
|
384
|
+
`;
|
|
385
|
+
container.appendChild(separator);
|
|
386
|
+
|
|
387
|
+
// Кастомный color picker
|
|
388
|
+
const customContainer = document.createElement('div');
|
|
389
|
+
customContainer.style.cssText = `
|
|
390
|
+
display: flex;
|
|
391
|
+
align-items: center;
|
|
392
|
+
gap: 8px;
|
|
393
|
+
`;
|
|
394
|
+
|
|
395
|
+
const customLabel = document.createElement('span');
|
|
396
|
+
customLabel.textContent = 'Свой цвет:';
|
|
397
|
+
customLabel.style.cssText = `
|
|
398
|
+
font-size: 12px;
|
|
399
|
+
color: #666;
|
|
400
|
+
`;
|
|
401
|
+
|
|
402
|
+
this.colorInput = document.createElement('input');
|
|
403
|
+
this.colorInput.type = 'color';
|
|
404
|
+
this.colorInput.style.cssText = `
|
|
405
|
+
width: 32px;
|
|
406
|
+
height: 24px;
|
|
407
|
+
border: 1px solid #ddd;
|
|
408
|
+
border-radius: 3px;
|
|
409
|
+
cursor: pointer;
|
|
410
|
+
padding: 0;
|
|
411
|
+
`;
|
|
412
|
+
|
|
413
|
+
this.colorInput.addEventListener('change', (e) => {
|
|
414
|
+
this._selectColor(e.target.value);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
customContainer.appendChild(customLabel);
|
|
418
|
+
customContainer.appendChild(this.colorInput);
|
|
419
|
+
container.appendChild(customContainer);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
_toggleColorDropdown() {
|
|
423
|
+
if (this.colorDropdown.style.display === 'none') {
|
|
424
|
+
this.colorDropdown.style.display = 'block';
|
|
425
|
+
} else {
|
|
426
|
+
this.colorDropdown.style.display = 'none';
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
_hideColorDropdown() {
|
|
431
|
+
if (this.colorDropdown) {
|
|
432
|
+
this.colorDropdown.style.display = 'none';
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
_selectColor(color) {
|
|
437
|
+
this._changeTextColor(color);
|
|
438
|
+
this._updateCurrentColorButton(color);
|
|
439
|
+
this._hideColorDropdown();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
_updateCurrentColorButton(color) {
|
|
443
|
+
if (this.currentColorButton) {
|
|
444
|
+
this.currentColorButton.style.backgroundColor = color;
|
|
445
|
+
this.currentColorButton.title = `Текущий цвет: ${color}`;
|
|
446
|
+
}
|
|
447
|
+
if (this.colorInput) {
|
|
448
|
+
this.colorInput.value = color;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
_createCompactBackgroundSelector(panel) {
|
|
453
|
+
// Контейнер для селектора фона
|
|
454
|
+
const bgSelectorContainer = document.createElement('div');
|
|
455
|
+
bgSelectorContainer.style.cssText = `
|
|
456
|
+
position: relative;
|
|
457
|
+
display: inline-block;
|
|
458
|
+
margin-left: 4px;
|
|
459
|
+
`;
|
|
460
|
+
|
|
461
|
+
// Кнопка показывающая текущий цвет фона
|
|
462
|
+
this.currentBgColorButton = document.createElement('button');
|
|
463
|
+
this.currentBgColorButton.type = 'button';
|
|
464
|
+
this.currentBgColorButton.title = 'Выбрать цвет выделения';
|
|
465
|
+
this.currentBgColorButton.style.cssText = `
|
|
466
|
+
width: 32px;
|
|
467
|
+
height: 24px;
|
|
468
|
+
border: 2px solid #ddd;
|
|
469
|
+
border-radius: 4px;
|
|
470
|
+
background-color: transparent;
|
|
471
|
+
cursor: pointer;
|
|
472
|
+
margin: 0;
|
|
473
|
+
padding: 0;
|
|
474
|
+
display: block;
|
|
475
|
+
box-sizing: border-box;
|
|
476
|
+
position: relative;
|
|
477
|
+
`;
|
|
478
|
+
|
|
479
|
+
// Создаем выпадающую панель с цветами фона
|
|
480
|
+
this.bgColorDropdown = document.createElement('div');
|
|
481
|
+
this.bgColorDropdown.style.cssText = `
|
|
482
|
+
position: absolute;
|
|
483
|
+
top: 100%;
|
|
484
|
+
left: 0;
|
|
485
|
+
background: white;
|
|
486
|
+
border: 1px solid #ddd;
|
|
487
|
+
border-radius: 6px;
|
|
488
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
489
|
+
padding: 8px;
|
|
490
|
+
display: none;
|
|
491
|
+
z-index: 10000;
|
|
492
|
+
min-width: 200px;
|
|
493
|
+
`;
|
|
494
|
+
|
|
495
|
+
// Создаем сетку цветов фона
|
|
496
|
+
this._createBackgroundColorGrid(this.bgColorDropdown);
|
|
497
|
+
|
|
498
|
+
// Обработчик клика по кнопке
|
|
499
|
+
this.currentBgColorButton.addEventListener('click', (e) => {
|
|
500
|
+
e.stopPropagation();
|
|
501
|
+
this._toggleBgColorDropdown();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Закрываем панель при клике вне её
|
|
505
|
+
document.addEventListener('click', (e) => {
|
|
506
|
+
if (!bgSelectorContainer.contains(e.target)) {
|
|
507
|
+
this._hideBgColorDropdown();
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
bgSelectorContainer.appendChild(this.currentBgColorButton);
|
|
512
|
+
bgSelectorContainer.appendChild(this.bgColorDropdown);
|
|
513
|
+
panel.appendChild(bgSelectorContainer);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
_createBackgroundColorGrid(container) {
|
|
517
|
+
// Цвета для выделения текста (включая прозрачный)
|
|
518
|
+
const bgColors = [
|
|
519
|
+
{ color: 'transparent', name: 'Без выделения' },
|
|
520
|
+
{ color: '#ffff99', name: 'Желтый' },
|
|
521
|
+
{ color: '#ffcc99', name: 'Оранжевый' },
|
|
522
|
+
{ color: '#ff9999', name: 'Розовый' },
|
|
523
|
+
{ color: '#ccffcc', name: 'Зеленый' },
|
|
524
|
+
{ color: '#99ccff', name: 'Голубой' },
|
|
525
|
+
{ color: '#cc99ff', name: 'Фиолетовый' },
|
|
526
|
+
{ color: '#f0f0f0', name: 'Светло-серый' },
|
|
527
|
+
{ color: '#d0d0d0', name: 'Серый' },
|
|
528
|
+
{ color: '#ffffff', name: 'Белый' },
|
|
529
|
+
{ color: '#000000', name: 'Черный' },
|
|
530
|
+
{ color: '#333333', name: 'Темно-серый' }
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
// Сетка заготовленных цветов фона
|
|
534
|
+
const presetsGrid = document.createElement('div');
|
|
535
|
+
presetsGrid.style.cssText = `
|
|
536
|
+
display: grid;
|
|
537
|
+
grid-template-columns: repeat(6, 1fr);
|
|
538
|
+
gap: 4px;
|
|
539
|
+
margin-bottom: 8px;
|
|
540
|
+
`;
|
|
541
|
+
|
|
542
|
+
bgColors.forEach(preset => {
|
|
543
|
+
const colorButton = document.createElement('button');
|
|
544
|
+
colorButton.type = 'button';
|
|
545
|
+
colorButton.title = preset.name;
|
|
546
|
+
|
|
547
|
+
if (preset.color === 'transparent') {
|
|
548
|
+
// Специальная кнопка для "без выделения"
|
|
549
|
+
colorButton.style.cssText = `
|
|
550
|
+
width: 24px;
|
|
551
|
+
height: 24px;
|
|
552
|
+
border: 1px solid #ddd;
|
|
553
|
+
border-radius: 3px;
|
|
554
|
+
background: white;
|
|
555
|
+
cursor: pointer;
|
|
556
|
+
margin: 0;
|
|
557
|
+
padding: 0;
|
|
558
|
+
display: flex;
|
|
559
|
+
align-items: center;
|
|
560
|
+
justify-content: center;
|
|
561
|
+
box-sizing: border-box;
|
|
562
|
+
position: relative;
|
|
563
|
+
`;
|
|
564
|
+
|
|
565
|
+
// Добавляем диагональную линию для обозначения "нет"
|
|
566
|
+
const line = document.createElement('div');
|
|
567
|
+
line.style.cssText = `
|
|
568
|
+
width: 20px;
|
|
569
|
+
height: 1px;
|
|
570
|
+
background: #ff0000;
|
|
571
|
+
transform: rotate(45deg);
|
|
572
|
+
`;
|
|
573
|
+
colorButton.appendChild(line);
|
|
574
|
+
} else {
|
|
575
|
+
colorButton.style.cssText = `
|
|
576
|
+
width: 24px;
|
|
577
|
+
height: 24px;
|
|
578
|
+
border: 1px solid #ddd;
|
|
579
|
+
border-radius: 3px;
|
|
580
|
+
background-color: ${preset.color};
|
|
581
|
+
cursor: pointer;
|
|
582
|
+
margin: 0;
|
|
583
|
+
padding: 0;
|
|
584
|
+
display: block;
|
|
585
|
+
box-sizing: border-box;
|
|
586
|
+
${preset.color === '#ffffff' ? 'border-color: #ccc;' : ''}
|
|
587
|
+
`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
colorButton.addEventListener('click', () => {
|
|
591
|
+
this._selectBgColor(preset.color);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
presetsGrid.appendChild(colorButton);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
container.appendChild(presetsGrid);
|
|
598
|
+
|
|
599
|
+
// Разделитель
|
|
600
|
+
const separator = document.createElement('div');
|
|
601
|
+
separator.style.cssText = `
|
|
602
|
+
height: 1px;
|
|
603
|
+
background: #eee;
|
|
604
|
+
margin: 8px 0;
|
|
605
|
+
`;
|
|
606
|
+
container.appendChild(separator);
|
|
607
|
+
|
|
608
|
+
// Кастомный color picker для фона
|
|
609
|
+
const customContainer = document.createElement('div');
|
|
610
|
+
customContainer.style.cssText = `
|
|
611
|
+
display: flex;
|
|
612
|
+
align-items: center;
|
|
613
|
+
gap: 8px;
|
|
614
|
+
`;
|
|
615
|
+
|
|
616
|
+
const customLabel = document.createElement('span');
|
|
617
|
+
customLabel.textContent = 'Свой цвет:';
|
|
618
|
+
customLabel.style.cssText = `
|
|
619
|
+
font-size: 12px;
|
|
620
|
+
color: #666;
|
|
621
|
+
`;
|
|
622
|
+
|
|
623
|
+
this.bgColorInput = document.createElement('input');
|
|
624
|
+
this.bgColorInput.type = 'color';
|
|
625
|
+
this.bgColorInput.style.cssText = `
|
|
626
|
+
width: 32px;
|
|
627
|
+
height: 24px;
|
|
628
|
+
border: 1px solid #ddd;
|
|
629
|
+
border-radius: 3px;
|
|
630
|
+
cursor: pointer;
|
|
631
|
+
padding: 0;
|
|
632
|
+
`;
|
|
633
|
+
|
|
634
|
+
this.bgColorInput.addEventListener('change', (e) => {
|
|
635
|
+
this._selectBgColor(e.target.value);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
customContainer.appendChild(customLabel);
|
|
639
|
+
customContainer.appendChild(this.bgColorInput);
|
|
640
|
+
container.appendChild(customContainer);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
_toggleBgColorDropdown() {
|
|
644
|
+
if (this.bgColorDropdown.style.display === 'none') {
|
|
645
|
+
this.bgColorDropdown.style.display = 'block';
|
|
646
|
+
} else {
|
|
647
|
+
this.bgColorDropdown.style.display = 'none';
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
_hideBgColorDropdown() {
|
|
652
|
+
if (this.bgColorDropdown) {
|
|
653
|
+
this.bgColorDropdown.style.display = 'none';
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
_selectBgColor(color) {
|
|
658
|
+
this._changeBackgroundColor(color);
|
|
659
|
+
this._updateCurrentBgColorButton(color);
|
|
660
|
+
this._hideBgColorDropdown();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
_updateCurrentBgColorButton(color) {
|
|
664
|
+
if (this.currentBgColorButton) {
|
|
665
|
+
if (color === 'transparent') {
|
|
666
|
+
this.currentBgColorButton.style.backgroundColor = 'white';
|
|
667
|
+
this.currentBgColorButton.title = 'Без выделения';
|
|
668
|
+
// Добавляем диагональную линию если её нет
|
|
669
|
+
if (!this.currentBgColorButton.querySelector('div')) {
|
|
670
|
+
const line = document.createElement('div');
|
|
671
|
+
line.style.cssText = `
|
|
672
|
+
width: 20px;
|
|
673
|
+
height: 1px;
|
|
674
|
+
background: #ff0000;
|
|
675
|
+
transform: rotate(45deg);
|
|
676
|
+
position: absolute;
|
|
677
|
+
top: 50%;
|
|
678
|
+
left: 50%;
|
|
679
|
+
transform-origin: center;
|
|
680
|
+
transform: translate(-50%, -50%) rotate(45deg);
|
|
681
|
+
`;
|
|
682
|
+
this.currentBgColorButton.appendChild(line);
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
this.currentBgColorButton.style.backgroundColor = color;
|
|
686
|
+
this.currentBgColorButton.title = `Цвет выделения: ${color}`;
|
|
687
|
+
// Убираем диагональную линию если есть
|
|
688
|
+
const line = this.currentBgColorButton.querySelector('div');
|
|
689
|
+
if (line) {
|
|
690
|
+
line.remove();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (this.bgColorInput) {
|
|
695
|
+
this.bgColorInput.value = color === 'transparent' ? '#ffff99' : color;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
_changeFontFamily(fontFamily) {
|
|
700
|
+
if (!this.currentId) return;
|
|
701
|
+
|
|
702
|
+
console.log('🔧 TextPropertiesPanel: Changing font family to:', fontFamily);
|
|
703
|
+
|
|
704
|
+
// Обновляем свойства объекта через StateManager
|
|
705
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
706
|
+
objectId: this.currentId,
|
|
707
|
+
updates: {
|
|
708
|
+
fontFamily: fontFamily
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
// Также обновляем визуальное отображение
|
|
713
|
+
this._updateTextAppearance(this.currentId, { fontFamily });
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
_changeFontSize(fontSize) {
|
|
717
|
+
if (!this.currentId) return;
|
|
718
|
+
|
|
719
|
+
console.log('🔧 TextPropertiesPanel: Changing font size to:', fontSize);
|
|
720
|
+
|
|
721
|
+
// Обновляем свойства объекта через StateManager
|
|
722
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
723
|
+
objectId: this.currentId,
|
|
724
|
+
updates: {
|
|
725
|
+
fontSize: fontSize
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// Также обновляем визуальное отображение
|
|
730
|
+
this._updateTextAppearance(this.currentId, { fontSize });
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
_changeTextColor(color) {
|
|
734
|
+
if (!this.currentId) return;
|
|
735
|
+
|
|
736
|
+
console.log('🔧 TextPropertiesPanel: Changing text color to:', color);
|
|
737
|
+
|
|
738
|
+
// Обновляем свойства объекта через StateManager
|
|
739
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
740
|
+
objectId: this.currentId,
|
|
741
|
+
updates: {
|
|
742
|
+
color: color
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// Также обновляем визуальное отображение
|
|
747
|
+
this._updateTextAppearance(this.currentId, { color });
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
_changeBackgroundColor(backgroundColor) {
|
|
751
|
+
if (!this.currentId) return;
|
|
752
|
+
|
|
753
|
+
console.log('🔧 TextPropertiesPanel: Changing background color to:', backgroundColor);
|
|
754
|
+
|
|
755
|
+
// Обновляем свойства объекта через StateManager
|
|
756
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
757
|
+
objectId: this.currentId,
|
|
758
|
+
updates: {
|
|
759
|
+
backgroundColor: backgroundColor
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Также обновляем визуальное отображение
|
|
764
|
+
this._updateTextAppearance(this.currentId, { backgroundColor });
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
_updateTextAppearance(objectId, properties) {
|
|
768
|
+
// Обновляем HTML текст через HtmlTextLayer
|
|
769
|
+
const htmlElement = document.querySelector(`[data-id="${objectId}"]`);
|
|
770
|
+
if (htmlElement) {
|
|
771
|
+
if (properties.fontFamily) {
|
|
772
|
+
htmlElement.style.fontFamily = properties.fontFamily;
|
|
773
|
+
}
|
|
774
|
+
if (properties.fontSize) {
|
|
775
|
+
htmlElement.style.fontSize = `${properties.fontSize}px`;
|
|
776
|
+
}
|
|
777
|
+
if (properties.color) {
|
|
778
|
+
htmlElement.style.color = properties.color;
|
|
779
|
+
}
|
|
780
|
+
if (properties.backgroundColor !== undefined) {
|
|
781
|
+
if (properties.backgroundColor === 'transparent') {
|
|
782
|
+
htmlElement.style.backgroundColor = '';
|
|
783
|
+
} else {
|
|
784
|
+
htmlElement.style.backgroundColor = properties.backgroundColor;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Обновляем PIXI объект и его метаданные
|
|
790
|
+
const pixiData = { objectId, pixiObject: null };
|
|
791
|
+
this.eventBus.emit(Events.Tool.GetObjectPixi, pixiData);
|
|
792
|
+
const pixiObject = pixiData.pixiObject;
|
|
793
|
+
|
|
794
|
+
if (pixiObject && pixiObject._mb) {
|
|
795
|
+
if (!pixiObject._mb.properties) {
|
|
796
|
+
pixiObject._mb.properties = {};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Обновляем свойства в метаданных объекта
|
|
800
|
+
Object.assign(pixiObject._mb.properties, properties);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Помечаем изменения для автосохранения
|
|
804
|
+
if (this.core && this.core.state) {
|
|
805
|
+
this.core.state.markDirty();
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
_updateControlsFromObject() {
|
|
810
|
+
if (!this.currentId || !this.fontSelect || !this.fontSizeSelect) return;
|
|
811
|
+
|
|
812
|
+
// Получаем текущие свойства объекта
|
|
813
|
+
const pixiData = { objectId: this.currentId, pixiObject: null };
|
|
814
|
+
this.eventBus.emit(Events.Tool.GetObjectPixi, pixiData);
|
|
815
|
+
const pixiObject = pixiData.pixiObject;
|
|
816
|
+
|
|
817
|
+
if (pixiObject && pixiObject._mb && pixiObject._mb.properties) {
|
|
818
|
+
const properties = pixiObject._mb.properties;
|
|
819
|
+
|
|
820
|
+
// Устанавливаем выбранный шрифт в селекте
|
|
821
|
+
if (properties.fontFamily) {
|
|
822
|
+
this.fontSelect.value = properties.fontFamily;
|
|
823
|
+
} else {
|
|
824
|
+
// Устанавливаем дефолтный шрифт
|
|
825
|
+
this.fontSelect.value = 'Arial, sans-serif';
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Устанавливаем размер шрифта в селекте
|
|
829
|
+
if (properties.fontSize) {
|
|
830
|
+
this.fontSizeSelect.value = properties.fontSize;
|
|
831
|
+
} else {
|
|
832
|
+
// Устанавливаем дефолтный размер
|
|
833
|
+
this.fontSizeSelect.value = '18';
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Устанавливаем цвет текста
|
|
837
|
+
if (properties.color) {
|
|
838
|
+
this._updateCurrentColorButton(properties.color);
|
|
839
|
+
} else {
|
|
840
|
+
// Устанавливаем дефолтный цвет (черный)
|
|
841
|
+
this._updateCurrentColorButton('#000000');
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Устанавливаем цвет фона
|
|
845
|
+
if (properties.backgroundColor !== undefined) {
|
|
846
|
+
this._updateCurrentBgColorButton(properties.backgroundColor);
|
|
847
|
+
} else {
|
|
848
|
+
// Устанавливаем дефолтный фон (прозрачный)
|
|
849
|
+
this._updateCurrentBgColorButton('transparent');
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
// Дефолтные значения
|
|
853
|
+
this.fontSelect.value = 'Arial, sans-serif';
|
|
854
|
+
this.fontSizeSelect.value = '18';
|
|
855
|
+
this._updateCurrentColorButton('#000000');
|
|
856
|
+
this._updateCurrentBgColorButton('transparent');
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
reposition() {
|
|
861
|
+
if (!this.panel || !this.currentId || this.panel.style.display === 'none') return;
|
|
862
|
+
|
|
863
|
+
// Получаем позицию и размеры объекта
|
|
864
|
+
const posData = { objectId: this.currentId, position: null };
|
|
865
|
+
const sizeData = { objectId: this.currentId, size: null };
|
|
866
|
+
this.eventBus.emit(Events.Tool.GetObjectPosition, posData);
|
|
867
|
+
this.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
|
|
868
|
+
|
|
869
|
+
if (!posData.position || !sizeData.size) return;
|
|
870
|
+
|
|
871
|
+
// Получаем зум и позицию мира
|
|
872
|
+
const worldLayer = this.core?.pixi?.worldLayer;
|
|
873
|
+
const scale = worldLayer?.scale?.x || 1;
|
|
874
|
+
const worldX = worldLayer?.x || 0;
|
|
875
|
+
const worldY = worldLayer?.y || 0;
|
|
876
|
+
|
|
877
|
+
// Преобразуем координаты объекта в экранные координаты
|
|
878
|
+
const screenX = posData.position.x * scale + worldX;
|
|
879
|
+
const screenY = posData.position.y * scale + worldY;
|
|
880
|
+
const objectWidth = sizeData.size.width * scale;
|
|
881
|
+
|
|
882
|
+
// Позиционируем панель над объектом
|
|
883
|
+
const panelX = screenX + (objectWidth / 2) - (this.panel.offsetWidth / 2);
|
|
884
|
+
const panelY = screenY - this.panel.offsetHeight - 10; // 10px отступ от объекта
|
|
885
|
+
|
|
886
|
+
// Проверяем границы контейнера
|
|
887
|
+
const containerRect = this.container.getBoundingClientRect();
|
|
888
|
+
const finalX = Math.max(10, Math.min(panelX, containerRect.width - this.panel.offsetWidth - 10));
|
|
889
|
+
const finalY = Math.max(10, panelY);
|
|
890
|
+
|
|
891
|
+
this.panel.style.left = `${finalX}px`;
|
|
892
|
+
this.panel.style.top = `${finalY}px`;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
_onDocMouseDown(e) {
|
|
896
|
+
// Скрываем панель при клике вне неё и вне текстового объекта
|
|
897
|
+
if (!this.panel || !e.target) return;
|
|
898
|
+
|
|
899
|
+
// Если клик внутри панели - не скрываем
|
|
900
|
+
if (this.panel.contains(e.target)) return;
|
|
901
|
+
|
|
902
|
+
// Проверяем, не кликнули ли по текущему текстовому объекту
|
|
903
|
+
const rect = this.container.getBoundingClientRect();
|
|
904
|
+
const x = e.clientX - rect.left;
|
|
905
|
+
const y = e.clientY - rect.top;
|
|
906
|
+
|
|
907
|
+
// Здесь можно добавить проверку попадания в текстовый объект
|
|
908
|
+
// Пока просто скрываем панель
|
|
909
|
+
this.hide();
|
|
910
|
+
}
|
|
911
|
+
}
|