@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
package/src/ui/Topbar.js
CHANGED
|
@@ -40,15 +40,15 @@ export class Topbar {
|
|
|
40
40
|
// не подсвечиваем дефолт до прихода актуального типа из ядра
|
|
41
41
|
|
|
42
42
|
// Синхронизация активного состояния по событию из ядра
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
});
|
|
43
|
+
this._onGridCurrent = ({ type }) => this.setActive(type);
|
|
44
|
+
this.eventBus.on(Events.UI.GridCurrent, this._onGridCurrent);
|
|
46
45
|
|
|
47
46
|
// Обновляем цвет кнопки "краски" при выборе цвета фона
|
|
48
|
-
this.
|
|
47
|
+
this._onPaintPick = ({ btnHex }) => {
|
|
49
48
|
if (!btnHex) return;
|
|
50
49
|
this.setPaintButtonHex(btnHex);
|
|
51
|
-
}
|
|
50
|
+
};
|
|
51
|
+
this.eventBus.on(Events.UI.PaintPick, this._onPaintPick);
|
|
52
52
|
|
|
53
53
|
// Инициализация цвета кнопки "краска" из настроек (или фона рендерера)
|
|
54
54
|
try {
|
|
@@ -254,18 +254,6 @@ export class Topbar {
|
|
|
254
254
|
const colors = this._palette.map(c => ({ id: c.id, name: c.name, hex: c.btnHex, board: c.board }));
|
|
255
255
|
const grid = document.createElement('div');
|
|
256
256
|
grid.className = 'moodboard-topbar__paint-grid';
|
|
257
|
-
const darken = (hex, amount = 0.25) => {
|
|
258
|
-
try {
|
|
259
|
-
const h = hex.replace('#','');
|
|
260
|
-
const r = parseInt(h.substring(0,2), 16);
|
|
261
|
-
const g = parseInt(h.substring(2,4), 16);
|
|
262
|
-
const b = parseInt(h.substring(4,6), 16);
|
|
263
|
-
const dr = Math.max(0, Math.min(255, Math.round(r * (1 - amount))));
|
|
264
|
-
const dg = Math.max(0, Math.min(255, Math.round(g * (1 - amount))));
|
|
265
|
-
const db = Math.max(0, Math.min(255, Math.round(b * (1 - amount))));
|
|
266
|
-
return `#${dr.toString(16).padStart(2,'0')}${dg.toString(16).padStart(2,'0')}${db.toString(16).padStart(2,'0')}`;
|
|
267
|
-
} catch (_) { return hex; }
|
|
268
|
-
};
|
|
269
257
|
|
|
270
258
|
colors.forEach(c => {
|
|
271
259
|
const b = document.createElement('button');
|
|
@@ -274,7 +262,7 @@ export class Topbar {
|
|
|
274
262
|
b.title = `Цвет ${c.id}`;
|
|
275
263
|
b.style.background = c.hex;
|
|
276
264
|
// Тёмная обводка того же цвета
|
|
277
|
-
b.style.borderColor =
|
|
265
|
+
b.style.borderColor = this._darkenHex(c.hex, 0.35);
|
|
278
266
|
// Цвет галочки — чёрный для максимальной видимости
|
|
279
267
|
b.style.color = '#111';
|
|
280
268
|
// Для надёжного сравнения — сохраняем оба значения в dataset
|
|
@@ -326,22 +314,32 @@ export class Topbar {
|
|
|
326
314
|
this.element.appendChild(pop);
|
|
327
315
|
this._paintPopover = pop;
|
|
328
316
|
|
|
329
|
-
// закрытие по клику вне
|
|
330
|
-
|
|
317
|
+
// закрытие по клику вне (сохраняем ссылку для cleanup при destroy)
|
|
318
|
+
this._onDocClickOutside = (ev) => {
|
|
331
319
|
if (!pop.contains(ev.target) && ev.target !== anchorBtn) {
|
|
332
320
|
pop.remove();
|
|
333
321
|
this._paintPopover = null;
|
|
334
|
-
document.removeEventListener('click',
|
|
322
|
+
document.removeEventListener('click', this._onDocClickOutside, true);
|
|
335
323
|
}
|
|
336
324
|
};
|
|
337
|
-
setTimeout(() => document.addEventListener('click',
|
|
325
|
+
setTimeout(() => document.addEventListener('click', this._onDocClickOutside, true), 0);
|
|
338
326
|
}
|
|
339
327
|
|
|
340
328
|
destroy() {
|
|
341
|
-
if (this.element)
|
|
342
|
-
|
|
343
|
-
|
|
329
|
+
if (!this.element) return;
|
|
330
|
+
|
|
331
|
+
if (this._paintPopover) {
|
|
332
|
+
this._paintPopover.remove();
|
|
333
|
+
this._paintPopover = null;
|
|
334
|
+
document.removeEventListener('click', this._onDocClickOutside, true);
|
|
344
335
|
}
|
|
336
|
+
|
|
337
|
+
if (this._onGridCurrent) this.eventBus.off(Events.UI.GridCurrent, this._onGridCurrent);
|
|
338
|
+
if (this._onPaintPick) this.eventBus.off(Events.UI.PaintPick, this._onPaintPick);
|
|
339
|
+
|
|
340
|
+
this.element.remove();
|
|
341
|
+
this.element = null;
|
|
342
|
+
this._paintBtn = null;
|
|
345
343
|
}
|
|
346
344
|
}
|
|
347
345
|
|
package/src/ui/ZoomPanel.js
CHANGED
|
@@ -72,17 +72,18 @@ export class ZoomPanel {
|
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
this._onDocMouseDown = (e) => {
|
|
76
76
|
if (!this.menuEl) return;
|
|
77
|
-
// ИСПРАВЛЕНИЕ: Защита от null элементов
|
|
78
77
|
if (!this.element || !e.target) return;
|
|
79
78
|
if (this.element.contains(e.target)) return;
|
|
80
79
|
this.hideMenu();
|
|
81
|
-
}
|
|
80
|
+
};
|
|
81
|
+
document.addEventListener('mousedown', this._onDocMouseDown);
|
|
82
82
|
|
|
83
|
-
this.
|
|
83
|
+
this._onZoomPercent = ({ percentage }) => {
|
|
84
84
|
if (this.valueEl) this.valueEl.textContent = `${percentage}%`;
|
|
85
|
-
}
|
|
85
|
+
};
|
|
86
|
+
this.eventBus.on(Events.UI.ZoomPercent, this._onZoomPercent);
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
showMenu() {
|
|
@@ -115,9 +116,19 @@ export class ZoomPanel {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
destroy() {
|
|
119
|
+
if (this._onDocMouseDown) {
|
|
120
|
+
document.removeEventListener('mousedown', this._onDocMouseDown);
|
|
121
|
+
this._onDocMouseDown = null;
|
|
122
|
+
}
|
|
123
|
+
if (this._onZoomPercent) {
|
|
124
|
+
this.eventBus.off(Events.UI.ZoomPercent, this._onZoomPercent);
|
|
125
|
+
this._onZoomPercent = null;
|
|
126
|
+
}
|
|
127
|
+
this.hideMenu();
|
|
118
128
|
if (this.element) this.element.remove();
|
|
119
129
|
this.element = null;
|
|
120
130
|
this.labelEl = null;
|
|
131
|
+
this.valueEl = null;
|
|
121
132
|
}
|
|
122
133
|
}
|
|
123
134
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class GroupSelectionHandlesController {
|
|
2
|
+
constructor(host) {
|
|
3
|
+
this.host = host;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
renderForSelection(ids) {
|
|
7
|
+
const preview = this.host._groupRotationPreview;
|
|
8
|
+
if (preview && Array.isArray(preview.ids) && preview.ids.length === ids.length) {
|
|
9
|
+
const hasSameSelection = ids.every((id) => preview.ids.includes(id));
|
|
10
|
+
if (hasSameSelection && preview.startBounds) {
|
|
11
|
+
this.host._showBounds({
|
|
12
|
+
x: preview.center.x - preview.startBounds.width / 2,
|
|
13
|
+
y: preview.center.y - preview.startBounds.height / 2,
|
|
14
|
+
width: preview.startBounds.width,
|
|
15
|
+
height: preview.startBounds.height,
|
|
16
|
+
}, '__group__', {
|
|
17
|
+
rotation: preview.angle || 0,
|
|
18
|
+
});
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const worldBounds = this.host.positioningService.getGroupSelectionWorldBounds(ids);
|
|
23
|
+
if (!worldBounds) {
|
|
24
|
+
this.host.hide();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
this.host._showBounds(worldBounds, '__group__');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { Events } from '../../core/events/Events.js';
|
|
2
|
+
import { createRotatedResizeCursor } from '../../tools/object-tools/selection/CursorController.js';
|
|
3
|
+
|
|
4
|
+
const HANDLES_ACCENT_COLOR = '#80D8FF';
|
|
5
|
+
|
|
6
|
+
export class HandlesDomRenderer {
|
|
7
|
+
constructor(host, rotateIconSvg) {
|
|
8
|
+
this.host = host;
|
|
9
|
+
this.rotateIconSvg = rotateIconSvg;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
setHandlesVisibility(show) {
|
|
13
|
+
if (!this.host.layer) return;
|
|
14
|
+
const box = this.host.layer.querySelector('.mb-handles-box');
|
|
15
|
+
if (!box) return;
|
|
16
|
+
|
|
17
|
+
box.querySelectorAll('[data-dir]').forEach((el) => {
|
|
18
|
+
el.style.display = show ? '' : 'none';
|
|
19
|
+
});
|
|
20
|
+
box.querySelectorAll('[data-edge]').forEach((el) => {
|
|
21
|
+
el.style.display = show ? '' : 'none';
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const rot = box.querySelector('[data-handle="rotate"]');
|
|
25
|
+
if (rot) rot.style.display = show ? '' : 'none';
|
|
26
|
+
if (show && !box.querySelector('[data-dir]')) {
|
|
27
|
+
this.host.update();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
showBounds(worldBounds, id, options = {}) {
|
|
32
|
+
if (!this.host.layer) return;
|
|
33
|
+
|
|
34
|
+
const cssRect = this.host.positioningService.worldBoundsToCssRect(worldBounds);
|
|
35
|
+
|
|
36
|
+
let isFileTarget = false;
|
|
37
|
+
let isFrameTarget = false;
|
|
38
|
+
if (id !== '__group__') {
|
|
39
|
+
const req = { objectId: id, pixiObject: null };
|
|
40
|
+
this.host.eventBus.emit(Events.Tool.GetObjectPixi, req);
|
|
41
|
+
const mbType = req.pixiObject && req.pixiObject._mb && req.pixiObject._mb.type;
|
|
42
|
+
isFileTarget = mbType === 'file';
|
|
43
|
+
isFrameTarget = mbType === 'frame';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const left = cssRect.left;
|
|
47
|
+
const top = cssRect.top;
|
|
48
|
+
const width = cssRect.width;
|
|
49
|
+
const height = cssRect.height;
|
|
50
|
+
|
|
51
|
+
this.host.layer.innerHTML = '';
|
|
52
|
+
const box = document.createElement('div');
|
|
53
|
+
box.className = 'mb-handles-box';
|
|
54
|
+
|
|
55
|
+
let rotation = options.rotation ?? 0;
|
|
56
|
+
if (id !== '__group__') {
|
|
57
|
+
const rotationData = { objectId: id, rotation: 0 };
|
|
58
|
+
this.host.eventBus.emit(Events.Tool.GetObjectRotation, rotationData);
|
|
59
|
+
rotation = rotationData.rotation || 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Object.assign(box.style, {
|
|
63
|
+
position: 'absolute', left: `${left}px`, top: `${top}px`,
|
|
64
|
+
width: `${width}px`, height: `${height}px`,
|
|
65
|
+
outline: `2px solid ${HANDLES_ACCENT_COLOR}`, outlineOffset: '0', borderRadius: '3px', boxSizing: 'border-box', pointerEvents: 'none',
|
|
66
|
+
transformOrigin: 'center center',
|
|
67
|
+
transform: `rotate(${rotation}deg)`,
|
|
68
|
+
});
|
|
69
|
+
this.host.layer.appendChild(box);
|
|
70
|
+
if (this.host._handlesSuppressed) {
|
|
71
|
+
this.host.visible = true;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const mkCorner = (dir, x, y) => {
|
|
76
|
+
const cursor = createRotatedResizeCursor(dir, rotation);
|
|
77
|
+
const h = document.createElement('div');
|
|
78
|
+
h.dataset.dir = dir;
|
|
79
|
+
h.dataset.id = id;
|
|
80
|
+
h.className = 'mb-handle';
|
|
81
|
+
h.style.pointerEvents = isFileTarget ? 'none' : 'auto';
|
|
82
|
+
h.style.cursor = cursor;
|
|
83
|
+
h.style.left = `${x - 6}px`;
|
|
84
|
+
h.style.top = `${y - 6}px`;
|
|
85
|
+
h.style.display = isFileTarget ? 'none' : 'block';
|
|
86
|
+
|
|
87
|
+
const inner = document.createElement('div');
|
|
88
|
+
inner.className = 'mb-handle-inner';
|
|
89
|
+
h.appendChild(inner);
|
|
90
|
+
|
|
91
|
+
h.addEventListener('mouseenter', () => {
|
|
92
|
+
h.style.background = HANDLES_ACCENT_COLOR;
|
|
93
|
+
h.style.borderColor = HANDLES_ACCENT_COLOR;
|
|
94
|
+
h.style.cursor = cursor;
|
|
95
|
+
});
|
|
96
|
+
h.addEventListener('mouseleave', () => {
|
|
97
|
+
h.style.background = HANDLES_ACCENT_COLOR;
|
|
98
|
+
h.style.borderColor = HANDLES_ACCENT_COLOR;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!isFileTarget) {
|
|
102
|
+
h.addEventListener('mousedown', (e) => this.host._onHandleDown(e, box));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
box.appendChild(h);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const x0 = 0;
|
|
109
|
+
const y0 = 0;
|
|
110
|
+
const x1 = width;
|
|
111
|
+
const y1 = height;
|
|
112
|
+
mkCorner('nw', x0, y0);
|
|
113
|
+
mkCorner('ne', x1, y0);
|
|
114
|
+
mkCorner('se', x1, y1);
|
|
115
|
+
mkCorner('sw', x0, y1);
|
|
116
|
+
|
|
117
|
+
const edgeSize = 10;
|
|
118
|
+
const makeEdge = (name, style, cursorHandleType) => {
|
|
119
|
+
const cursor = createRotatedResizeCursor(cursorHandleType, rotation);
|
|
120
|
+
const e = document.createElement('div');
|
|
121
|
+
e.dataset.edge = name;
|
|
122
|
+
e.dataset.id = id;
|
|
123
|
+
e.className = 'mb-edge';
|
|
124
|
+
Object.assign(e.style, style, {
|
|
125
|
+
pointerEvents: isFileTarget ? 'none' : 'auto',
|
|
126
|
+
cursor,
|
|
127
|
+
display: isFileTarget ? 'none' : 'block',
|
|
128
|
+
});
|
|
129
|
+
if (!isFileTarget) {
|
|
130
|
+
e.addEventListener('mousedown', (evt) => this.host._onEdgeResizeDown(evt));
|
|
131
|
+
}
|
|
132
|
+
box.appendChild(e);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const cornerGap = 20;
|
|
136
|
+
makeEdge('top', {
|
|
137
|
+
left: `${cornerGap}px`,
|
|
138
|
+
top: `-${edgeSize / 2}px`,
|
|
139
|
+
width: `${Math.max(0, width - 2 * cornerGap)}px`,
|
|
140
|
+
height: `${edgeSize}px`,
|
|
141
|
+
}, 'n');
|
|
142
|
+
|
|
143
|
+
makeEdge('bottom', {
|
|
144
|
+
left: `${cornerGap}px`,
|
|
145
|
+
top: `${height - edgeSize / 2}px`,
|
|
146
|
+
width: `${Math.max(0, width - 2 * cornerGap)}px`,
|
|
147
|
+
height: `${edgeSize}px`,
|
|
148
|
+
}, 's');
|
|
149
|
+
|
|
150
|
+
makeEdge('left', {
|
|
151
|
+
left: `-${edgeSize / 2}px`,
|
|
152
|
+
top: `${cornerGap}px`,
|
|
153
|
+
width: `${edgeSize}px`,
|
|
154
|
+
height: `${Math.max(0, height - 2 * cornerGap)}px`,
|
|
155
|
+
}, 'w');
|
|
156
|
+
|
|
157
|
+
makeEdge('right', {
|
|
158
|
+
left: `${width - edgeSize / 2}px`,
|
|
159
|
+
top: `${cornerGap}px`,
|
|
160
|
+
width: `${edgeSize}px`,
|
|
161
|
+
height: `${Math.max(0, height - 2 * cornerGap)}px`,
|
|
162
|
+
}, 'e');
|
|
163
|
+
|
|
164
|
+
const rotateHandle = document.createElement('div');
|
|
165
|
+
rotateHandle.dataset.handle = 'rotate';
|
|
166
|
+
rotateHandle.dataset.id = id;
|
|
167
|
+
if (isFileTarget || isFrameTarget) {
|
|
168
|
+
Object.assign(rotateHandle.style, { display: 'none', pointerEvents: 'none' });
|
|
169
|
+
} else {
|
|
170
|
+
rotateHandle.className = 'mb-rotate-handle';
|
|
171
|
+
const d = 38;
|
|
172
|
+
const L = Math.max(1, Math.hypot(width, height));
|
|
173
|
+
const centerX = -(width / L) * d;
|
|
174
|
+
const centerY = height + (height / L) * d;
|
|
175
|
+
rotateHandle.style.left = `${Math.round(centerX)}px`;
|
|
176
|
+
rotateHandle.style.top = `${Math.round(centerY - 10)}px`;
|
|
177
|
+
rotateHandle.innerHTML = this.rotateIconSvg;
|
|
178
|
+
const svgEl = rotateHandle.querySelector('svg');
|
|
179
|
+
if (svgEl) {
|
|
180
|
+
svgEl.style.width = '100%';
|
|
181
|
+
svgEl.style.height = '100%';
|
|
182
|
+
svgEl.style.display = 'block';
|
|
183
|
+
}
|
|
184
|
+
rotateHandle.addEventListener('mousedown', (e) => this.host._onRotateHandleDown(e, box));
|
|
185
|
+
}
|
|
186
|
+
box.appendChild(rotateHandle);
|
|
187
|
+
|
|
188
|
+
this.host.visible = true;
|
|
189
|
+
this.host.target = { type: id === '__group__' ? 'group' : 'single', id, bounds: worldBounds };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
repositionBoxChildren(box) {
|
|
193
|
+
const width = parseFloat(box.style.width);
|
|
194
|
+
const height = parseFloat(box.style.height);
|
|
195
|
+
const cx = width / 2;
|
|
196
|
+
const cy = height / 2;
|
|
197
|
+
|
|
198
|
+
box.querySelectorAll('[data-dir]').forEach((h) => {
|
|
199
|
+
const dir = h.dataset.dir;
|
|
200
|
+
switch (dir) {
|
|
201
|
+
case 'nw':
|
|
202
|
+
h.style.left = `${-6}px`;
|
|
203
|
+
h.style.top = `${-6}px`;
|
|
204
|
+
break;
|
|
205
|
+
case 'ne':
|
|
206
|
+
h.style.left = `${Math.max(-6, width - 6)}px`;
|
|
207
|
+
h.style.top = `${-6}px`;
|
|
208
|
+
break;
|
|
209
|
+
case 'se':
|
|
210
|
+
h.style.left = `${Math.max(-6, width - 6)}px`;
|
|
211
|
+
h.style.top = `${Math.max(-6, height - 6)}px`;
|
|
212
|
+
break;
|
|
213
|
+
case 'sw':
|
|
214
|
+
h.style.left = `${-6}px`;
|
|
215
|
+
h.style.top = `${Math.max(-6, height - 6)}px`;
|
|
216
|
+
break;
|
|
217
|
+
case 'n':
|
|
218
|
+
h.style.left = `${cx - 6}px`;
|
|
219
|
+
h.style.top = `${-6}px`;
|
|
220
|
+
break;
|
|
221
|
+
case 'e':
|
|
222
|
+
h.style.left = `${Math.max(-6, width - 6)}px`;
|
|
223
|
+
h.style.top = `${cy - 6}px`;
|
|
224
|
+
break;
|
|
225
|
+
case 's':
|
|
226
|
+
h.style.left = `${cx - 6}px`;
|
|
227
|
+
h.style.top = `${Math.max(-6, height - 6)}px`;
|
|
228
|
+
break;
|
|
229
|
+
case 'w':
|
|
230
|
+
h.style.left = `${-6}px`;
|
|
231
|
+
h.style.top = `${cy - 6}px`;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const edgeSize = 10;
|
|
237
|
+
const cornerGap = 20;
|
|
238
|
+
const top = box.querySelector('[data-edge="top"]');
|
|
239
|
+
const bottom = box.querySelector('[data-edge="bottom"]');
|
|
240
|
+
const left = box.querySelector('[data-edge="left"]');
|
|
241
|
+
const right = box.querySelector('[data-edge="right"]');
|
|
242
|
+
|
|
243
|
+
if (top) Object.assign(top.style, {
|
|
244
|
+
left: `${cornerGap}px`,
|
|
245
|
+
top: `-${edgeSize / 2}px`,
|
|
246
|
+
width: `${Math.max(0, width - 2 * cornerGap)}px`,
|
|
247
|
+
height: `${edgeSize}px`,
|
|
248
|
+
});
|
|
249
|
+
if (bottom) Object.assign(bottom.style, {
|
|
250
|
+
left: `${cornerGap}px`,
|
|
251
|
+
top: `${height - edgeSize / 2}px`,
|
|
252
|
+
width: `${Math.max(0, width - 2 * cornerGap)}px`,
|
|
253
|
+
height: `${edgeSize}px`,
|
|
254
|
+
});
|
|
255
|
+
if (left) Object.assign(left.style, {
|
|
256
|
+
left: `-${edgeSize / 2}px`,
|
|
257
|
+
top: `${cornerGap}px`,
|
|
258
|
+
width: `${edgeSize}px`,
|
|
259
|
+
height: `${Math.max(0, height - 2 * cornerGap)}px`,
|
|
260
|
+
});
|
|
261
|
+
if (right) Object.assign(right.style, {
|
|
262
|
+
left: `${width - edgeSize / 2}px`,
|
|
263
|
+
top: `${cornerGap}px`,
|
|
264
|
+
width: `${edgeSize}px`,
|
|
265
|
+
height: `${Math.max(0, height - 2 * cornerGap)}px`,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const rotateHandle = box.querySelector('[data-handle="rotate"]');
|
|
269
|
+
if (rotateHandle) {
|
|
270
|
+
const d = 20;
|
|
271
|
+
const L = Math.max(1, Math.hypot(width, height));
|
|
272
|
+
const centerX = -(width / L) * d;
|
|
273
|
+
const centerY = height + (height / L) * d;
|
|
274
|
+
rotateHandle.style.left = `${Math.round(centerX - 10)}px`;
|
|
275
|
+
rotateHandle.style.top = `${Math.round(centerY - 10)}px`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Events } from '../../core/events/Events.js';
|
|
2
|
+
|
|
3
|
+
export class HandlesEventBridge {
|
|
4
|
+
constructor(host) {
|
|
5
|
+
this.host = host;
|
|
6
|
+
this.subscriptions = [];
|
|
7
|
+
this.isAttached = false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
attach() {
|
|
11
|
+
if (this.isAttached) return;
|
|
12
|
+
this.isAttached = true;
|
|
13
|
+
|
|
14
|
+
const bindings = [
|
|
15
|
+
[Events.Tool.SelectionAdd, () => this.host.update()],
|
|
16
|
+
[Events.Tool.SelectionRemove, () => this.host.update()],
|
|
17
|
+
[Events.Tool.SelectionClear, () => {
|
|
18
|
+
this.host._endGroupRotationPreview();
|
|
19
|
+
this.host.hide();
|
|
20
|
+
}],
|
|
21
|
+
[Events.Tool.DragUpdate, () => this.host.update()],
|
|
22
|
+
[Events.Object.Deleted, (data) => {
|
|
23
|
+
const objectId = data?.objectId || data;
|
|
24
|
+
console.log('🗑️ HtmlHandlesLayer: получено событие удаления:', data, 'objectId:', objectId);
|
|
25
|
+
this.host.hide();
|
|
26
|
+
this.host.layer.innerHTML = '';
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
this.host.update();
|
|
29
|
+
}, 10);
|
|
30
|
+
}],
|
|
31
|
+
[Events.Tool.DragStart, () => { this.host._handlesSuppressed = true; this.host._setHandlesVisibility(false); }],
|
|
32
|
+
[Events.Tool.DragEnd, () => { this.host._handlesSuppressed = false; this.host._setHandlesVisibility(true); }],
|
|
33
|
+
[Events.Tool.ResizeUpdate, () => this.host.update()],
|
|
34
|
+
[Events.Tool.ResizeStart, () => { this.host._handlesSuppressed = true; this.host._setHandlesVisibility(false); }],
|
|
35
|
+
[Events.Tool.ResizeEnd, () => { this.host._handlesSuppressed = false; this.host._setHandlesVisibility(true); }],
|
|
36
|
+
[Events.Tool.RotateUpdate, () => this.host.update()],
|
|
37
|
+
[Events.Tool.RotateStart, () => { this.host._handlesSuppressed = true; this.host._setHandlesVisibility(false); }],
|
|
38
|
+
[Events.Tool.RotateEnd, () => { this.host._handlesSuppressed = false; this.host._setHandlesVisibility(true); }],
|
|
39
|
+
[Events.Tool.GroupDragUpdate, () => {
|
|
40
|
+
this.host._syncGroupRotationPreviewTranslation();
|
|
41
|
+
this.host.update();
|
|
42
|
+
}],
|
|
43
|
+
[Events.Tool.GroupDragStart, () => { this.host._handlesSuppressed = true; this.host._setHandlesVisibility(false); }],
|
|
44
|
+
[Events.Tool.GroupDragEnd, () => { this.host._handlesSuppressed = false; this.host._setHandlesVisibility(true); }],
|
|
45
|
+
[Events.Tool.GroupResizeUpdate, (data) => {
|
|
46
|
+
this.host._updateGroupResizePreview(data);
|
|
47
|
+
this.host.update();
|
|
48
|
+
}],
|
|
49
|
+
[Events.Tool.GroupResizeStart, (data) => {
|
|
50
|
+
this.host._startGroupResizePreview(data);
|
|
51
|
+
this.host._handlesSuppressed = true;
|
|
52
|
+
this.host._setHandlesVisibility(false);
|
|
53
|
+
}],
|
|
54
|
+
[Events.Tool.GroupResizeEnd, () => {
|
|
55
|
+
this.host._finishGroupResizePreview();
|
|
56
|
+
this.host._handlesSuppressed = false;
|
|
57
|
+
this.host._setHandlesVisibility(true);
|
|
58
|
+
}],
|
|
59
|
+
[Events.Tool.GroupRotateUpdate, (data) => {
|
|
60
|
+
this.host._updateGroupRotationPreview(data);
|
|
61
|
+
this.host.update();
|
|
62
|
+
}],
|
|
63
|
+
[Events.Tool.GroupRotateStart, (data) => {
|
|
64
|
+
this.host._startGroupRotationPreview(data);
|
|
65
|
+
this.host._handlesSuppressed = true;
|
|
66
|
+
this.host._setHandlesVisibility(false);
|
|
67
|
+
}],
|
|
68
|
+
[Events.Tool.GroupRotateEnd, () => {
|
|
69
|
+
this.host._finishGroupRotationPreview();
|
|
70
|
+
this.host._handlesSuppressed = false;
|
|
71
|
+
this.host._setHandlesVisibility(true);
|
|
72
|
+
this.host.update();
|
|
73
|
+
}],
|
|
74
|
+
[Events.UI.ZoomPercent, () => this.host.update()],
|
|
75
|
+
[Events.Tool.PanUpdate, () => this.host.update()],
|
|
76
|
+
[Events.History.Changed, (data) => {
|
|
77
|
+
if (data?.lastUndone || data?.lastRedone) {
|
|
78
|
+
this.host._endGroupRotationPreview();
|
|
79
|
+
this.host.update();
|
|
80
|
+
}
|
|
81
|
+
}],
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
bindings.forEach(([event, handler]) => {
|
|
85
|
+
this.host.eventBus.on(event, handler);
|
|
86
|
+
this.subscriptions.push([event, handler]);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
detach() {
|
|
91
|
+
if (!this.isAttached) return;
|
|
92
|
+
this.isAttached = false;
|
|
93
|
+
if (typeof this.host.eventBus?.off !== 'function') {
|
|
94
|
+
this.subscriptions = [];
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.subscriptions.forEach(([event, handler]) => {
|
|
98
|
+
this.host.eventBus.off(event, handler);
|
|
99
|
+
});
|
|
100
|
+
this.subscriptions = [];
|
|
101
|
+
}
|
|
102
|
+
}
|