@sequent-org/moodboard 1.3.5 → 1.4.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 +6 -1
- package/src/assets/icons/mindmap.svg +3 -0
- package/src/core/SaveManager.js +44 -15
- package/src/core/commands/MindmapStatePatchCommand.js +85 -0
- package/src/core/commands/UpdateContentCommand.js +47 -4
- package/src/core/flows/LayerAndViewportFlow.js +87 -14
- package/src/core/flows/ObjectLifecycleFlow.js +7 -2
- package/src/core/flows/SaveFlow.js +10 -7
- package/src/core/flows/TransformFlow.js +2 -2
- package/src/core/index.js +81 -11
- package/src/core/rendering/ObjectRenderer.js +7 -2
- package/src/grid/BaseGrid.js +65 -0
- package/src/grid/CrossGrid.js +89 -24
- package/src/grid/CrossGridZoomPhases.js +167 -0
- package/src/grid/DotGrid.js +117 -34
- package/src/grid/DotGridZoomPhases.js +214 -16
- package/src/grid/GridDiagnostics.js +80 -0
- package/src/grid/GridFactory.js +13 -11
- package/src/grid/LineGrid.js +176 -37
- package/src/grid/LineGridZoomPhases.js +163 -0
- package/src/grid/ScreenGridPhaseMachine.js +51 -0
- package/src/mindmap/MindmapCompoundContract.js +235 -0
- package/src/moodboard/ActionHandler.js +1 -0
- package/src/moodboard/DataManager.js +57 -0
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +21 -0
- package/src/moodboard/integration/MoodBoardEventBindings.js +82 -1
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +15 -0
- package/src/objects/MindmapObject.js +76 -0
- package/src/objects/ObjectFactory.js +3 -1
- package/src/services/BoardService.js +127 -31
- package/src/services/GridSnapResolver.js +60 -0
- package/src/services/MiroZoomLevels.js +39 -0
- package/src/services/SettingsApplier.js +0 -4
- package/src/services/ZoomPanController.js +51 -32
- package/src/tools/object-tools/PlacementTool.js +12 -3
- package/src/tools/object-tools/SelectTool.js +11 -1
- package/src/tools/object-tools/placement/GhostController.js +100 -1
- package/src/tools/object-tools/placement/PlacementEventsBridge.js +2 -0
- package/src/tools/object-tools/placement/PlacementInputRouter.js +2 -2
- package/src/tools/object-tools/selection/FileNameInlineEditorController.js +2 -2
- package/src/tools/object-tools/selection/InlineEditorController.js +15 -0
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +757 -0
- package/src/tools/object-tools/selection/SelectInputRouter.js +20 -1
- package/src/tools/object-tools/selection/SelectToolSetup.js +2 -0
- package/src/tools/object-tools/selection/SelectionStateController.js +7 -0
- package/src/tools/object-tools/selection/TextEditorLifecycleRegistry.js +12 -16
- package/src/ui/ContextMenu.js +6 -6
- package/src/ui/DotGridDebugPanel.js +253 -0
- package/src/ui/HtmlTextLayer.js +1 -1
- package/src/ui/TextPropertiesPanel.js +2 -2
- package/src/ui/handles/GroupSelectionHandlesController.js +4 -1
- package/src/ui/handles/HandlesDomRenderer.js +1485 -14
- package/src/ui/handles/HandlesEventBridge.js +49 -5
- package/src/ui/handles/HandlesInteractionController.js +4 -4
- package/src/ui/mindmap/MindmapConnectionLayer.js +254 -0
- package/src/ui/mindmap/MindmapHtmlTextLayer.js +285 -0
- package/src/ui/mindmap/MindmapLayoutConfig.js +29 -0
- package/src/ui/mindmap/MindmapTextOverlayAdapter.js +144 -0
- package/src/ui/styles/toolbar.css +1 -0
- package/src/ui/styles/workspace.css +100 -0
- package/src/ui/toolbar/ToolbarActionRouter.js +35 -0
- package/src/ui/toolbar/ToolbarPopupsController.js +6 -6
- package/src/ui/toolbar/ToolbarRenderer.js +1 -0
- package/src/ui/toolbar/ToolbarStateController.js +1 -0
- package/src/utils/iconLoader.js +10 -4
|
@@ -3,11 +3,17 @@ import { Events } from '../../../core/events/Events.js';
|
|
|
3
3
|
export function onMouseDown(event) {
|
|
4
4
|
// Если активен текстовый редактор, закрываем его при клике вне
|
|
5
5
|
if (this.textEditor.active) {
|
|
6
|
+
const activeEditorType = this.textEditor.objectType;
|
|
6
7
|
if (this.textEditor.objectType === 'file') {
|
|
7
8
|
this._closeFileNameEditor(true);
|
|
8
9
|
} else {
|
|
9
10
|
this._closeTextEditor(true);
|
|
10
11
|
}
|
|
12
|
+
// Mindmap UX: outside click should fully reset on first click
|
|
13
|
+
// (editor close + selection clear + handles/buttons hidden).
|
|
14
|
+
if (activeEditorType === 'mindmap') {
|
|
15
|
+
this.clearSelection();
|
|
16
|
+
}
|
|
11
17
|
return; // Прерываем выполнение, чтобы не обрабатывать клик дальше
|
|
12
18
|
}
|
|
13
19
|
|
|
@@ -244,7 +250,20 @@ export function onContextMenu(event) {
|
|
|
244
250
|
export function onKeyDown(event) {
|
|
245
251
|
// Проверяем, не активен ли текстовый редактор (редактирование названия файла или текста)
|
|
246
252
|
if (this.textEditor && this.textEditor.active) {
|
|
247
|
-
|
|
253
|
+
if (event.key === 'Escape') {
|
|
254
|
+
if (this.textEditor.objectType === 'file') {
|
|
255
|
+
this._closeFileNameEditor(false);
|
|
256
|
+
} else {
|
|
257
|
+
this._closeTextEditor(false);
|
|
258
|
+
}
|
|
259
|
+
if (this.textEditor.objectType === 'mindmap') {
|
|
260
|
+
this.clearSelection();
|
|
261
|
+
}
|
|
262
|
+
if (event?.originalEvent?.preventDefault) {
|
|
263
|
+
event.originalEvent.preventDefault();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return; // Не обрабатываем остальные клавиши во время редактирования
|
|
248
267
|
}
|
|
249
268
|
|
|
250
269
|
switch (event.key) {
|
|
@@ -93,6 +93,8 @@ export function registerSelectToolCoreSubscriptions(instance) {
|
|
|
93
93
|
const objectType = object.type || (object.object && object.object.type) || 'text';
|
|
94
94
|
if (objectType === 'file') {
|
|
95
95
|
instance._openFileNameEditor(object, object.create || false);
|
|
96
|
+
} else if (objectType === 'mindmap') {
|
|
97
|
+
instance._openMindmapEditor(object, object.create || false);
|
|
96
98
|
} else {
|
|
97
99
|
if (object.create) {
|
|
98
100
|
instance._openTextEditor(object, true);
|
|
@@ -48,6 +48,13 @@ export function hasSelection() {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export function setSelection(objectIds) {
|
|
51
|
+
if (this.textEditor?.active && !this._selectionSyncFromEditor) {
|
|
52
|
+
if (this.textEditor.objectType === 'file' && typeof this._closeFileNameEditor === 'function') {
|
|
53
|
+
this._closeFileNameEditor(true);
|
|
54
|
+
} else if (typeof this._closeTextEditor === 'function') {
|
|
55
|
+
this._closeTextEditor(true);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
51
58
|
const prev = this.selection.toArray();
|
|
52
59
|
this.selection.clear();
|
|
53
60
|
this.selection.addMany(objectIds);
|
|
@@ -20,31 +20,27 @@ export function updateGlobalTextEditorHandlesLayer() {
|
|
|
20
20
|
export function hideStaticTextDuringEditing(controller, objectId) {
|
|
21
21
|
if (!objectId) return;
|
|
22
22
|
|
|
23
|
-
if (typeof window !== 'undefined'
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.warn(`❌ SelectTool: HTML-элемент для объекта ${objectId} не найден, пропускаем HideObjectText`);
|
|
23
|
+
if (typeof window !== 'undefined') {
|
|
24
|
+
const textEl = window.moodboardHtmlTextLayer?.idToEl?.get?.(objectId);
|
|
25
|
+
const mindmapEl = window.moodboardMindmapHtmlTextLayer?.idToEl?.get?.(objectId);
|
|
26
|
+
if (!textEl && !mindmapEl) {
|
|
27
|
+
console.warn(`❌ SelectTool: HTML-элемент для объекта ${objectId} не найден, fallback через событие`);
|
|
29
28
|
}
|
|
30
|
-
} else {
|
|
31
|
-
controller.eventBus.emit(Events.Tool.HideObjectText, { objectId });
|
|
32
29
|
}
|
|
30
|
+
controller.eventBus.emit(Events.Tool.HideObjectText, { objectId });
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
export function showStaticTextAfterEditing(controller, objectId) {
|
|
36
34
|
if (!objectId) return;
|
|
37
35
|
|
|
38
|
-
if (typeof window !== 'undefined'
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.warn(`❌ SelectTool: HTML-элемент для объекта ${objectId} не найден, пропускаем ShowObjectText`);
|
|
36
|
+
if (typeof window !== 'undefined') {
|
|
37
|
+
const textEl = window.moodboardHtmlTextLayer?.idToEl?.get?.(objectId);
|
|
38
|
+
const mindmapEl = window.moodboardMindmapHtmlTextLayer?.idToEl?.get?.(objectId);
|
|
39
|
+
if (!textEl && !mindmapEl) {
|
|
40
|
+
console.warn(`❌ SelectTool: HTML-элемент для объекта ${objectId} не найден, fallback через событие`);
|
|
44
41
|
}
|
|
45
|
-
} else {
|
|
46
|
-
controller.eventBus.emit(Events.Tool.ShowObjectText, { objectId });
|
|
47
42
|
}
|
|
43
|
+
controller.eventBus.emit(Events.Tool.ShowObjectText, { objectId });
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
export function hideNotePixiText(controller, objectId) {
|
package/src/ui/ContextMenu.js
CHANGED
|
@@ -65,11 +65,11 @@ export class ContextMenu {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
show(x, y, context = 'canvas', targetId = null) {
|
|
68
|
-
this.lastX = x;
|
|
69
|
-
this.lastY = y;
|
|
68
|
+
this.lastX = Math.round(x);
|
|
69
|
+
this.lastY = Math.round(y);
|
|
70
70
|
this.renderItems(context, targetId);
|
|
71
|
-
this.element.style.left = `${
|
|
72
|
-
this.element.style.top = `${
|
|
71
|
+
this.element.style.left = `${this.lastX}px`;
|
|
72
|
+
this.element.style.top = `${this.lastY}px`;
|
|
73
73
|
this.element.style.display = 'block';
|
|
74
74
|
this.isVisible = true;
|
|
75
75
|
this.ensureInViewport();
|
|
@@ -90,8 +90,8 @@ export class ContextMenu {
|
|
|
90
90
|
if (dx !== 0 || dy !== 0) {
|
|
91
91
|
const left = parseInt(this.element.style.left || '0', 10) + dx;
|
|
92
92
|
const top = parseInt(this.element.style.top || '0', 10) + dy;
|
|
93
|
-
this.element.style.left = `${left}px`;
|
|
94
|
-
this.element.style.top = `${top}px`;
|
|
93
|
+
this.element.style.left = `${Math.round(left)}px`;
|
|
94
|
+
this.element.style.top = `${Math.round(top)}px`;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { getCrossCheckpointForZoom, updateCrossCheckpoint } from '../grid/CrossGridZoomPhases.js';
|
|
2
|
+
import { Events } from '../core/events/Events.js';
|
|
3
|
+
|
|
4
|
+
function intToHex(color) {
|
|
5
|
+
const n = Math.max(0, Math.min(0xFFFFFF, Math.round(Number(color) || 0)));
|
|
6
|
+
return `#${n.toString(16).padStart(6, '0')}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function hexToInt(hex) {
|
|
10
|
+
const h = String(hex || '').trim().replace('#', '');
|
|
11
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
|
|
12
|
+
return Number.parseInt(h, 16);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class DotGridDebugPanel {
|
|
16
|
+
constructor(container, coreMoodboard) {
|
|
17
|
+
this.container = container;
|
|
18
|
+
this.core = coreMoodboard;
|
|
19
|
+
this.element = null;
|
|
20
|
+
this._pollTimer = null;
|
|
21
|
+
this._activeCheckpoint = null;
|
|
22
|
+
this._isApplying = false;
|
|
23
|
+
this._init();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_init() {
|
|
27
|
+
this._create();
|
|
28
|
+
this._attach();
|
|
29
|
+
this._startPolling();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_create() {
|
|
33
|
+
const panel = document.createElement('div');
|
|
34
|
+
panel.style.position = 'absolute';
|
|
35
|
+
panel.style.right = '16px';
|
|
36
|
+
panel.style.top = '16px';
|
|
37
|
+
panel.style.zIndex = '9999';
|
|
38
|
+
panel.style.width = '260px';
|
|
39
|
+
panel.style.padding = '10px';
|
|
40
|
+
panel.style.border = '1px solid #d9dee8';
|
|
41
|
+
panel.style.borderRadius = '10px';
|
|
42
|
+
panel.style.background = 'rgba(255,255,255,0.97)';
|
|
43
|
+
panel.style.boxShadow = '0 6px 20px rgba(0,0,0,0.12)';
|
|
44
|
+
panel.style.fontFamily = 'Segoe UI, Arial, sans-serif';
|
|
45
|
+
panel.style.fontSize = '12px';
|
|
46
|
+
panel.style.color = '#1f2937';
|
|
47
|
+
|
|
48
|
+
const title = document.createElement('div');
|
|
49
|
+
title.textContent = 'Cross Grid Debug';
|
|
50
|
+
title.style.fontWeight = '700';
|
|
51
|
+
title.style.marginBottom = '8px';
|
|
52
|
+
panel.appendChild(title);
|
|
53
|
+
|
|
54
|
+
this.infoEl = document.createElement('div');
|
|
55
|
+
this.infoEl.style.marginBottom = '8px';
|
|
56
|
+
this.infoEl.style.color = '#475569';
|
|
57
|
+
this.infoEl.textContent = 'zoom: -, checkpoint: -, grid: -';
|
|
58
|
+
panel.appendChild(this.infoEl);
|
|
59
|
+
|
|
60
|
+
const makeRow = (labelText, inputEl) => {
|
|
61
|
+
const row = document.createElement('div');
|
|
62
|
+
row.style.marginBottom = '8px';
|
|
63
|
+
const label = document.createElement('label');
|
|
64
|
+
label.textContent = labelText;
|
|
65
|
+
label.style.display = 'block';
|
|
66
|
+
label.style.marginBottom = '4px';
|
|
67
|
+
row.appendChild(label);
|
|
68
|
+
row.appendChild(inputEl);
|
|
69
|
+
panel.appendChild(row);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
this.crossSizeRange = document.createElement('input');
|
|
73
|
+
this.crossSizeRange.type = 'range';
|
|
74
|
+
this.crossSizeRange.min = '1';
|
|
75
|
+
this.crossSizeRange.max = '12';
|
|
76
|
+
this.crossSizeRange.step = '1';
|
|
77
|
+
this.crossSizeRange.style.width = '100%';
|
|
78
|
+
makeRow('cross half size', this.crossSizeRange);
|
|
79
|
+
|
|
80
|
+
this.crossSizeValue = document.createElement('div');
|
|
81
|
+
this.crossSizeValue.style.marginTop = '-6px';
|
|
82
|
+
this.crossSizeValue.style.marginBottom = '8px';
|
|
83
|
+
this.crossSizeValue.style.color = '#64748b';
|
|
84
|
+
panel.appendChild(this.crossSizeValue);
|
|
85
|
+
|
|
86
|
+
this.spacingRange = document.createElement('input');
|
|
87
|
+
this.spacingRange.type = 'range';
|
|
88
|
+
this.spacingRange.min = '8';
|
|
89
|
+
this.spacingRange.max = '140';
|
|
90
|
+
this.spacingRange.step = '1';
|
|
91
|
+
this.spacingRange.style.width = '100%';
|
|
92
|
+
makeRow('spacing', this.spacingRange);
|
|
93
|
+
|
|
94
|
+
this.spacingValue = document.createElement('div');
|
|
95
|
+
this.spacingValue.style.marginTop = '-6px';
|
|
96
|
+
this.spacingValue.style.marginBottom = '8px';
|
|
97
|
+
this.spacingValue.style.color = '#64748b';
|
|
98
|
+
panel.appendChild(this.spacingValue);
|
|
99
|
+
|
|
100
|
+
this.colorInput = document.createElement('input');
|
|
101
|
+
this.colorInput.type = 'color';
|
|
102
|
+
this.colorInput.style.width = '100%';
|
|
103
|
+
this.colorInput.style.height = '30px';
|
|
104
|
+
this.colorInput.style.border = '1px solid #d1d5db';
|
|
105
|
+
this.colorInput.style.borderRadius = '6px';
|
|
106
|
+
makeRow('color', this.colorInput);
|
|
107
|
+
|
|
108
|
+
const controls = document.createElement('div');
|
|
109
|
+
controls.style.display = 'flex';
|
|
110
|
+
controls.style.gap = '6px';
|
|
111
|
+
controls.style.marginTop = '6px';
|
|
112
|
+
|
|
113
|
+
this.copyBtn = document.createElement('button');
|
|
114
|
+
this.copyBtn.textContent = 'Copy checkpoint';
|
|
115
|
+
this.copyBtn.style.flex = '1';
|
|
116
|
+
this.copyBtn.style.padding = '6px 8px';
|
|
117
|
+
this.copyBtn.style.border = '1px solid #cbd5e1';
|
|
118
|
+
this.copyBtn.style.borderRadius = '6px';
|
|
119
|
+
this.copyBtn.style.cursor = 'pointer';
|
|
120
|
+
this.copyBtn.style.background = '#f8fafc';
|
|
121
|
+
controls.appendChild(this.copyBtn);
|
|
122
|
+
|
|
123
|
+
this.enableCrossBtn = document.createElement('button');
|
|
124
|
+
this.enableCrossBtn.textContent = 'Set Cross';
|
|
125
|
+
this.enableCrossBtn.style.flex = '0 0 80px';
|
|
126
|
+
this.enableCrossBtn.style.padding = '6px 8px';
|
|
127
|
+
this.enableCrossBtn.style.border = '1px solid #93c5fd';
|
|
128
|
+
this.enableCrossBtn.style.borderRadius = '6px';
|
|
129
|
+
this.enableCrossBtn.style.cursor = 'pointer';
|
|
130
|
+
this.enableCrossBtn.style.background = '#dbeafe';
|
|
131
|
+
controls.appendChild(this.enableCrossBtn);
|
|
132
|
+
|
|
133
|
+
panel.appendChild(controls);
|
|
134
|
+
this.element = panel;
|
|
135
|
+
this.container.appendChild(panel);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_attach() {
|
|
139
|
+
this._onCrossSizeInput = () => this._applyPatch({ crossHalfSize: Number(this.crossSizeRange.value) });
|
|
140
|
+
this._onSpacingInput = () => this._applyPatch({ spacing: Number(this.spacingRange.value) });
|
|
141
|
+
this._onColorInput = () => {
|
|
142
|
+
const color = hexToInt(this.colorInput.value);
|
|
143
|
+
if (color != null) this._applyPatch({ color });
|
|
144
|
+
};
|
|
145
|
+
this._onCopyClick = async () => {
|
|
146
|
+
if (!this._activeCheckpoint) return;
|
|
147
|
+
const worldScale = this._getWorldScale();
|
|
148
|
+
const payload = {
|
|
149
|
+
zoomPercent: this._activeCheckpoint.zoomPercent,
|
|
150
|
+
zoomLabel: `${Math.round((worldScale || 1) * 100)}%`,
|
|
151
|
+
gridType: 'cross',
|
|
152
|
+
spacing: this._activeCheckpoint.spacing,
|
|
153
|
+
crossHalfSize: this._activeCheckpoint.crossHalfSize,
|
|
154
|
+
color: `0x${Number(this._activeCheckpoint.color || 0).toString(16).toUpperCase().padStart(6, '0')}`,
|
|
155
|
+
};
|
|
156
|
+
const text = JSON.stringify(payload);
|
|
157
|
+
try {
|
|
158
|
+
await navigator.clipboard.writeText(text);
|
|
159
|
+
this.copyBtn.textContent = 'Copied';
|
|
160
|
+
setTimeout(() => { this.copyBtn.textContent = 'Copy checkpoint'; }, 1000);
|
|
161
|
+
} catch (_) {
|
|
162
|
+
this.copyBtn.textContent = 'Copy failed';
|
|
163
|
+
setTimeout(() => { this.copyBtn.textContent = 'Copy checkpoint'; }, 1000);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
this._onEnableCrossClick = () => {
|
|
167
|
+
const eventBus = this.core?.eventBus;
|
|
168
|
+
if (eventBus) {
|
|
169
|
+
eventBus.emit(Events.UI.GridChange, { type: 'cross' });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
this.crossSizeRange.addEventListener('input', this._onCrossSizeInput);
|
|
175
|
+
this.spacingRange.addEventListener('input', this._onSpacingInput);
|
|
176
|
+
this.colorInput.addEventListener('input', this._onColorInput);
|
|
177
|
+
this.copyBtn.addEventListener('click', this._onCopyClick);
|
|
178
|
+
this.enableCrossBtn.addEventListener('click', this._onEnableCrossClick);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
_startPolling() {
|
|
182
|
+
this._pollTimer = setInterval(() => {
|
|
183
|
+
const scale = this._getWorldScale();
|
|
184
|
+
if (!Number.isFinite(scale)) return;
|
|
185
|
+
const checkpoint = getCrossCheckpointForZoom(scale);
|
|
186
|
+
if (!checkpoint) return;
|
|
187
|
+
const grid = this._getGrid();
|
|
188
|
+
this.infoEl.textContent = `zoom: ${Math.round(scale * 100)}%, checkpoint: ${checkpoint.zoomPercent}%, grid: ${grid?.type || 'none'}`;
|
|
189
|
+
|
|
190
|
+
const changed = !this._activeCheckpoint || this._activeCheckpoint.zoomPercent !== checkpoint.zoomPercent;
|
|
191
|
+
this._activeCheckpoint = checkpoint;
|
|
192
|
+
if (changed && !this._isApplying) {
|
|
193
|
+
this.crossSizeRange.value = String(checkpoint.crossHalfSize);
|
|
194
|
+
this.spacingRange.value = String(checkpoint.spacing);
|
|
195
|
+
this.colorInput.value = intToHex(checkpoint.color);
|
|
196
|
+
}
|
|
197
|
+
this.crossSizeValue.textContent = `cross half size: ${this.crossSizeRange.value}px`;
|
|
198
|
+
this.spacingValue.textContent = `spacing: ${this.spacingRange.value}px`;
|
|
199
|
+
}, 150);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_getWorldScale() {
|
|
203
|
+
const world = this.core?.pixi?.worldLayer || this.core?.pixi?.app?.stage;
|
|
204
|
+
return world?.scale?.x ?? null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_getGrid() {
|
|
208
|
+
return this.core?.boardService?.grid || null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
_refreshGridVisual() {
|
|
212
|
+
const boardService = this.core?.boardService;
|
|
213
|
+
if (boardService && typeof boardService.refreshGridViewport === 'function') {
|
|
214
|
+
boardService.refreshGridViewport();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const grid = this._getGrid();
|
|
218
|
+
if (grid && typeof grid.updateVisual === 'function') {
|
|
219
|
+
grid.updateVisual();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_applyPatch(patch) {
|
|
224
|
+
if (!this._activeCheckpoint) return;
|
|
225
|
+
this._isApplying = true;
|
|
226
|
+
const updated = updateCrossCheckpoint(this._activeCheckpoint.zoomPercent, patch);
|
|
227
|
+
if (updated) {
|
|
228
|
+
this._activeCheckpoint = updated;
|
|
229
|
+
this.crossSizeRange.value = String(updated.crossHalfSize);
|
|
230
|
+
this.spacingRange.value = String(updated.spacing);
|
|
231
|
+
this.colorInput.value = intToHex(updated.color);
|
|
232
|
+
this.crossSizeValue.textContent = `cross half size: ${this.crossSizeRange.value}px`;
|
|
233
|
+
this.spacingValue.textContent = `spacing: ${this.spacingRange.value}px`;
|
|
234
|
+
this._refreshGridVisual();
|
|
235
|
+
}
|
|
236
|
+
this._isApplying = false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
destroy() {
|
|
240
|
+
if (this._pollTimer) {
|
|
241
|
+
clearInterval(this._pollTimer);
|
|
242
|
+
this._pollTimer = null;
|
|
243
|
+
}
|
|
244
|
+
this.crossSizeRange?.removeEventListener('input', this._onCrossSizeInput);
|
|
245
|
+
this.spacingRange?.removeEventListener('input', this._onSpacingInput);
|
|
246
|
+
this.colorInput?.removeEventListener('input', this._onColorInput);
|
|
247
|
+
this.copyBtn?.removeEventListener('click', this._onCopyClick);
|
|
248
|
+
this.enableCrossBtn?.removeEventListener('click', this._onEnableCrossClick);
|
|
249
|
+
if (this.element) this.element.remove();
|
|
250
|
+
this.element = null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
package/src/ui/HtmlTextLayer.js
CHANGED
|
@@ -199,7 +199,7 @@ export class HtmlTextLayer {
|
|
|
199
199
|
el.dataset.id = objectId;
|
|
200
200
|
// Получаем свойства из properties объекта
|
|
201
201
|
const fontFamily = objectData.properties?.fontFamily || objectData.fontFamily || 'Caveat, Arial, cursive';
|
|
202
|
-
const color = objectData.color || objectData.properties?.color || '#000000';
|
|
202
|
+
const color = objectData.color || objectData.properties?.color || objectData.properties?.textColor || '#000000';
|
|
203
203
|
const backgroundColor = objectData.backgroundColor || objectData.properties?.backgroundColor || 'transparent';
|
|
204
204
|
|
|
205
205
|
// Базовый line-height исходя из стартового размера шрифта
|
|
@@ -258,8 +258,8 @@ export class TextPropertiesPanel {
|
|
|
258
258
|
const finalX = Math.max(10, Math.min(panelX, containerRect.width - this.panel.offsetWidth - 10));
|
|
259
259
|
const finalY = Math.max(10, panelY);
|
|
260
260
|
|
|
261
|
-
this.panel.style.left = `${finalX}px`;
|
|
262
|
-
this.panel.style.top = `${finalY}px`;
|
|
261
|
+
this.panel.style.left = `${Math.round(finalX)}px`;
|
|
262
|
+
this.panel.style.top = `${Math.round(finalY)}px`;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
_onDocMouseDown(event) {
|
|
@@ -15,6 +15,7 @@ export class GroupSelectionHandlesController {
|
|
|
15
15
|
height: preview.startBounds.height,
|
|
16
16
|
}, '__group__', {
|
|
17
17
|
rotation: preview.angle || 0,
|
|
18
|
+
selectionIds: ids,
|
|
18
19
|
});
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
@@ -24,6 +25,8 @@ export class GroupSelectionHandlesController {
|
|
|
24
25
|
this.host.hide();
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
|
-
this.host._showBounds(worldBounds, '__group__'
|
|
28
|
+
this.host._showBounds(worldBounds, '__group__', {
|
|
29
|
+
selectionIds: ids,
|
|
30
|
+
});
|
|
28
31
|
}
|
|
29
32
|
}
|