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