@sequent-org/moodboard 1.2.119 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -1
- package/src/assets/icons/rotate-icon.svg +1 -1
- package/src/core/HistoryManager.js +16 -16
- package/src/core/KeyboardManager.js +48 -539
- package/src/core/PixiEngine.js +9 -9
- package/src/core/SaveManager.js +56 -31
- package/src/core/bootstrap/CoreInitializer.js +65 -0
- package/src/core/commands/DeleteObjectCommand.js +8 -0
- package/src/core/commands/GroupDeleteCommand.js +75 -0
- package/src/core/commands/GroupRotateCommand.js +6 -0
- package/src/core/commands/UpdateContentCommand.js +52 -0
- package/src/core/commands/UpdateFramePropertiesCommand.js +98 -0
- package/src/core/commands/UpdateFrameTypeCommand.js +85 -0
- package/src/core/commands/UpdateNoteStyleCommand.js +88 -0
- package/src/core/commands/UpdateTextStyleCommand.js +90 -0
- package/src/core/commands/index.js +6 -0
- package/src/core/events/Events.js +6 -0
- package/src/core/flows/ClipboardFlow.js +553 -0
- package/src/core/flows/LayerAndViewportFlow.js +283 -0
- package/src/core/flows/ObjectLifecycleFlow.js +336 -0
- package/src/core/flows/SaveFlow.js +34 -0
- package/src/core/flows/TransformFlow.js +277 -0
- package/src/core/flows/TransformFlowResizeHelpers.js +83 -0
- package/src/core/index.js +41 -1773
- package/src/core/keyboard/KeyboardClipboardImagePaste.js +190 -0
- package/src/core/keyboard/KeyboardContextGuards.js +35 -0
- package/src/core/keyboard/KeyboardEventRouter.js +92 -0
- package/src/core/keyboard/KeyboardSelectionActions.js +103 -0
- package/src/core/keyboard/KeyboardShortcutMap.js +31 -0
- package/src/core/keyboard/KeyboardToolSwitching.js +26 -0
- package/src/core/rendering/ObjectRenderer.js +3 -7
- package/src/grid/BaseGrid.js +26 -0
- package/src/grid/CrossGrid.js +7 -6
- package/src/grid/DotGrid.js +89 -33
- package/src/grid/DotGridZoomPhases.js +42 -0
- package/src/grid/LineGrid.js +22 -21
- package/src/moodboard/MoodBoard.js +31 -532
- package/src/moodboard/bootstrap/MoodBoardInitializer.js +47 -0
- package/src/moodboard/bootstrap/MoodBoardManagersFactory.js +38 -0
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +109 -0
- package/src/moodboard/integration/MoodBoardEventBindings.js +65 -0
- package/src/moodboard/integration/MoodBoardLoadApi.js +82 -0
- package/src/moodboard/integration/MoodBoardScreenshotApi.js +33 -0
- package/src/moodboard/integration/MoodBoardScreenshotCanvas.js +98 -0
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +97 -0
- package/src/objects/FileObject.js +17 -6
- package/src/objects/FrameObject.js +50 -10
- package/src/objects/NoteObject.js +5 -4
- package/src/services/BoardService.js +42 -2
- package/src/services/FrameService.js +83 -42
- package/src/services/ResizePolicyService.js +152 -0
- package/src/services/SettingsApplier.js +7 -2
- package/src/services/ZoomPanController.js +35 -9
- package/src/tools/ToolManager.js +30 -537
- package/src/tools/board-tools/PanTool.js +5 -11
- package/src/tools/manager/ToolActivationController.js +49 -0
- package/src/tools/manager/ToolEventRouter.js +396 -0
- package/src/tools/manager/ToolManagerGuards.js +33 -0
- package/src/tools/manager/ToolManagerLifecycle.js +110 -0
- package/src/tools/manager/ToolRegistry.js +33 -0
- package/src/tools/object-tools/DrawingTool.js +48 -14
- package/src/tools/object-tools/PlacementTool.js +50 -1049
- package/src/tools/object-tools/PlacementToolV2.js +88 -0
- package/src/tools/object-tools/SelectTool.js +174 -2681
- package/src/tools/object-tools/placement/GhostController.js +504 -0
- package/src/tools/object-tools/placement/PlacementCoordinateResolver.js +20 -0
- package/src/tools/object-tools/placement/PlacementEventsBridge.js +91 -0
- package/src/tools/object-tools/placement/PlacementInputRouter.js +267 -0
- package/src/tools/object-tools/placement/PlacementPayloadFactory.js +111 -0
- package/src/tools/object-tools/placement/PlacementSessionStore.js +18 -0
- package/src/tools/object-tools/selection/BoxSelectController.js +0 -5
- package/src/tools/object-tools/selection/CloneFlowController.js +71 -0
- package/src/tools/object-tools/selection/CoordinateMapper.js +10 -0
- package/src/tools/object-tools/selection/CursorController.js +78 -0
- package/src/tools/object-tools/selection/FileNameInlineEditorController.js +184 -0
- package/src/tools/object-tools/selection/HitTestService.js +102 -0
- package/src/tools/object-tools/selection/InlineEditorController.js +24 -0
- package/src/tools/object-tools/selection/InlineEditorDomFactory.js +50 -0
- package/src/tools/object-tools/selection/InlineEditorListenersRegistry.js +14 -0
- package/src/tools/object-tools/selection/InlineEditorPositioningService.js +25 -0
- package/src/tools/object-tools/selection/NoteInlineEditorController.js +113 -0
- package/src/tools/object-tools/selection/SelectInputRouter.js +267 -0
- package/src/tools/object-tools/selection/SelectToolLifecycleController.js +128 -0
- package/src/tools/object-tools/selection/SelectToolSetup.js +134 -0
- package/src/tools/object-tools/selection/SelectionOverlayService.js +81 -0
- package/src/tools/object-tools/selection/SelectionStateController.js +91 -0
- package/src/tools/object-tools/selection/TextEditorDomFactory.js +65 -0
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +266 -0
- package/src/tools/object-tools/selection/TextEditorLifecycleRegistry.js +90 -0
- package/src/tools/object-tools/selection/TextEditorPositioningService.js +158 -0
- package/src/tools/object-tools/selection/TextEditorSyncService.js +110 -0
- package/src/tools/object-tools/selection/TextInlineEditorController.js +457 -0
- package/src/tools/object-tools/selection/TransformInteractionController.js +466 -0
- package/src/ui/FilePropertiesPanel.js +61 -32
- package/src/ui/FramePropertiesPanel.js +176 -101
- package/src/ui/HtmlHandlesLayer.js +121 -999
- package/src/ui/MapPanel.js +12 -7
- package/src/ui/NotePropertiesPanel.js +17 -2
- package/src/ui/TextPropertiesPanel.js +124 -738
- package/src/ui/Toolbar.js +82 -1181
- 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 +665 -0
- package/src/ui/toolbar/ToolbarRenderer.js +97 -0
- package/src/ui/toolbar/ToolbarStateController.js +79 -0
- package/src/ui/toolbar/ToolbarTooltipController.js +52 -0
- package/src/utils/emojiLoaderNoBundler.js +1 -1
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { Events } from '../../../core/events/Events.js';
|
|
2
|
+
import { tryStartAltCloneDuringDrag, resetCloneStateAfterDragEnd } from './CloneFlowController.js';
|
|
3
|
+
|
|
4
|
+
export function handleObjectSelect(objectId, event) {
|
|
5
|
+
if (!this.isMultiSelect) {
|
|
6
|
+
this.clearSelection();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (this.selection.has(objectId)) {
|
|
10
|
+
if (this.isMultiSelect) {
|
|
11
|
+
this.removeFromSelection(objectId);
|
|
12
|
+
} else if (this.selection.size() > 1) {
|
|
13
|
+
// Перетаскивание группы
|
|
14
|
+
this.startGroupDrag(event);
|
|
15
|
+
} else {
|
|
16
|
+
// Начинаем перетаскивание
|
|
17
|
+
this.startDrag(objectId, event);
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
this.addToSelection(objectId);
|
|
21
|
+
if (this.selection.size() > 1) {
|
|
22
|
+
this.startGroupDrag(event);
|
|
23
|
+
} else {
|
|
24
|
+
this.startDrag(objectId, event);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function startDrag(objectId, event) {
|
|
30
|
+
this.isDragging = true;
|
|
31
|
+
this.dragTarget = objectId;
|
|
32
|
+
// Сообщаем HtmlHandlesLayer о начале перетаскивания одиночного объекта
|
|
33
|
+
this.emit(Events.Tool.DragStart, { object: objectId });
|
|
34
|
+
|
|
35
|
+
// Получаем текущую позицию объекта
|
|
36
|
+
const objectData = { objectId, position: null };
|
|
37
|
+
this.emit(Events.Tool.GetObjectPosition, objectData);
|
|
38
|
+
// Нормализуем координаты в мировые (worldLayer), чтобы убрать влияние зума
|
|
39
|
+
const w = this._toWorld(event.x, event.y);
|
|
40
|
+
// Запоминаем смещение точки захвата курсора относительно левого-верхнего угла объекта (в мировых координатах)
|
|
41
|
+
if (objectData.position) {
|
|
42
|
+
this._dragGrabOffset = {
|
|
43
|
+
x: w.x - objectData.position.x,
|
|
44
|
+
y: w.y - objectData.position.y
|
|
45
|
+
};
|
|
46
|
+
} else {
|
|
47
|
+
this._dragGrabOffset = null;
|
|
48
|
+
}
|
|
49
|
+
const worldEvent = { ...event, x: w.x, y: w.y };
|
|
50
|
+
if (this._dragCtrl) this._dragCtrl.start(objectId, worldEvent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function updateDrag(event) {
|
|
54
|
+
// Перетаскивание группы
|
|
55
|
+
if (this.isGroupDragging && this._groupDragCtrl) {
|
|
56
|
+
const w = this._toWorld(event.x, event.y);
|
|
57
|
+
this._groupDragCtrl.update({ ...event, x: w.x, y: w.y });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
tryStartAltCloneDuringDrag.call(this, event);
|
|
61
|
+
// Если ожидаем создание копии — продолжаем двигать текущую цель (исходник)
|
|
62
|
+
if (!this.dragTarget) return;
|
|
63
|
+
|
|
64
|
+
if (this._dragCtrl) {
|
|
65
|
+
const w = this._toWorld(event.x, event.y);
|
|
66
|
+
this._dragCtrl.update({ ...event, x: w.x, y: w.y });
|
|
67
|
+
}
|
|
68
|
+
// Обновление позиции в ядро уже выполняется через SimpleDragController (drag:update)
|
|
69
|
+
// Дополнительный эмит здесь не нужен и приводил к некорректным данным
|
|
70
|
+
|
|
71
|
+
// Обновляем ручки во время перетаскивания
|
|
72
|
+
if (this.resizeHandles && this.selection.has(this.dragTarget)) {
|
|
73
|
+
this.resizeHandles.updateHandles();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function endDrag() {
|
|
78
|
+
if (this.isGroupDragging) {
|
|
79
|
+
const ids = this.selection.toArray();
|
|
80
|
+
this.emit(Events.Tool.GroupDragEnd, { objects: ids });
|
|
81
|
+
if (this._groupDragCtrl) this._groupDragCtrl.end();
|
|
82
|
+
} else if (this.dragTarget) {
|
|
83
|
+
if (this._dragCtrl) this._dragCtrl.end();
|
|
84
|
+
// Сообщаем о завершении перетаскивания одиночного объекта
|
|
85
|
+
this.emit(Events.Tool.DragEnd, { object: this.dragTarget });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.isDragging = false;
|
|
89
|
+
this.isGroupDragging = false;
|
|
90
|
+
this.dragTarget = null;
|
|
91
|
+
this.dragOffset = { x: 0, y: 0 };
|
|
92
|
+
resetCloneStateAfterDragEnd.call(this);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function startResize(handle, objectId) {
|
|
96
|
+
// Групповой resize
|
|
97
|
+
if (objectId === this.groupId && this.selection.size() > 1) {
|
|
98
|
+
this.isGroupResizing = true;
|
|
99
|
+
this.resizeHandle = handle;
|
|
100
|
+
if (this._groupResizeCtrl) this._groupResizeCtrl.start(handle, { x: this.currentX, y: this.currentY });
|
|
101
|
+
this.isResizing = false;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.isResizing = true;
|
|
106
|
+
this.resizeHandle = handle;
|
|
107
|
+
this.dragTarget = objectId;
|
|
108
|
+
if (this._resizeCtrl) {
|
|
109
|
+
const w = this._toWorld(this.currentX, this.currentY);
|
|
110
|
+
this._resizeCtrl.start(handle, objectId, { x: w.x, y: w.y });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function updateResize(event) {
|
|
115
|
+
// Групповой resize
|
|
116
|
+
if (this.isGroupResizing && this._groupResizeCtrl) {
|
|
117
|
+
const w = this._toWorld(event.x, event.y);
|
|
118
|
+
this._groupResizeCtrl.update({ ...event, x: w.x, y: w.y });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (this._resizeCtrl) {
|
|
123
|
+
const w = this._toWorld(event.x, event.y);
|
|
124
|
+
this._resizeCtrl.update({ ...event, x: w.x, y: w.y }, {
|
|
125
|
+
calculateNewSize: (handleType, startBounds, dx, dy, keepAR) => {
|
|
126
|
+
const rot = (() => { const d = { objectId: this.dragTarget, rotation: 0 }; this.emit(Events.Tool.GetObjectRotation, d); return d.rotation || 0; })();
|
|
127
|
+
return this.calculateNewSize(handleType, startBounds, dx, dy, keepAR, rot);
|
|
128
|
+
},
|
|
129
|
+
calculatePositionOffset: (handleType, startBounds, newSize, objectRotation) => {
|
|
130
|
+
return this.calculatePositionOffset(handleType, startBounds, newSize, objectRotation);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Обновляем ручки в реальном времени во время resize
|
|
136
|
+
// HTML-ручки обновляются слоем HtmlHandlesLayer
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function endResize() {
|
|
140
|
+
if (this.isGroupResizing) {
|
|
141
|
+
if (this._groupResizeCtrl) this._groupResizeCtrl.end();
|
|
142
|
+
this.isGroupResizing = false;
|
|
143
|
+
this.resizeHandle = null;
|
|
144
|
+
this.groupStartBounds = null;
|
|
145
|
+
this.groupStartMouse = null;
|
|
146
|
+
this.groupObjectsInitial = null;
|
|
147
|
+
// Принудительно синхронизируем ручки и рамку после завершения, чтобы отлипли от курсора
|
|
148
|
+
const gb = this.computeGroupBounds();
|
|
149
|
+
this.ensureGroupBoundsGraphics(gb);
|
|
150
|
+
if (this.groupBoundsGraphics) {
|
|
151
|
+
this.groupBoundsGraphics.rotation = 0;
|
|
152
|
+
this.groupBoundsGraphics.pivot.set(0, 0);
|
|
153
|
+
this.groupBoundsGraphics.position.set(gb.x, gb.y);
|
|
154
|
+
}
|
|
155
|
+
if (this.resizeHandles) {
|
|
156
|
+
// Отключаем старые PIXI-ручки
|
|
157
|
+
this.resizeHandles.hideHandles();
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (this._resizeCtrl) this._resizeCtrl.end();
|
|
162
|
+
|
|
163
|
+
// Обновляем позицию ручек после resize
|
|
164
|
+
// HTML-ручки обновляются слоем HtmlHandlesLayer
|
|
165
|
+
|
|
166
|
+
this.isResizing = false;
|
|
167
|
+
this.resizeHandle = null;
|
|
168
|
+
this.resizeStartBounds = null;
|
|
169
|
+
this.resizeStartMousePos = null;
|
|
170
|
+
this.resizeStartPosition = null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function startRotate(objectId) {
|
|
174
|
+
// Групповой поворот
|
|
175
|
+
if (objectId === this.groupId && this.selection.size() > 1) {
|
|
176
|
+
this.isGroupRotating = true;
|
|
177
|
+
const gb = this.computeGroupBounds();
|
|
178
|
+
this.groupRotateBounds = gb;
|
|
179
|
+
this.rotateCenter = { x: gb.x + gb.width / 2, y: gb.y + gb.height / 2 };
|
|
180
|
+
this.rotateStartAngle = 0;
|
|
181
|
+
this.rotateCurrentAngle = 0;
|
|
182
|
+
this.rotateStartMouseAngle = Math.atan2(
|
|
183
|
+
this.currentY - this.rotateCenter.y,
|
|
184
|
+
this.currentX - this.rotateCenter.x
|
|
185
|
+
);
|
|
186
|
+
// Настраиваем целевой прямоугольник для ручек: центр в pivot для корректного вращения
|
|
187
|
+
this.ensureGroupBoundsGraphics(gb);
|
|
188
|
+
if (this.groupBoundsGraphics) {
|
|
189
|
+
this.groupBoundsGraphics.pivot.set(gb.width / 2, gb.height / 2);
|
|
190
|
+
this.groupBoundsGraphics.position.set(this.rotateCenter.x, this.rotateCenter.y);
|
|
191
|
+
this.groupBoundsGraphics.rotation = 0;
|
|
192
|
+
}
|
|
193
|
+
// Подгоняем визуальную рамку под центр
|
|
194
|
+
if (this.groupSelectionGraphics) {
|
|
195
|
+
this.groupSelectionGraphics.pivot.set(0, 0);
|
|
196
|
+
this.groupSelectionGraphics.position.set(0, 0);
|
|
197
|
+
this.groupSelectionGraphics.clear();
|
|
198
|
+
this.groupSelectionGraphics.lineStyle(1, 0x3B82F6, 1);
|
|
199
|
+
// Нарисуем пока осевую рамку, вращение применим в update
|
|
200
|
+
this.groupSelectionGraphics.drawRect(gb.x, gb.y, gb.width, gb.height);
|
|
201
|
+
}
|
|
202
|
+
const ids = this.selection.toArray();
|
|
203
|
+
this.emit('group:rotate:start', { objects: ids, center: this.rotateCenter });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.isRotating = true;
|
|
208
|
+
this.dragTarget = objectId; // Используем dragTarget для совместимости
|
|
209
|
+
const posData = { objectId, position: null };
|
|
210
|
+
this.emit('get:object:position', posData);
|
|
211
|
+
const sizeData = { objectId, size: null };
|
|
212
|
+
this.emit('get:object:size', sizeData);
|
|
213
|
+
if (posData.position && sizeData.size && this._rotateCtrl) {
|
|
214
|
+
const center = { x: posData.position.x + sizeData.size.width / 2, y: posData.position.y + sizeData.size.height / 2 };
|
|
215
|
+
const w = this._toWorld(this.currentX, this.currentY);
|
|
216
|
+
this._rotateCtrl.start(objectId, { x: w.x, y: w.y }, center);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function updateRotate(event) {
|
|
221
|
+
// Групповой поворот
|
|
222
|
+
if (this.isGroupRotating && this._groupRotateCtrl) {
|
|
223
|
+
const w = this._toWorld(event.x, event.y);
|
|
224
|
+
this._groupRotateCtrl.update({ ...event, x: w.x, y: w.y });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!this.isRotating || !this._rotateCtrl) return;
|
|
228
|
+
{
|
|
229
|
+
const w = this._toWorld(event.x, event.y);
|
|
230
|
+
this._rotateCtrl.update({ ...event, x: w.x, y: w.y });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Обновляем ручки в реальном времени во время поворота
|
|
234
|
+
// HTML-ручки обновляются слоем HtmlHandlesLayer
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function endRotate() {
|
|
238
|
+
if (this.isGroupRotating) {
|
|
239
|
+
if (this._groupRotateCtrl) this._groupRotateCtrl.end();
|
|
240
|
+
this.isGroupRotating = false;
|
|
241
|
+
// Восстановление рамки
|
|
242
|
+
const gb = this.computeGroupBounds();
|
|
243
|
+
this.ensureGroupBoundsGraphics(gb);
|
|
244
|
+
if (this.groupBoundsGraphics) {
|
|
245
|
+
this.groupBoundsGraphics.rotation = 0;
|
|
246
|
+
this.groupBoundsGraphics.pivot.set(0, 0);
|
|
247
|
+
this.groupBoundsGraphics.position.set(gb.x, gb.y);
|
|
248
|
+
}
|
|
249
|
+
if (this.resizeHandles) this.resizeHandles.hideHandles();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (this._rotateCtrl) this._rotateCtrl.end();
|
|
253
|
+
|
|
254
|
+
// Обновляем позицию ручек после поворота
|
|
255
|
+
if (this.resizeHandles) {
|
|
256
|
+
this.resizeHandles.updateHandles(); // Обновляем позицию ручек
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.isRotating = false;
|
|
260
|
+
this.rotateCenter = null;
|
|
261
|
+
this.rotateStartAngle = 0;
|
|
262
|
+
this.rotateCurrentAngle = 0;
|
|
263
|
+
this.rotateStartMouseAngle = 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function startBoxSelect(event) {
|
|
267
|
+
this.isBoxSelect = true;
|
|
268
|
+
if (this._boxSelect) this._boxSelect.start({ x: event.x, y: event.y }, this.isMultiSelect);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function updateBoxSelect(event) {
|
|
272
|
+
if (this._boxSelect) this._boxSelect.update({ x: event.x, y: event.y });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function endBoxSelect() {
|
|
276
|
+
this.isBoxSelect = false;
|
|
277
|
+
if (this._boxSelect) this._boxSelect.end();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function startGroupDrag(event) {
|
|
281
|
+
const gb = this.computeGroupBounds();
|
|
282
|
+
this.groupStartBounds = gb;
|
|
283
|
+
this.isGroupDragging = true;
|
|
284
|
+
this.isDragging = false; // отключаем одиночный drag, если был
|
|
285
|
+
this.ensureGroupBoundsGraphics(gb);
|
|
286
|
+
if (this.groupBoundsGraphics && this.resizeHandles) {
|
|
287
|
+
this.resizeHandles.hideHandles();
|
|
288
|
+
}
|
|
289
|
+
if (this._groupDragCtrl) {
|
|
290
|
+
const w = this._toWorld(event.x, event.y);
|
|
291
|
+
this._groupDragCtrl.start(gb, { x: w.x, y: w.y });
|
|
292
|
+
}
|
|
293
|
+
this.emit(Events.Tool.GroupDragStart, { objects: this.selection.toArray() });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function prepareAltCloneDrag(objectId, event) {
|
|
297
|
+
// Очищаем текущее выделение и выделяем исходный объект
|
|
298
|
+
this.clearSelection();
|
|
299
|
+
this.addToSelection(objectId);
|
|
300
|
+
|
|
301
|
+
// Включаем режим Alt-клона и запрашиваем дубликат у ядра
|
|
302
|
+
this.isAltCloneMode = true;
|
|
303
|
+
this.clonePending = true;
|
|
304
|
+
this.cloneSourceId = objectId;
|
|
305
|
+
|
|
306
|
+
// Сохраняем текущее положение курсора
|
|
307
|
+
this.currentX = event.x;
|
|
308
|
+
this.currentY = event.y;
|
|
309
|
+
|
|
310
|
+
// Запрашиваем текущую позицию исходного объекта
|
|
311
|
+
const positionData = { objectId, position: null };
|
|
312
|
+
this.emit('get:object:position', positionData);
|
|
313
|
+
|
|
314
|
+
// Сообщаем ядру о необходимости создать дубликат у позиции исходного объекта
|
|
315
|
+
this.emit('duplicate:request', {
|
|
316
|
+
originalId: objectId,
|
|
317
|
+
position: positionData.position || { x: event.x, y: event.y }
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Помечаем, что находимся в состоянии drag, но цели пока нет — ждём newId
|
|
321
|
+
this.isDragging = true;
|
|
322
|
+
this.dragTarget = null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function transformHandleType(handleType, rotationDegrees) {
|
|
326
|
+
// Нормализуем угол поворота к диапазону 0-360
|
|
327
|
+
let angle = rotationDegrees % 360;
|
|
328
|
+
if (angle < 0) angle += 360;
|
|
329
|
+
|
|
330
|
+
// Определяем количество поворотов на 90 градусов
|
|
331
|
+
const rotations = Math.round(angle / 90) % 4;
|
|
332
|
+
|
|
333
|
+
if (rotations === 0) return handleType; // Нет поворота
|
|
334
|
+
|
|
335
|
+
// Карта преобразований для каждого поворота на 90°
|
|
336
|
+
const transformMap = {
|
|
337
|
+
'nw': ['ne', 'se', 'sw', 'nw'], // nw -> ne -> se -> sw -> nw
|
|
338
|
+
'n': ['e', 's', 'w', 'n'], // n -> e -> s -> w -> n
|
|
339
|
+
'ne': ['se', 'sw', 'nw', 'ne'], // ne -> se -> sw -> nw -> ne
|
|
340
|
+
'e': ['s', 'w', 'n', 'e'], // e -> s -> w -> n -> e
|
|
341
|
+
'se': ['sw', 'nw', 'ne', 'se'], // se -> sw -> nw -> ne -> se
|
|
342
|
+
's': ['w', 'n', 'e', 's'], // s -> w -> n -> e -> s
|
|
343
|
+
'sw': ['nw', 'ne', 'se', 'sw'], // sw -> nw -> ne -> se -> sw
|
|
344
|
+
'w': ['n', 'e', 's', 'w'] // w -> n -> e -> s -> w
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
return transformMap[handleType] ? transformMap[handleType][rotations - 1] : handleType;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function calculateNewSize(handleType, startBounds, deltaX, deltaY, maintainAspectRatio) {
|
|
351
|
+
let newWidth = startBounds.width;
|
|
352
|
+
let newHeight = startBounds.height;
|
|
353
|
+
|
|
354
|
+
// Получаем угол поворота объекта
|
|
355
|
+
const rotationData = { objectId: this.dragTarget, rotation: 0 };
|
|
356
|
+
this.emit('get:object:rotation', rotationData);
|
|
357
|
+
const objectRotation = rotationData.rotation || 0;
|
|
358
|
+
|
|
359
|
+
// Преобразуем тип ручки с учетом поворота объекта
|
|
360
|
+
const transformedHandleType = this.transformHandleType(handleType, objectRotation);
|
|
361
|
+
|
|
362
|
+
// Вычисляем изменения в зависимости от преобразованного типа ручки
|
|
363
|
+
switch (transformedHandleType) {
|
|
364
|
+
case 'nw': // Северо-запад - левый верхний угол
|
|
365
|
+
newWidth = startBounds.width - deltaX; // влево = меньше ширина
|
|
366
|
+
newHeight = startBounds.height - deltaY; // вверх = меньше высота
|
|
367
|
+
break;
|
|
368
|
+
case 'n': // Север - верхняя сторона
|
|
369
|
+
newHeight = startBounds.height - deltaY; // вверх = меньше высота
|
|
370
|
+
break;
|
|
371
|
+
case 'ne': // Северо-восток - правый верхний угол
|
|
372
|
+
newWidth = startBounds.width + deltaX; // вправо = больше ширина
|
|
373
|
+
newHeight = startBounds.height - deltaY; // вверх = меньше высота
|
|
374
|
+
break;
|
|
375
|
+
case 'e': // Восток - правая сторона
|
|
376
|
+
newWidth = startBounds.width + deltaX; // вправо = больше ширина
|
|
377
|
+
break;
|
|
378
|
+
case 'se': // Юго-восток - правый нижний угол
|
|
379
|
+
newWidth = startBounds.width + deltaX; // вправо = больше ширина
|
|
380
|
+
newHeight = startBounds.height + deltaY; // вниз = больше высота
|
|
381
|
+
break;
|
|
382
|
+
case 's': // Юг - нижняя сторона
|
|
383
|
+
newHeight = startBounds.height + deltaY; // вниз = больше высота
|
|
384
|
+
break;
|
|
385
|
+
case 'sw': // Юго-запад - левый нижний угол
|
|
386
|
+
newWidth = startBounds.width - deltaX; // влево = меньше ширина
|
|
387
|
+
newHeight = startBounds.height + deltaY; // вниз = больше высота
|
|
388
|
+
break;
|
|
389
|
+
case 'w': // Запад - левая сторона
|
|
390
|
+
newWidth = startBounds.width - deltaX; // влево = меньше ширина
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Поддержка пропорционального изменения размера (Shift)
|
|
395
|
+
if (maintainAspectRatio) {
|
|
396
|
+
const aspectRatio = startBounds.width / startBounds.height;
|
|
397
|
+
|
|
398
|
+
// Определяем, какую сторону использовать как основную
|
|
399
|
+
if (['nw', 'ne', 'sw', 'se'].includes(handleType)) {
|
|
400
|
+
// Угловые ручки - используем большее изменение
|
|
401
|
+
const widthChange = Math.abs(newWidth - startBounds.width);
|
|
402
|
+
const heightChange = Math.abs(newHeight - startBounds.height);
|
|
403
|
+
|
|
404
|
+
if (widthChange > heightChange) {
|
|
405
|
+
newHeight = newWidth / aspectRatio;
|
|
406
|
+
} else {
|
|
407
|
+
newWidth = newHeight * aspectRatio;
|
|
408
|
+
}
|
|
409
|
+
} else if (['e', 'w'].includes(handleType)) {
|
|
410
|
+
// Горизонтальные ручки
|
|
411
|
+
newHeight = newWidth / aspectRatio;
|
|
412
|
+
} else if (['n', 's'].includes(handleType)) {
|
|
413
|
+
// Вертикальные ручки
|
|
414
|
+
newWidth = newHeight * aspectRatio;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
width: Math.round(newWidth),
|
|
420
|
+
height: Math.round(newHeight)
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function calculatePositionOffset(handleType, startBounds, newSize) {
|
|
425
|
+
// Позиция в состоянии — левый верх. Для правых/нижних ручек топ-лев остается на месте.
|
|
426
|
+
// Для левых/верхних ручек топ-лев должен смещаться на полную величину изменения размера.
|
|
427
|
+
// deltaWidth/deltaHeight = изменение размера (может быть отрицательным при уменьшении)
|
|
428
|
+
|
|
429
|
+
const deltaWidth = newSize.width - startBounds.width;
|
|
430
|
+
const deltaHeight = newSize.height - startBounds.height;
|
|
431
|
+
|
|
432
|
+
let offsetX = 0;
|
|
433
|
+
let offsetY = 0;
|
|
434
|
+
|
|
435
|
+
switch (handleType) {
|
|
436
|
+
case 'nw':
|
|
437
|
+
offsetX = -deltaWidth; // левый край смещается на полную величину изменения ширины
|
|
438
|
+
offsetY = -deltaHeight; // верхний край смещается на полную величину изменения высоты
|
|
439
|
+
break;
|
|
440
|
+
case 'n':
|
|
441
|
+
offsetY = -deltaHeight; // только верхний край смещается
|
|
442
|
+
break;
|
|
443
|
+
case 'ne':
|
|
444
|
+
offsetY = -deltaHeight; // верх смещается, правый край — нет
|
|
445
|
+
break;
|
|
446
|
+
case 'e':
|
|
447
|
+
// правый край — левый верх не смещается
|
|
448
|
+
break;
|
|
449
|
+
case 'se':
|
|
450
|
+
// правый нижний — левый верх не смещается
|
|
451
|
+
break;
|
|
452
|
+
case 's':
|
|
453
|
+
// нижний — левый верх не смещается
|
|
454
|
+
break;
|
|
455
|
+
case 'sw':
|
|
456
|
+
offsetX = -deltaWidth; // левый край смещается, низ — нет
|
|
457
|
+
break;
|
|
458
|
+
case 'w':
|
|
459
|
+
offsetX = -deltaWidth; // левый край смещается на полную величину
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Для поворота корректное смещение требует преобразования в локальные координаты объекта
|
|
464
|
+
// и обратно. В данной итерации оставляем смещение в мировых осях для устойчивости без вращения.
|
|
465
|
+
return { x: offsetX, y: offsetY };
|
|
466
|
+
}
|
|
@@ -17,42 +17,51 @@ export class FilePropertiesPanel {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
_attachEvents() {
|
|
20
|
-
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
// Скрываем панель при удалении объекта
|
|
26
|
-
this.eventBus.on(Events.Object.Deleted, (data) => {
|
|
20
|
+
this._handlers = {};
|
|
21
|
+
this._handlers.onSelectionAdd = () => this.updateFromSelection();
|
|
22
|
+
this._handlers.onSelectionRemove = () => this.updateFromSelection();
|
|
23
|
+
this._handlers.onSelectionClear = () => this.hide();
|
|
24
|
+
this._handlers.onDeleted = (data) => {
|
|
27
25
|
const objectId = data?.objectId || data;
|
|
28
26
|
if (this.currentId && objectId === this.currentId) this.hide();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
39
|
-
this.eventBus.on(Events.Tool.RotateUpdate, () => this.reposition());
|
|
40
|
-
|
|
41
|
-
// Обновляем позицию при зуме/пане
|
|
42
|
-
this.eventBus.on(Events.UI.ZoomPercent, () => {
|
|
27
|
+
};
|
|
28
|
+
this._handlers.onDragStart = () => this.hide();
|
|
29
|
+
this._handlers.onDragUpdate = () => this.reposition();
|
|
30
|
+
this._handlers.onDragEnd = () => this.updateFromSelection();
|
|
31
|
+
this._handlers.onGroupDragUpdate = () => this.reposition();
|
|
32
|
+
this._handlers.onGroupDragStart = () => this.hide();
|
|
33
|
+
this._handlers.onGroupDragEnd = () => this.updateFromSelection();
|
|
34
|
+
this._handlers.onResizeUpdate = () => this.reposition();
|
|
35
|
+
this._handlers.onRotateUpdate = () => this.reposition();
|
|
36
|
+
this._handlers.onZoomPercent = () => {
|
|
43
37
|
if (this.currentId) this.reposition();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
this.eventBus.on(Events.Tool.PanUpdate, () => {
|
|
38
|
+
};
|
|
39
|
+
this._handlers.onPanUpdate = () => {
|
|
47
40
|
if (this.currentId) this.reposition();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
};
|
|
42
|
+
this._handlers.onActivated = ({ tool }) => {
|
|
43
|
+
if (tool !== 'select') this.hide();
|
|
44
|
+
};
|
|
45
|
+
this._handlers.onTransformUpdated = (data) => {
|
|
46
|
+
if (this.currentId && data?.objectId === this.currentId) this.reposition();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
this.eventBus.on(Events.Tool.SelectionAdd, this._handlers.onSelectionAdd);
|
|
50
|
+
this.eventBus.on(Events.Tool.SelectionRemove, this._handlers.onSelectionRemove);
|
|
51
|
+
this.eventBus.on(Events.Tool.SelectionClear, this._handlers.onSelectionClear);
|
|
52
|
+
this.eventBus.on(Events.Object.Deleted, this._handlers.onDeleted);
|
|
53
|
+
this.eventBus.on(Events.Tool.DragStart, this._handlers.onDragStart);
|
|
54
|
+
this.eventBus.on(Events.Tool.DragUpdate, this._handlers.onDragUpdate);
|
|
55
|
+
this.eventBus.on(Events.Tool.DragEnd, this._handlers.onDragEnd);
|
|
56
|
+
this.eventBus.on(Events.Tool.GroupDragUpdate, this._handlers.onGroupDragUpdate);
|
|
57
|
+
this.eventBus.on(Events.Tool.GroupDragStart, this._handlers.onGroupDragStart);
|
|
58
|
+
this.eventBus.on(Events.Tool.GroupDragEnd, this._handlers.onGroupDragEnd);
|
|
59
|
+
this.eventBus.on(Events.Tool.ResizeUpdate, this._handlers.onResizeUpdate);
|
|
60
|
+
this.eventBus.on(Events.Tool.RotateUpdate, this._handlers.onRotateUpdate);
|
|
61
|
+
this.eventBus.on(Events.UI.ZoomPercent, this._handlers.onZoomPercent);
|
|
62
|
+
this.eventBus.on(Events.Tool.PanUpdate, this._handlers.onPanUpdate);
|
|
63
|
+
this.eventBus.on(Events.Tool.Activated, this._handlers.onActivated);
|
|
64
|
+
this.eventBus.on(Events.Object.TransformUpdated, this._handlers.onTransformUpdated);
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
updateFromSelection() {
|
|
@@ -317,6 +326,26 @@ export class FilePropertiesPanel {
|
|
|
317
326
|
}
|
|
318
327
|
|
|
319
328
|
destroy() {
|
|
329
|
+
if (!this.eventBus || !this._handlers) return;
|
|
330
|
+
|
|
331
|
+
this.eventBus.off(Events.Tool.SelectionAdd, this._handlers.onSelectionAdd);
|
|
332
|
+
this.eventBus.off(Events.Tool.SelectionRemove, this._handlers.onSelectionRemove);
|
|
333
|
+
this.eventBus.off(Events.Tool.SelectionClear, this._handlers.onSelectionClear);
|
|
334
|
+
this.eventBus.off(Events.Object.Deleted, this._handlers.onDeleted);
|
|
335
|
+
this.eventBus.off(Events.Tool.DragStart, this._handlers.onDragStart);
|
|
336
|
+
this.eventBus.off(Events.Tool.DragUpdate, this._handlers.onDragUpdate);
|
|
337
|
+
this.eventBus.off(Events.Tool.DragEnd, this._handlers.onDragEnd);
|
|
338
|
+
this.eventBus.off(Events.Tool.GroupDragUpdate, this._handlers.onGroupDragUpdate);
|
|
339
|
+
this.eventBus.off(Events.Tool.GroupDragStart, this._handlers.onGroupDragStart);
|
|
340
|
+
this.eventBus.off(Events.Tool.GroupDragEnd, this._handlers.onGroupDragEnd);
|
|
341
|
+
this.eventBus.off(Events.Tool.ResizeUpdate, this._handlers.onResizeUpdate);
|
|
342
|
+
this.eventBus.off(Events.Tool.RotateUpdate, this._handlers.onRotateUpdate);
|
|
343
|
+
this.eventBus.off(Events.UI.ZoomPercent, this._handlers.onZoomPercent);
|
|
344
|
+
this.eventBus.off(Events.Tool.PanUpdate, this._handlers.onPanUpdate);
|
|
345
|
+
this.eventBus.off(Events.Tool.Activated, this._handlers.onActivated);
|
|
346
|
+
this.eventBus.off(Events.Object.TransformUpdated, this._handlers.onTransformUpdated);
|
|
347
|
+
this._handlers = null;
|
|
348
|
+
|
|
320
349
|
if (this.panel && this.panel.parentNode) {
|
|
321
350
|
this.panel.parentNode.removeChild(this.panel);
|
|
322
351
|
}
|