@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.
Files changed (122) hide show
  1. package/package.json +11 -1
  2. package/src/assets/icons/rotate-icon.svg +1 -1
  3. package/src/core/HistoryManager.js +16 -16
  4. package/src/core/KeyboardManager.js +48 -539
  5. package/src/core/PixiEngine.js +9 -9
  6. package/src/core/SaveManager.js +56 -31
  7. package/src/core/bootstrap/CoreInitializer.js +65 -0
  8. package/src/core/commands/DeleteObjectCommand.js +8 -0
  9. package/src/core/commands/GroupDeleteCommand.js +75 -0
  10. package/src/core/commands/GroupRotateCommand.js +6 -0
  11. package/src/core/commands/UpdateContentCommand.js +52 -0
  12. package/src/core/commands/UpdateFramePropertiesCommand.js +98 -0
  13. package/src/core/commands/UpdateFrameTypeCommand.js +85 -0
  14. package/src/core/commands/UpdateNoteStyleCommand.js +88 -0
  15. package/src/core/commands/UpdateTextStyleCommand.js +90 -0
  16. package/src/core/commands/index.js +6 -0
  17. package/src/core/events/Events.js +6 -0
  18. package/src/core/flows/ClipboardFlow.js +553 -0
  19. package/src/core/flows/LayerAndViewportFlow.js +283 -0
  20. package/src/core/flows/ObjectLifecycleFlow.js +336 -0
  21. package/src/core/flows/SaveFlow.js +34 -0
  22. package/src/core/flows/TransformFlow.js +277 -0
  23. package/src/core/flows/TransformFlowResizeHelpers.js +83 -0
  24. package/src/core/index.js +41 -1773
  25. package/src/core/keyboard/KeyboardClipboardImagePaste.js +190 -0
  26. package/src/core/keyboard/KeyboardContextGuards.js +35 -0
  27. package/src/core/keyboard/KeyboardEventRouter.js +92 -0
  28. package/src/core/keyboard/KeyboardSelectionActions.js +103 -0
  29. package/src/core/keyboard/KeyboardShortcutMap.js +31 -0
  30. package/src/core/keyboard/KeyboardToolSwitching.js +26 -0
  31. package/src/core/rendering/ObjectRenderer.js +3 -7
  32. package/src/grid/BaseGrid.js +26 -0
  33. package/src/grid/CrossGrid.js +7 -6
  34. package/src/grid/DotGrid.js +89 -33
  35. package/src/grid/DotGridZoomPhases.js +42 -0
  36. package/src/grid/LineGrid.js +22 -21
  37. package/src/moodboard/MoodBoard.js +31 -532
  38. package/src/moodboard/bootstrap/MoodBoardInitializer.js +47 -0
  39. package/src/moodboard/bootstrap/MoodBoardManagersFactory.js +38 -0
  40. package/src/moodboard/bootstrap/MoodBoardUiFactory.js +109 -0
  41. package/src/moodboard/integration/MoodBoardEventBindings.js +65 -0
  42. package/src/moodboard/integration/MoodBoardLoadApi.js +82 -0
  43. package/src/moodboard/integration/MoodBoardScreenshotApi.js +33 -0
  44. package/src/moodboard/integration/MoodBoardScreenshotCanvas.js +98 -0
  45. package/src/moodboard/lifecycle/MoodBoardDestroyer.js +97 -0
  46. package/src/objects/FileObject.js +17 -6
  47. package/src/objects/FrameObject.js +50 -10
  48. package/src/objects/NoteObject.js +5 -4
  49. package/src/services/BoardService.js +42 -2
  50. package/src/services/FrameService.js +83 -42
  51. package/src/services/ResizePolicyService.js +152 -0
  52. package/src/services/SettingsApplier.js +7 -2
  53. package/src/services/ZoomPanController.js +35 -9
  54. package/src/tools/ToolManager.js +30 -537
  55. package/src/tools/board-tools/PanTool.js +5 -11
  56. package/src/tools/manager/ToolActivationController.js +49 -0
  57. package/src/tools/manager/ToolEventRouter.js +396 -0
  58. package/src/tools/manager/ToolManagerGuards.js +33 -0
  59. package/src/tools/manager/ToolManagerLifecycle.js +110 -0
  60. package/src/tools/manager/ToolRegistry.js +33 -0
  61. package/src/tools/object-tools/DrawingTool.js +48 -14
  62. package/src/tools/object-tools/PlacementTool.js +50 -1049
  63. package/src/tools/object-tools/PlacementToolV2.js +88 -0
  64. package/src/tools/object-tools/SelectTool.js +174 -2681
  65. package/src/tools/object-tools/placement/GhostController.js +504 -0
  66. package/src/tools/object-tools/placement/PlacementCoordinateResolver.js +20 -0
  67. package/src/tools/object-tools/placement/PlacementEventsBridge.js +91 -0
  68. package/src/tools/object-tools/placement/PlacementInputRouter.js +267 -0
  69. package/src/tools/object-tools/placement/PlacementPayloadFactory.js +111 -0
  70. package/src/tools/object-tools/placement/PlacementSessionStore.js +18 -0
  71. package/src/tools/object-tools/selection/BoxSelectController.js +0 -5
  72. package/src/tools/object-tools/selection/CloneFlowController.js +71 -0
  73. package/src/tools/object-tools/selection/CoordinateMapper.js +10 -0
  74. package/src/tools/object-tools/selection/CursorController.js +78 -0
  75. package/src/tools/object-tools/selection/FileNameInlineEditorController.js +184 -0
  76. package/src/tools/object-tools/selection/HitTestService.js +102 -0
  77. package/src/tools/object-tools/selection/InlineEditorController.js +24 -0
  78. package/src/tools/object-tools/selection/InlineEditorDomFactory.js +50 -0
  79. package/src/tools/object-tools/selection/InlineEditorListenersRegistry.js +14 -0
  80. package/src/tools/object-tools/selection/InlineEditorPositioningService.js +25 -0
  81. package/src/tools/object-tools/selection/NoteInlineEditorController.js +113 -0
  82. package/src/tools/object-tools/selection/SelectInputRouter.js +267 -0
  83. package/src/tools/object-tools/selection/SelectToolLifecycleController.js +128 -0
  84. package/src/tools/object-tools/selection/SelectToolSetup.js +134 -0
  85. package/src/tools/object-tools/selection/SelectionOverlayService.js +81 -0
  86. package/src/tools/object-tools/selection/SelectionStateController.js +91 -0
  87. package/src/tools/object-tools/selection/TextEditorDomFactory.js +65 -0
  88. package/src/tools/object-tools/selection/TextEditorInteractionController.js +266 -0
  89. package/src/tools/object-tools/selection/TextEditorLifecycleRegistry.js +90 -0
  90. package/src/tools/object-tools/selection/TextEditorPositioningService.js +158 -0
  91. package/src/tools/object-tools/selection/TextEditorSyncService.js +110 -0
  92. package/src/tools/object-tools/selection/TextInlineEditorController.js +457 -0
  93. package/src/tools/object-tools/selection/TransformInteractionController.js +466 -0
  94. package/src/ui/FilePropertiesPanel.js +61 -32
  95. package/src/ui/FramePropertiesPanel.js +176 -101
  96. package/src/ui/HtmlHandlesLayer.js +121 -999
  97. package/src/ui/MapPanel.js +12 -7
  98. package/src/ui/NotePropertiesPanel.js +17 -2
  99. package/src/ui/TextPropertiesPanel.js +124 -738
  100. package/src/ui/Toolbar.js +71 -1180
  101. package/src/ui/Topbar.js +23 -25
  102. package/src/ui/ZoomPanel.js +16 -5
  103. package/src/ui/handles/GroupSelectionHandlesController.js +29 -0
  104. package/src/ui/handles/HandlesDomRenderer.js +278 -0
  105. package/src/ui/handles/HandlesEventBridge.js +102 -0
  106. package/src/ui/handles/HandlesInteractionController.js +772 -0
  107. package/src/ui/handles/HandlesPositioningService.js +206 -0
  108. package/src/ui/handles/SingleSelectionHandlesController.js +22 -0
  109. package/src/ui/styles/toolbar.css +2 -0
  110. package/src/ui/styles/workspace.css +13 -6
  111. package/src/ui/text-properties/TextPropertiesPanelBindings.js +92 -0
  112. package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +77 -0
  113. package/src/ui/text-properties/TextPropertiesPanelMapper.js +173 -0
  114. package/src/ui/text-properties/TextPropertiesPanelRenderer.js +434 -0
  115. package/src/ui/text-properties/TextPropertiesPanelState.js +39 -0
  116. package/src/ui/toolbar/ToolbarActionRouter.js +193 -0
  117. package/src/ui/toolbar/ToolbarDialogsController.js +186 -0
  118. package/src/ui/toolbar/ToolbarPopupsController.js +662 -0
  119. package/src/ui/toolbar/ToolbarRenderer.js +97 -0
  120. package/src/ui/toolbar/ToolbarStateController.js +79 -0
  121. package/src/ui/toolbar/ToolbarTooltipController.js +52 -0
  122. 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
- border: 1px solid #1DE9B6;
247
+ outline: 2px solid #80D8FF;
248
+ outline-offset: 0;
248
249
  border-radius: 3px;
249
- box-sizing: content-box;
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: #1DE9B6;
259
- border: 2px solid #1DE9B6;
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 { display: grid; gap: 8px; }
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
+ }