@sequent-org/moodboard 1.2.119 → 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 +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 +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
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import * as PIXI from 'pixi.js';
|
|
2
|
+
import { Events } from '../../core/events/Events.js';
|
|
3
|
+
|
|
4
|
+
export class HandlesPositioningService {
|
|
5
|
+
constructor(host) {
|
|
6
|
+
this.host = host;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
_rotatePoint(point, center, angleDegrees) {
|
|
10
|
+
const angleRad = angleDegrees * Math.PI / 180;
|
|
11
|
+
const cos = Math.cos(angleRad);
|
|
12
|
+
const sin = Math.sin(angleRad);
|
|
13
|
+
const dx = point.x - center.x;
|
|
14
|
+
const dy = point.y - center.y;
|
|
15
|
+
return {
|
|
16
|
+
x: center.x + dx * cos - dy * sin,
|
|
17
|
+
y: center.y + dx * sin + dy * cos,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_getWorldRectFromState(position, size, rotation = 0) {
|
|
22
|
+
if (!position || !size) return null;
|
|
23
|
+
const width = Math.max(1, size.width || 1);
|
|
24
|
+
const height = Math.max(1, size.height || 1);
|
|
25
|
+
if (Math.abs(rotation || 0) < 0.001) {
|
|
26
|
+
return {
|
|
27
|
+
x: position.x,
|
|
28
|
+
y: position.y,
|
|
29
|
+
width,
|
|
30
|
+
height,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const center = {
|
|
35
|
+
x: position.x + width / 2,
|
|
36
|
+
y: position.y + height / 2,
|
|
37
|
+
};
|
|
38
|
+
const corners = [
|
|
39
|
+
{ x: position.x, y: position.y },
|
|
40
|
+
{ x: position.x + width, y: position.y },
|
|
41
|
+
{ x: position.x + width, y: position.y + height },
|
|
42
|
+
{ x: position.x, y: position.y + height },
|
|
43
|
+
].map((point) => this._rotatePoint(point, center, rotation));
|
|
44
|
+
|
|
45
|
+
const xs = corners.map((point) => point.x);
|
|
46
|
+
const ys = corners.map((point) => point.y);
|
|
47
|
+
const minX = Math.min(...xs);
|
|
48
|
+
const maxX = Math.max(...xs);
|
|
49
|
+
const minY = Math.min(...ys);
|
|
50
|
+
const maxY = Math.max(...ys);
|
|
51
|
+
return {
|
|
52
|
+
x: minX,
|
|
53
|
+
y: minY,
|
|
54
|
+
width: Math.max(1, maxX - minX),
|
|
55
|
+
height: Math.max(1, maxY - minY),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
toWorldScreenInverse(dx, dy) {
|
|
60
|
+
const world = this.host.core.pixi.worldLayer || this.host.core.pixi.app.stage;
|
|
61
|
+
const s = world?.scale?.x || 1;
|
|
62
|
+
return { dxWorld: dx / s, dyWorld: dy / s };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getViewportOffsets() {
|
|
66
|
+
const containerRect = this.host.container.getBoundingClientRect();
|
|
67
|
+
const view = this.host.core.pixi.app.view;
|
|
68
|
+
const viewRect = view.getBoundingClientRect();
|
|
69
|
+
return {
|
|
70
|
+
offsetLeft: viewRect.left - containerRect.left,
|
|
71
|
+
offsetTop: viewRect.top - containerRect.top,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getWorldTransform() {
|
|
76
|
+
const world = this.host.core.pixi.worldLayer || this.host.core.pixi.app.stage;
|
|
77
|
+
const s = world?.scale?.x || 1;
|
|
78
|
+
const tx = world?.x || 0;
|
|
79
|
+
const ty = world?.y || 0;
|
|
80
|
+
const rendererRes = (this.host.core.pixi.app.renderer?.resolution) || 1;
|
|
81
|
+
return { world, s, tx, ty, rendererRes };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
worldBoundsToCssRect(worldBounds) {
|
|
85
|
+
const { world } = this.getWorldTransform();
|
|
86
|
+
const { offsetLeft, offsetTop } = this.getViewportOffsets();
|
|
87
|
+
const tl = world.toGlobal(new PIXI.Point(worldBounds.x, worldBounds.y));
|
|
88
|
+
const br = world.toGlobal(new PIXI.Point(worldBounds.x + worldBounds.width, worldBounds.y + worldBounds.height));
|
|
89
|
+
return {
|
|
90
|
+
left: offsetLeft + tl.x,
|
|
91
|
+
top: offsetTop + tl.y,
|
|
92
|
+
width: Math.max(1, br.x - tl.x),
|
|
93
|
+
height: Math.max(1, br.y - tl.y),
|
|
94
|
+
offsetLeft,
|
|
95
|
+
offsetTop,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
cssRectToWorldRect(cssRect, offsets = null) {
|
|
100
|
+
const { s, tx, ty, rendererRes } = this.getWorldTransform();
|
|
101
|
+
const { offsetLeft, offsetTop } = offsets || this.getViewportOffsets();
|
|
102
|
+
const screenX = cssRect.left - offsetLeft;
|
|
103
|
+
const screenY = cssRect.top - offsetTop;
|
|
104
|
+
return {
|
|
105
|
+
x: ((screenX * rendererRes) - tx) / s,
|
|
106
|
+
y: ((screenY * rendererRes) - ty) / s,
|
|
107
|
+
width: (cssRect.width * rendererRes) / s,
|
|
108
|
+
height: (cssRect.height * rendererRes) / s,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getSingleSelectionWorldBounds(id, pixi) {
|
|
113
|
+
const positionData = { objectId: id, position: null };
|
|
114
|
+
const sizeData = { objectId: id, size: null };
|
|
115
|
+
this.host.eventBus.emit(Events.Tool.GetObjectPosition, positionData);
|
|
116
|
+
this.host.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
|
|
117
|
+
|
|
118
|
+
if (positionData.position && sizeData.size) {
|
|
119
|
+
return {
|
|
120
|
+
x: positionData.position.x,
|
|
121
|
+
y: positionData.position.y,
|
|
122
|
+
width: sizeData.size.width,
|
|
123
|
+
height: sizeData.size.height,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { world } = this.getWorldTransform();
|
|
128
|
+
const b = pixi.getBounds();
|
|
129
|
+
const tl = world.toLocal(new PIXI.Point(b.x, b.y));
|
|
130
|
+
const br = world.toLocal(new PIXI.Point(b.x + b.width, b.y + b.height));
|
|
131
|
+
const wx = Math.min(tl.x, br.x);
|
|
132
|
+
const wy = Math.min(tl.y, br.y);
|
|
133
|
+
const ww = Math.max(1, Math.abs(br.x - tl.x));
|
|
134
|
+
const wh = Math.max(1, Math.abs(br.y - tl.y));
|
|
135
|
+
return { x: wx, y: wy, width: ww, height: wh };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getGroupSelectionWorldBounds(ids) {
|
|
139
|
+
const { world } = this.getWorldTransform();
|
|
140
|
+
let minX = Infinity;
|
|
141
|
+
let minY = Infinity;
|
|
142
|
+
let maxX = -Infinity;
|
|
143
|
+
let maxY = -Infinity;
|
|
144
|
+
|
|
145
|
+
ids.forEach((id) => {
|
|
146
|
+
const positionData = { objectId: id, position: null };
|
|
147
|
+
const sizeData = { objectId: id, size: null };
|
|
148
|
+
const rotationData = { objectId: id, rotation: 0 };
|
|
149
|
+
this.host.eventBus.emit(Events.Tool.GetObjectPosition, positionData);
|
|
150
|
+
this.host.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
|
|
151
|
+
this.host.eventBus.emit(Events.Tool.GetObjectRotation, rotationData);
|
|
152
|
+
|
|
153
|
+
const rectFromState = this._getWorldRectFromState(
|
|
154
|
+
positionData.position,
|
|
155
|
+
sizeData.size,
|
|
156
|
+
rotationData.rotation || 0
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
let x0;
|
|
160
|
+
let y0;
|
|
161
|
+
let x1;
|
|
162
|
+
let y1;
|
|
163
|
+
|
|
164
|
+
if (rectFromState) {
|
|
165
|
+
x0 = rectFromState.x;
|
|
166
|
+
y0 = rectFromState.y;
|
|
167
|
+
x1 = rectFromState.x + rectFromState.width;
|
|
168
|
+
y1 = rectFromState.y + rectFromState.height;
|
|
169
|
+
} else {
|
|
170
|
+
const p = this.host.core.pixi.objects.get(id);
|
|
171
|
+
if (!p) return;
|
|
172
|
+
const b = p.getBounds();
|
|
173
|
+
const tl = world.toLocal(new PIXI.Point(b.x, b.y));
|
|
174
|
+
const br = world.toLocal(new PIXI.Point(b.x + b.width, b.y + b.height));
|
|
175
|
+
x0 = Math.min(tl.x, br.x);
|
|
176
|
+
y0 = Math.min(tl.y, br.y);
|
|
177
|
+
x1 = Math.max(tl.x, br.x);
|
|
178
|
+
y1 = Math.max(tl.y, br.y);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
minX = Math.min(minX, x0);
|
|
182
|
+
minY = Math.min(minY, y0);
|
|
183
|
+
maxX = Math.max(maxX, x1);
|
|
184
|
+
maxY = Math.max(maxY, y1);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!isFinite(minX)) return null;
|
|
188
|
+
return {
|
|
189
|
+
x: minX,
|
|
190
|
+
y: minY,
|
|
191
|
+
width: Math.max(1, maxX - minX),
|
|
192
|
+
height: Math.max(1, maxY - minY),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
cssPointToWorld(centerX, centerY) {
|
|
197
|
+
const { s, tx, ty, rendererRes } = this.getWorldTransform();
|
|
198
|
+
const { offsetLeft, offsetTop } = this.getViewportOffsets();
|
|
199
|
+
const screenX = centerX - offsetLeft;
|
|
200
|
+
const screenY = centerY - offsetTop;
|
|
201
|
+
return {
|
|
202
|
+
x: ((screenX * rendererRes) - tx) / s,
|
|
203
|
+
y: ((screenY * rendererRes) - ty) / s,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class SingleSelectionHandlesController {
|
|
2
|
+
constructor(host) {
|
|
3
|
+
this.host = host;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
renderForSelection(id) {
|
|
7
|
+
const pixi = this.host.core.pixi.objects.get(id);
|
|
8
|
+
if (!pixi) {
|
|
9
|
+
this.host.hide();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const mb = pixi._mb || {};
|
|
14
|
+
if (mb.type === 'comment') {
|
|
15
|
+
this.host.hide();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const worldBounds = this.host.positioningService.getSingleSelectionWorldBounds(id, pixi);
|
|
20
|
+
this.host._showBounds(worldBounds, id);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
.moodboard-toolbar__button--text-add svg { height: 16px; width: auto; }
|
|
50
50
|
.moodboard-toolbar__button--text-add:hover svg { transform: none; }
|
|
51
51
|
.moodboard-toolbar__button:hover svg { transform: none; }
|
|
52
|
+
.moodboard-toolbar__button--image2 svg { color: #7c3aed; }
|
|
52
53
|
|
|
53
54
|
/* Hover palette for all toolbar buttons */
|
|
54
55
|
.moodboard-toolbar__button--select:hover,
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
.moodboard-toolbar__button--text-add:hover,
|
|
57
58
|
.moodboard-toolbar__button--note:hover,
|
|
58
59
|
.moodboard-toolbar__button--image:hover,
|
|
60
|
+
.moodboard-toolbar__button--image2:hover,
|
|
59
61
|
.moodboard-toolbar__button--shapes:hover,
|
|
60
62
|
.moodboard-toolbar__button--pencil:hover,
|
|
61
63
|
.moodboard-toolbar__button--comments:hover,
|
|
@@ -244,9 +244,10 @@
|
|
|
244
244
|
|
|
245
245
|
.mb-handles-box {
|
|
246
246
|
position: absolute;
|
|
247
|
-
|
|
247
|
+
outline: 2px solid #80D8FF;
|
|
248
|
+
outline-offset: 0;
|
|
248
249
|
border-radius: 3px;
|
|
249
|
-
box-sizing:
|
|
250
|
+
box-sizing: border-box;
|
|
250
251
|
pointer-events: none;
|
|
251
252
|
transform-origin: center center;
|
|
252
253
|
}
|
|
@@ -255,8 +256,8 @@
|
|
|
255
256
|
position: absolute;
|
|
256
257
|
width: 12px;
|
|
257
258
|
height: 12px;
|
|
258
|
-
background: #
|
|
259
|
-
border: 2px solid #
|
|
259
|
+
background: #80D8FF;
|
|
260
|
+
border: 2px solid #80D8FF;
|
|
260
261
|
border-radius: 50%;
|
|
261
262
|
box-sizing: border-box;
|
|
262
263
|
pointer-events: auto;
|
|
@@ -645,8 +646,14 @@
|
|
|
645
646
|
gap: 8px;
|
|
646
647
|
}
|
|
647
648
|
|
|
648
|
-
/* Draw popup layout */
|
|
649
|
-
.moodboard-draw__grid {
|
|
649
|
+
/* Draw popup layout: 2×3 grid, верхний и нижний ряд выровнены по колонкам */
|
|
650
|
+
.moodboard-draw__grid {
|
|
651
|
+
display: grid;
|
|
652
|
+
grid-template-columns: repeat(3, 1fr);
|
|
653
|
+
gap: 8px;
|
|
654
|
+
}
|
|
655
|
+
.moodboard-draw__grid > .moodboard-draw__row { display: contents; }
|
|
656
|
+
.moodboard-draw__placeholder { min-width: 30px; min-height: 30px; }
|
|
650
657
|
.moodboard-emoji__section { margin-bottom: 8px; }
|
|
651
658
|
.moodboard-emoji__title {
|
|
652
659
|
font-size: 12px;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
function hidePresetTicks(buttons) {
|
|
2
|
+
buttons.forEach((button) => {
|
|
3
|
+
const tick = button.querySelector('i');
|
|
4
|
+
if (tick) {
|
|
5
|
+
tick.style.display = 'none';
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function bindTextPropertiesPanelControls(panel) {
|
|
11
|
+
if (panel._bindingsAttached) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
panel.fontSelect.addEventListener('change', (event) => {
|
|
16
|
+
panel._changeFontFamily(event.target.value);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
panel.fontSizeSelect.addEventListener('change', (event) => {
|
|
20
|
+
panel._changeFontSize(parseInt(event.target.value, 10));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
panel.currentColorButton.addEventListener('click', (event) => {
|
|
24
|
+
event.stopPropagation();
|
|
25
|
+
panel._toggleColorDropdown();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
panel._colorPresetButtons.forEach((button) => {
|
|
29
|
+
button.addEventListener('click', () => {
|
|
30
|
+
hidePresetTicks(panel._colorPresetButtons);
|
|
31
|
+
const tick = button.querySelector('i');
|
|
32
|
+
if (tick) {
|
|
33
|
+
tick.style.display = 'block';
|
|
34
|
+
}
|
|
35
|
+
panel._selectColor(button.dataset.colorValue);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
panel.colorInput.addEventListener('change', (event) => {
|
|
40
|
+
panel._selectColor(event.target.value);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
panel._onColorDocumentClick = (event) => {
|
|
44
|
+
if (!panel._colorSelectorContainer || !event.target || !panel._colorSelectorContainer.contains(event.target)) {
|
|
45
|
+
panel._hideColorDropdown();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
document.addEventListener('click', panel._onColorDocumentClick);
|
|
49
|
+
|
|
50
|
+
panel.currentBgColorButton.addEventListener('click', (event) => {
|
|
51
|
+
event.stopPropagation();
|
|
52
|
+
panel._toggleBgColorDropdown();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
panel._bgPresetButtons.forEach((button) => {
|
|
56
|
+
button.addEventListener('click', () => {
|
|
57
|
+
hidePresetTicks(panel._bgPresetButtons);
|
|
58
|
+
const tick = button.querySelector('i');
|
|
59
|
+
if (tick) {
|
|
60
|
+
tick.style.display = 'block';
|
|
61
|
+
}
|
|
62
|
+
panel._selectBgColor(button.dataset.colorValue);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
panel.bgColorInput.addEventListener('change', (event) => {
|
|
67
|
+
panel._selectBgColor(event.target.value);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
panel._onBgDocumentClick = (event) => {
|
|
71
|
+
if (!panel._bgSelectorContainer || !event.target || !panel._bgSelectorContainer.contains(event.target)) {
|
|
72
|
+
panel._hideBgColorDropdown();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
document.addEventListener('click', panel._onBgDocumentClick);
|
|
76
|
+
|
|
77
|
+
panel._bindingsAttached = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function unbindTextPropertiesPanelControls(panel) {
|
|
81
|
+
if (panel._onColorDocumentClick) {
|
|
82
|
+
document.removeEventListener('click', panel._onColorDocumentClick);
|
|
83
|
+
panel._onColorDocumentClick = null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (panel._onBgDocumentClick) {
|
|
87
|
+
document.removeEventListener('click', panel._onBgDocumentClick);
|
|
88
|
+
panel._onBgDocumentClick = null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
panel._bindingsAttached = false;
|
|
92
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Events } from '../../core/events/Events.js';
|
|
2
|
+
|
|
3
|
+
export function attachTextPropertiesPanelEventBridge(panel) {
|
|
4
|
+
if (panel._eventBridgeAttached) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
panel._eventBridgeHandlers = {
|
|
9
|
+
onSelectionAdd: () => panel.updateFromSelection(),
|
|
10
|
+
onSelectionRemove: () => panel.updateFromSelection(),
|
|
11
|
+
onSelectionClear: () => panel.hide(),
|
|
12
|
+
onDragUpdate: () => panel.reposition(),
|
|
13
|
+
onGroupDragUpdate: () => panel.reposition(),
|
|
14
|
+
onResizeUpdate: () => panel.reposition(),
|
|
15
|
+
onRotateUpdate: () => panel.reposition(),
|
|
16
|
+
onZoomPercent: () => panel.reposition(),
|
|
17
|
+
onPanUpdate: () => panel.reposition(),
|
|
18
|
+
onDeleted: ({ objectId }) => {
|
|
19
|
+
if (panel.currentId && objectId === panel.currentId) {
|
|
20
|
+
panel.hide();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
onTextEditStart: () => {
|
|
24
|
+
panel.isTextEditing = true;
|
|
25
|
+
panel.hide();
|
|
26
|
+
},
|
|
27
|
+
onTextEditEnd: () => {
|
|
28
|
+
panel.isTextEditing = false;
|
|
29
|
+
setTimeout(() => panel.updateFromSelection(), 100);
|
|
30
|
+
},
|
|
31
|
+
onStateChanged: ({ objectId }) => {
|
|
32
|
+
if (panel.currentId && objectId === panel.currentId && panel.panel && panel.panel.style.display !== 'none') {
|
|
33
|
+
panel._updateControlsFromObject();
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
panel.eventBus.on(Events.Tool.SelectionAdd, panel._eventBridgeHandlers.onSelectionAdd);
|
|
39
|
+
panel.eventBus.on(Events.Tool.SelectionRemove, panel._eventBridgeHandlers.onSelectionRemove);
|
|
40
|
+
panel.eventBus.on(Events.Tool.SelectionClear, panel._eventBridgeHandlers.onSelectionClear);
|
|
41
|
+
panel.eventBus.on(Events.Tool.DragUpdate, panel._eventBridgeHandlers.onDragUpdate);
|
|
42
|
+
panel.eventBus.on(Events.Tool.GroupDragUpdate, panel._eventBridgeHandlers.onGroupDragUpdate);
|
|
43
|
+
panel.eventBus.on(Events.Tool.ResizeUpdate, panel._eventBridgeHandlers.onResizeUpdate);
|
|
44
|
+
panel.eventBus.on(Events.Tool.RotateUpdate, panel._eventBridgeHandlers.onRotateUpdate);
|
|
45
|
+
panel.eventBus.on(Events.UI.ZoomPercent, panel._eventBridgeHandlers.onZoomPercent);
|
|
46
|
+
panel.eventBus.on(Events.Tool.PanUpdate, panel._eventBridgeHandlers.onPanUpdate);
|
|
47
|
+
panel.eventBus.on(Events.Object.Deleted, panel._eventBridgeHandlers.onDeleted);
|
|
48
|
+
panel.eventBus.on(Events.UI.TextEditStart, panel._eventBridgeHandlers.onTextEditStart);
|
|
49
|
+
panel.eventBus.on(Events.UI.TextEditEnd, panel._eventBridgeHandlers.onTextEditEnd);
|
|
50
|
+
panel.eventBus.on(Events.Object.StateChanged, panel._eventBridgeHandlers.onStateChanged);
|
|
51
|
+
|
|
52
|
+
panel._eventBridgeAttached = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function detachTextPropertiesPanelEventBridge(panel) {
|
|
56
|
+
if (!panel._eventBridgeAttached || !panel._eventBridgeHandlers || !panel.eventBus?.off) {
|
|
57
|
+
panel._eventBridgeAttached = false;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
panel.eventBus.off(Events.Tool.SelectionAdd, panel._eventBridgeHandlers.onSelectionAdd);
|
|
62
|
+
panel.eventBus.off(Events.Tool.SelectionRemove, panel._eventBridgeHandlers.onSelectionRemove);
|
|
63
|
+
panel.eventBus.off(Events.Tool.SelectionClear, panel._eventBridgeHandlers.onSelectionClear);
|
|
64
|
+
panel.eventBus.off(Events.Tool.DragUpdate, panel._eventBridgeHandlers.onDragUpdate);
|
|
65
|
+
panel.eventBus.off(Events.Tool.GroupDragUpdate, panel._eventBridgeHandlers.onGroupDragUpdate);
|
|
66
|
+
panel.eventBus.off(Events.Tool.ResizeUpdate, panel._eventBridgeHandlers.onResizeUpdate);
|
|
67
|
+
panel.eventBus.off(Events.Tool.RotateUpdate, panel._eventBridgeHandlers.onRotateUpdate);
|
|
68
|
+
panel.eventBus.off(Events.UI.ZoomPercent, panel._eventBridgeHandlers.onZoomPercent);
|
|
69
|
+
panel.eventBus.off(Events.Tool.PanUpdate, panel._eventBridgeHandlers.onPanUpdate);
|
|
70
|
+
panel.eventBus.off(Events.Object.Deleted, panel._eventBridgeHandlers.onDeleted);
|
|
71
|
+
panel.eventBus.off(Events.UI.TextEditStart, panel._eventBridgeHandlers.onTextEditStart);
|
|
72
|
+
panel.eventBus.off(Events.UI.TextEditEnd, panel._eventBridgeHandlers.onTextEditEnd);
|
|
73
|
+
panel.eventBus.off(Events.Object.StateChanged, panel._eventBridgeHandlers.onStateChanged);
|
|
74
|
+
|
|
75
|
+
panel._eventBridgeHandlers = null;
|
|
76
|
+
panel._eventBridgeAttached = false;
|
|
77
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Events } from '../../core/events/Events.js';
|
|
2
|
+
|
|
3
|
+
export const FONT_OPTIONS = [
|
|
4
|
+
{ value: 'Roboto, Arial, sans-serif', name: 'Roboto' },
|
|
5
|
+
{ value: 'Oswald, Arial, sans-serif', name: 'Oswald' },
|
|
6
|
+
{ value: '"Playfair Display", Georgia, serif', name: 'Playfair Display' },
|
|
7
|
+
{ value: '"Roboto Slab", Georgia, serif', name: 'Roboto Slab' },
|
|
8
|
+
{ value: '"Noto Serif", Georgia, serif', name: 'Noto Serif' },
|
|
9
|
+
{ value: 'Lobster, "Comic Sans MS", cursive', name: 'Lobster' },
|
|
10
|
+
{ value: 'Caveat, "Comic Sans MS", cursive', name: 'Caveat' },
|
|
11
|
+
{ value: '"Rubik Mono One", "Courier New", monospace', name: 'Rubik Mono One' },
|
|
12
|
+
{ value: '"Great Vibes", "Comic Sans MS", cursive', name: 'Great Vibes' },
|
|
13
|
+
{ value: '"Amatic SC", "Comic Sans MS", cursive', name: 'Amatic SC' },
|
|
14
|
+
{ value: '"Poiret One", Arial, sans-serif', name: 'Poiret One' },
|
|
15
|
+
{ value: 'Pacifico, "Comic Sans MS", cursive', name: 'Pacifico' },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const FONT_SIZE_OPTIONS = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 60, 72];
|
|
19
|
+
|
|
20
|
+
export const TEXT_COLOR_PRESETS = [
|
|
21
|
+
{ color: '#000000', name: '#000000' },
|
|
22
|
+
{ color: '#404040', name: '#404040' },
|
|
23
|
+
{ color: '#999999', name: '#999999' },
|
|
24
|
+
{ color: '#FF2D55', name: '#FF2D55' },
|
|
25
|
+
{ color: '#CB30E0', name: '#CB30E0' },
|
|
26
|
+
{ color: '#6155F5', name: '#6155F5' },
|
|
27
|
+
{ color: '#00C0E8', name: '#00C0E8' },
|
|
28
|
+
{ color: '#34C759', name: '#34C759' },
|
|
29
|
+
{ color: '#FF8D28', name: '#FF8D28' },
|
|
30
|
+
{ color: '#FFCC00', name: '#FFCC00' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const BACKGROUND_COLOR_PRESETS = [
|
|
34
|
+
{ color: 'transparent', name: 'Без выделения' },
|
|
35
|
+
{ color: '#ffff99', name: 'Желтый' },
|
|
36
|
+
{ color: '#ffcc99', name: 'Оранжевый' },
|
|
37
|
+
{ color: '#ff9999', name: 'Розовый' },
|
|
38
|
+
{ color: '#ccffcc', name: 'Зеленый' },
|
|
39
|
+
{ color: '#99ccff', name: 'Голубой' },
|
|
40
|
+
{ color: '#cc99ff', name: 'Фиолетовый' },
|
|
41
|
+
{ color: '#f0f0f0', name: 'Светло-серый' },
|
|
42
|
+
{ color: '#d0d0d0', name: 'Серый' },
|
|
43
|
+
{ color: '#ffffff', name: 'Белый' },
|
|
44
|
+
{ color: '#000000', name: 'Черный' },
|
|
45
|
+
{ color: '#333333', name: 'Темно-серый' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export function getSelectedTextObjectId(core) {
|
|
49
|
+
const ids = core?.selectTool ? Array.from(core.selectTool.selectedObjects || []) : [];
|
|
50
|
+
if (!ids || ids.length !== 1) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const id = ids[0];
|
|
55
|
+
const pixi = core?.pixi?.objects?.get ? core.pixi.objects.get(id) : null;
|
|
56
|
+
const mb = pixi?._mb || {};
|
|
57
|
+
|
|
58
|
+
if (mb.type !== 'text') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return id;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getPixiObject(eventBus, objectId) {
|
|
66
|
+
if (!objectId) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const pixiData = { objectId, pixiObject: null };
|
|
71
|
+
eventBus.emit(Events.Tool.GetObjectPixi, pixiData);
|
|
72
|
+
return pixiData.pixiObject;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getObjectProperties(eventBus, objectId) {
|
|
76
|
+
const pixiObject = getPixiObject(eventBus, objectId);
|
|
77
|
+
if (pixiObject && pixiObject._mb && pixiObject._mb.properties) {
|
|
78
|
+
return pixiObject._mb.properties;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getControlValuesFromProperties(properties) {
|
|
84
|
+
return {
|
|
85
|
+
fontFamily: properties.fontFamily || 'Roboto, Arial, sans-serif',
|
|
86
|
+
fontSize: String(properties.fontSize || 18),
|
|
87
|
+
color: properties.color || '#000000',
|
|
88
|
+
backgroundColor: properties.backgroundColor !== undefined ? properties.backgroundColor : 'transparent',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getFallbackControlValues() {
|
|
93
|
+
return {
|
|
94
|
+
fontFamily: 'Arial, sans-serif',
|
|
95
|
+
fontSize: '18',
|
|
96
|
+
color: '#000000',
|
|
97
|
+
backgroundColor: 'transparent',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function buildFontFamilyUpdate(fontFamily) {
|
|
102
|
+
return {
|
|
103
|
+
properties: { fontFamily },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function buildFontSizeUpdate(fontSize) {
|
|
108
|
+
return {
|
|
109
|
+
fontSize,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function buildTextColorUpdate(color) {
|
|
114
|
+
return {
|
|
115
|
+
color,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function buildBackgroundColorUpdate(backgroundColor) {
|
|
120
|
+
return {
|
|
121
|
+
backgroundColor,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function applyTextAppearanceToDom(objectId, properties) {
|
|
126
|
+
const htmlElement = document.querySelector(`[data-id="${objectId}"]`);
|
|
127
|
+
if (!htmlElement) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (properties.fontFamily) {
|
|
132
|
+
htmlElement.style.fontFamily = properties.fontFamily;
|
|
133
|
+
}
|
|
134
|
+
if (properties.fontSize) {
|
|
135
|
+
htmlElement.style.fontSize = `${properties.fontSize}px`;
|
|
136
|
+
}
|
|
137
|
+
if (properties.color) {
|
|
138
|
+
htmlElement.style.color = properties.color;
|
|
139
|
+
}
|
|
140
|
+
if (properties.backgroundColor !== undefined) {
|
|
141
|
+
if (properties.backgroundColor === 'transparent') {
|
|
142
|
+
htmlElement.style.backgroundColor = '';
|
|
143
|
+
} else {
|
|
144
|
+
htmlElement.style.backgroundColor = properties.backgroundColor;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function syncPixiTextProperties(eventBus, objectId, properties) {
|
|
150
|
+
const pixiObject = getPixiObject(eventBus, objectId);
|
|
151
|
+
if (!pixiObject || !pixiObject._mb) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!pixiObject._mb.properties) {
|
|
156
|
+
pixiObject._mb.properties = {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Object.assign(pixiObject._mb.properties, properties);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getObjectGeometry(eventBus, objectId) {
|
|
163
|
+
const posData = { objectId, position: null };
|
|
164
|
+
const sizeData = { objectId, size: null };
|
|
165
|
+
|
|
166
|
+
eventBus.emit(Events.Tool.GetObjectPosition, posData);
|
|
167
|
+
eventBus.emit(Events.Tool.GetObjectSize, sizeData);
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
position: posData.position,
|
|
171
|
+
size: sizeData.size,
|
|
172
|
+
};
|
|
173
|
+
}
|