@sequent-org/moodboard 1.2.118 → 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 +7 -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 -1765
  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 -976
  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,184 @@
1
+ import { Events } from '../../../core/events/Events.js';
2
+ import {
3
+ createFileNameEditorInput,
4
+ createFileNameEditorWrapper,
5
+ } from './InlineEditorDomFactory.js';
6
+ import { toScreenWithResolution } from './InlineEditorPositioningService.js';
7
+
8
+ export function openFileNameEditor(object, create = false) {
9
+ // Проверяем структуру объекта и извлекаем данные
10
+ let objectId, position, properties;
11
+
12
+ if (create) {
13
+ // Для создания нового объекта - данные в object.object
14
+ const objData = object.object || object;
15
+ objectId = objData.id || null;
16
+ position = objData.position;
17
+ properties = objData.properties || {};
18
+ } else {
19
+ // Для редактирования существующего объекта - данные в корне
20
+ objectId = object.id;
21
+ position = object.position;
22
+ properties = object.properties || {};
23
+ }
24
+
25
+ const fileName = properties.fileName || 'Untitled';
26
+
27
+ // Проверяем, что position существует
28
+ if (!position) {
29
+ console.error('❌ SelectTool: position is undefined in _openFileNameEditor', { object, create });
30
+ return;
31
+ }
32
+
33
+ // Закрываем предыдущий редактор, если он открыт
34
+ if (this.textEditor.active) {
35
+ if (this.textEditor.objectType === 'file') {
36
+ this._closeFileNameEditor(true);
37
+ } else {
38
+ this._closeTextEditor(true);
39
+ }
40
+ }
41
+
42
+ // Если это редактирование существующего объекта, получаем его данные
43
+ if (!create && objectId) {
44
+ const posData = { objectId, position: null };
45
+ const pixiReq = { objectId, pixiObject: null };
46
+ this.eventBus.emit(Events.Tool.GetObjectPosition, posData);
47
+ this.eventBus.emit(Events.Tool.GetObjectPixi, pixiReq);
48
+
49
+ // Обновляем данные из полученной информации
50
+ if (posData.position) position = posData.position;
51
+
52
+ // Скрываем текст файла на время редактирования
53
+ if (pixiReq.pixiObject && pixiReq.pixiObject._mb && pixiReq.pixiObject._mb.instance) {
54
+ const fileInstance = pixiReq.pixiObject._mb.instance;
55
+ if (typeof fileInstance.hideText === 'function') {
56
+ fileInstance.hideText();
57
+ }
58
+ }
59
+ }
60
+
61
+ // Создаем wrapper для input
62
+ const wrapper = createFileNameEditorWrapper();
63
+
64
+ // Создаем input для редактирования названия
65
+ const input = createFileNameEditorInput(fileName);
66
+
67
+ wrapper.appendChild(input);
68
+ document.body.appendChild(wrapper);
69
+
70
+ // Позиционируем редактор (аналогично _openTextEditor)
71
+ const toScreen = (wx, wy) => toScreenWithResolution(this.textEditor.world || (this.app?.stage), this.app, wx, wy);
72
+ const screenPos = toScreen(position.x, position.y);
73
+
74
+ // Получаем размеры файлового объекта для точного позиционирования
75
+ let fileWidth = 120;
76
+ let fileHeight = 140;
77
+
78
+ if (objectId) {
79
+ const sizeData = { objectId, size: null };
80
+ this.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
81
+ if (sizeData.size) {
82
+ fileWidth = sizeData.size.width;
83
+ fileHeight = sizeData.size.height;
84
+ }
85
+ }
86
+
87
+ // Позиционируем редактор в нижней части файла (где название)
88
+ // В FileObject название находится в позиции y = height - 40
89
+ const nameY = fileHeight - 40;
90
+ const centerX = fileWidth / 2;
91
+
92
+ wrapper.style.left = `${screenPos.x + centerX - 60}px`; // Центрируем относительно файла
93
+ wrapper.style.top = `${screenPos.y + nameY}px`; // Позиционируем на уровне названия
94
+
95
+ // Сохраняем состояние редактора
96
+ this.textEditor = {
97
+ active: true,
98
+ objectId: objectId,
99
+ textarea: input,
100
+ wrapper: wrapper,
101
+ position: position,
102
+ properties: properties,
103
+ objectType: 'file',
104
+ isResizing: false
105
+ };
106
+
107
+ // Фокусируем и выделяем весь текст
108
+ input.focus();
109
+ input.select();
110
+
111
+ // Функция завершения редактирования
112
+ const finalize = (commit) => {
113
+ this._closeFileNameEditor(commit);
114
+ };
115
+
116
+ // Обработчики событий
117
+ input.addEventListener('blur', () => finalize(true));
118
+ input.addEventListener('keydown', (e) => {
119
+ if (e.key === 'Enter') {
120
+ e.preventDefault();
121
+ finalize(true);
122
+ } else if (e.key === 'Escape') {
123
+ e.preventDefault();
124
+ finalize(false);
125
+ }
126
+ });
127
+ }
128
+
129
+ export function closeFileNameEditor(commit) {
130
+ // Проверяем, что редактор существует и не закрыт
131
+ if (!this.textEditor || !this.textEditor.textarea || this.textEditor.closing) {
132
+ return;
133
+ }
134
+
135
+ // Устанавливаем флаг закрытия, чтобы избежать повторных вызовов
136
+ this.textEditor.closing = true;
137
+
138
+ const input = this.textEditor.textarea;
139
+ const value = input.value.trim();
140
+ const commitValue = commit && value.length > 0;
141
+ const objectId = this.textEditor.objectId;
142
+
143
+ // Убираем wrapper из DOM
144
+ if (this.textEditor.wrapper && this.textEditor.wrapper.parentNode) {
145
+ this.textEditor.wrapper.remove();
146
+ }
147
+
148
+ // Показываем обратно текст файла
149
+ if (objectId) {
150
+ const pixiReq = { objectId, pixiObject: null };
151
+ this.eventBus.emit(Events.Tool.GetObjectPixi, pixiReq);
152
+
153
+ if (pixiReq.pixiObject && pixiReq.pixiObject._mb && pixiReq.pixiObject._mb.instance) {
154
+ const fileInstance = pixiReq.pixiObject._mb.instance;
155
+ if (typeof fileInstance.showText === 'function') {
156
+ fileInstance.showText();
157
+ }
158
+
159
+ // Применяем изменения если нужно
160
+ if (commitValue && value !== this.textEditor.properties.fileName) {
161
+ // Создаем команду изменения названия файла
162
+ const oldName = this.textEditor.properties.fileName || 'Untitled';
163
+ this.eventBus.emit(Events.Object.FileNameChange, {
164
+ objectId: objectId,
165
+ oldName: oldName,
166
+ newName: value
167
+ });
168
+ }
169
+ }
170
+ }
171
+
172
+ // Сбрасываем состояние редактора
173
+ this.textEditor = {
174
+ active: false,
175
+ objectId: null,
176
+ textarea: null,
177
+ wrapper: null,
178
+ world: null,
179
+ position: null,
180
+ properties: null,
181
+ objectType: 'text',
182
+ isResizing: false
183
+ };
184
+ }
@@ -0,0 +1,102 @@
1
+ import * as PIXI from 'pixi.js';
2
+ import { Events } from '../../../core/events/Events.js';
3
+
4
+ export function hitTest(x, y) {
5
+ // Проверяем, что инструмент не уничтожен
6
+ if (this.destroyed) {
7
+ return { type: 'empty' };
8
+ }
9
+
10
+ // Сначала проверяем ручки изменения размера (они имеют приоритет)
11
+ if (this.resizeHandles) {
12
+ const pixiObjectAtPoint = this.getPixiObjectAt(x, y);
13
+ const handleInfo = this.resizeHandles.getHandleInfo(pixiObjectAtPoint);
14
+ if (handleInfo) {
15
+ // Определяем тип ручки
16
+ const hitType = handleInfo.type === 'rotate' ? 'rotate-handle' : 'resize-handle';
17
+
18
+ return {
19
+ type: hitType,
20
+ handle: handleInfo.type,
21
+ object: handleInfo.targetObjectId,
22
+ pixiObject: handleInfo.handle
23
+ };
24
+ }
25
+ }
26
+
27
+ // Получаем объекты из системы через событие
28
+ const hitTestData = { x, y, result: null };
29
+ this.emit(Events.Tool.HitTest, hitTestData);
30
+
31
+ if (hitTestData.result && hitTestData.result.object) {
32
+ return hitTestData.result;
33
+ }
34
+
35
+ return { type: 'empty' };
36
+ }
37
+
38
+ export function getPixiObjectAt(x, y) {
39
+ // Проверяем, что инструмент не уничтожен
40
+ if (this.destroyed) {
41
+ return null;
42
+ }
43
+
44
+ if (!this.resizeHandles || !this.resizeHandles.app || !this.resizeHandles.container) return null;
45
+
46
+ const point = new PIXI.Point(x, y);
47
+
48
+ // Сначала ищем в контейнере ручек (приоритет)
49
+ if (this.resizeHandles.container && this.resizeHandles.container.visible) {
50
+ const container = this.resizeHandles.container;
51
+ if (!container || !container.children) return null;
52
+
53
+ for (let i = container.children.length - 1; i >= 0; i--) {
54
+ const child = container.children[i];
55
+
56
+ // Проверяем обычные объекты
57
+ if (child && child.containsPoint && typeof child.containsPoint === 'function') {
58
+ try {
59
+ if (child.containsPoint(point)) {
60
+ return child;
61
+ }
62
+ } catch (_) {
63
+ // Игнорируем ошибки containsPoint
64
+ }
65
+ }
66
+
67
+ // Специальная проверка для контейнеров (ручка вращения)
68
+ if (child instanceof PIXI.Container && child.children && child.children.length > 0) {
69
+ // Проверяем границы контейнера
70
+ try {
71
+ const bounds = child.getBounds();
72
+ if (bounds && point.x >= bounds.x && point.x <= bounds.x + bounds.width &&
73
+ point.y >= bounds.y && point.y <= bounds.y + bounds.height) {
74
+ return child;
75
+ }
76
+ } catch (_) {
77
+ // Игнорируем ошибки getBounds
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ // Затем ищем в основной сцене
84
+ const stage = this.resizeHandles.app.stage;
85
+ if (!stage || !stage.children) return null;
86
+
87
+ for (let i = stage.children.length - 1; i >= 0; i--) {
88
+ const child = stage.children[i];
89
+ if (this.resizeHandles.container && child && child !== this.resizeHandles.container &&
90
+ child.containsPoint && typeof child.containsPoint === 'function') {
91
+ try {
92
+ if (child.containsPoint(point)) {
93
+ return child;
94
+ }
95
+ } catch (_) {
96
+ // Игнорируем ошибки containsPoint
97
+ }
98
+ }
99
+ }
100
+
101
+ return null;
102
+ }
@@ -0,0 +1,24 @@
1
+ import {
2
+ closeTextEditor as closeTextEditorViaController,
3
+ openTextEditor as openTextEditorViaController,
4
+ } from './TextInlineEditorController.js';
5
+ import {
6
+ closeFileNameEditor as closeFileNameEditorViaController,
7
+ openFileNameEditor as openFileNameEditorViaController,
8
+ } from './FileNameInlineEditorController.js';
9
+
10
+ export function openTextEditor(object, create = false) {
11
+ return openTextEditorViaController.call(this, object, create);
12
+ }
13
+
14
+ export function openFileNameEditor(object, create = false) {
15
+ return openFileNameEditorViaController.call(this, object, create);
16
+ }
17
+
18
+ export function closeFileNameEditor(commit) {
19
+ return closeFileNameEditorViaController.call(this, commit);
20
+ }
21
+
22
+ export function closeTextEditor(commit) {
23
+ return closeTextEditorViaController.call(this, commit);
24
+ }
@@ -0,0 +1,50 @@
1
+ export function createTextEditorWrapper() {
2
+ const wrapper = document.createElement('div');
3
+ wrapper.className = 'moodboard-text-editor';
4
+ return wrapper;
5
+ }
6
+
7
+ export function createTextEditorTextarea(content) {
8
+ const textarea = document.createElement('textarea');
9
+ textarea.className = 'moodboard-text-input';
10
+ textarea.value = content || '';
11
+ textarea.placeholder = 'Напишите что-нибудь';
12
+ return textarea;
13
+ }
14
+
15
+ export function createFileNameEditorWrapper() {
16
+ const wrapper = document.createElement('div');
17
+ wrapper.className = 'moodboard-file-name-editor';
18
+ wrapper.style.cssText = `
19
+ position: absolute;
20
+ z-index: 1000;
21
+ background: white;
22
+ border: 2px solid #2563eb;
23
+ border-radius: 6px;
24
+ padding: 6px 8px;
25
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
26
+ min-width: 140px;
27
+ max-width: 200px;
28
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
29
+ `;
30
+ return wrapper;
31
+ }
32
+
33
+ export function createFileNameEditorInput(fileName) {
34
+ const input = document.createElement('input');
35
+ input.type = 'text';
36
+ input.value = fileName;
37
+ input.style.cssText = `
38
+ border: none;
39
+ outline: none;
40
+ background: transparent;
41
+ font-family: inherit;
42
+ font-size: 12px;
43
+ text-align: center;
44
+ width: 100%;
45
+ padding: 2px 4px;
46
+ color: #1f2937;
47
+ font-weight: 500;
48
+ `;
49
+ return input;
50
+ }
@@ -0,0 +1,14 @@
1
+ export function registerEditorListeners(eventBus, listeners) {
2
+ listeners.forEach(([eventName, handler]) => {
3
+ eventBus.on(eventName, handler);
4
+ });
5
+ return listeners;
6
+ }
7
+
8
+ export function unregisterEditorListeners(eventBus, listeners) {
9
+ listeners.forEach(([eventName, handler]) => {
10
+ try {
11
+ eventBus.off(eventName, handler);
12
+ } catch (_) {}
13
+ });
14
+ }
@@ -0,0 +1,25 @@
1
+ import * as PIXI from 'pixi.js';
2
+
3
+ export function toScreenWithContainerOffset(worldLayer, view, wx, wy) {
4
+ if (!worldLayer || !view || !view.parentElement) return { x: wx, y: wy };
5
+
6
+ const containerRect = view.parentElement.getBoundingClientRect();
7
+ const viewRect = view.getBoundingClientRect();
8
+ const offsetLeft = viewRect.left - containerRect.left;
9
+ const offsetTop = viewRect.top - containerRect.top;
10
+ const global = worldLayer.toGlobal(new PIXI.Point(wx, wy));
11
+
12
+ return {
13
+ x: offsetLeft + global.x,
14
+ y: offsetTop + global.y,
15
+ };
16
+ }
17
+
18
+ export function toScreenWithResolution(worldLayer, app, wx, wy) {
19
+ if (!worldLayer) return { x: wx, y: wy };
20
+
21
+ const global = worldLayer.toGlobal(new PIXI.Point(wx, wy));
22
+ const view = app?.view || document.querySelector('canvas');
23
+ const viewRes = (app?.renderer?.resolution) || (view && view.width && view.clientWidth ? (view.width / view.clientWidth) : 1);
24
+ return { x: global.x / viewRes, y: global.y / viewRes };
25
+ }
@@ -0,0 +1,113 @@
1
+ import { Events } from '../../../core/events/Events.js';
2
+ import {
3
+ createNoteEditorUpdater,
4
+ registerNoteEditorSync,
5
+ } from './TextEditorSyncService.js';
6
+
7
+ export function setupNoteInlineEditor(controller, params) {
8
+ const {
9
+ objectId,
10
+ position,
11
+ initialSize,
12
+ view,
13
+ screenPos,
14
+ textarea,
15
+ wrapper,
16
+ computeLineHeightPx,
17
+ effectiveFontPx,
18
+ toScreen,
19
+ } = params;
20
+
21
+ // Получаем актуальные размеры записки
22
+ let noteWidth = 160;
23
+ let noteHeight = 100;
24
+
25
+ if (initialSize) {
26
+ noteWidth = initialSize.width;
27
+ noteHeight = initialSize.height;
28
+ } else if (objectId) {
29
+ // Если размер не передан, пытаемся получить его из объекта
30
+ const sizeData = { objectId, size: null };
31
+ controller.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
32
+ if (sizeData.size) {
33
+ noteWidth = sizeData.size.width;
34
+ noteHeight = sizeData.size.height;
35
+ }
36
+ }
37
+
38
+ // Текст у записки центрирован по обеим осям; textarea тоже центрируем
39
+ const horizontalPadding = 16; // немного больше, чем раньше
40
+ // Преобразуем мировые размеры/отступы в CSS-пиксели с учётом текущего зума
41
+ const viewResLocal = (controller.app?.renderer?.resolution) || (view.width && view.clientWidth ? (view.width / view.clientWidth) : 1);
42
+ const worldLayerRefForCss = controller.textEditor.world || (controller.app?.stage);
43
+ const sForCss = worldLayerRefForCss?.scale?.x || 1;
44
+ const sCssLocal = sForCss / viewResLocal;
45
+ const editorWidthWorld = Math.min(360, Math.max(1, noteWidth - (horizontalPadding * 2)));
46
+ const editorHeightWorld = Math.min(180, Math.max(1, noteHeight - (horizontalPadding * 2)));
47
+ const editorWidthPx = Math.max(1, Math.round(editorWidthWorld * sCssLocal));
48
+ const editorHeightPx = Math.max(1, Math.round(editorHeightWorld * sCssLocal));
49
+ const textCenterXWorld = noteWidth / 2;
50
+ const textCenterYWorld = noteHeight / 2;
51
+ const editorLeftWorld = textCenterXWorld - (editorWidthWorld / 2);
52
+ const editorTopWorld = textCenterYWorld - (editorHeightWorld / 2);
53
+ wrapper.style.left = `${Math.round(screenPos.x + editorLeftWorld * sCssLocal)}px`;
54
+ wrapper.style.top = `${Math.round(screenPos.y + editorTopWorld * sCssLocal)}px`;
55
+ // Устанавливаем размеры редактора (центрируем по контенту) в CSS-пикселях
56
+ textarea.style.width = `${editorWidthPx}px`;
57
+ textarea.style.height = `${editorHeightPx}px`;
58
+ wrapper.style.width = `${editorWidthPx}px`;
59
+ wrapper.style.height = `${editorHeightPx}px`;
60
+
61
+ // Для записок: авто-ресайз редактора под содержимое с сохранением центрирования
62
+ textarea.style.textAlign = 'center';
63
+ const maxEditorWidthPx = Math.max(1, Math.round((noteWidth - (horizontalPadding * 2)) * sCssLocal));
64
+ const maxEditorHeightPx = Math.max(1, Math.round((noteHeight - (horizontalPadding * 2)) * sCssLocal));
65
+ const MIN_NOTE_EDITOR_W = 20;
66
+ const MIN_NOTE_EDITOR_H = Math.max(1, computeLineHeightPx(effectiveFontPx));
67
+
68
+ const autoSizeNote = () => {
69
+ // Сначала сбрасываем размеры, чтобы измерить естественные
70
+ textarea.style.width = 'auto';
71
+ textarea.style.height = 'auto';
72
+
73
+ // Ширина по содержимому, но не шире границ записки (в CSS-пикселях)
74
+ const naturalW = Math.ceil(textarea.scrollWidth + 1);
75
+ const targetW = Math.min(maxEditorWidthPx, Math.max(MIN_NOTE_EDITOR_W, naturalW));
76
+ textarea.style.width = `${targetW}px`;
77
+ wrapper.style.width = `${targetW}px`;
78
+
79
+ // Высота по содержимому, c нижним пределом = одна строка
80
+ const computed = (typeof window !== 'undefined') ? window.getComputedStyle(textarea) : null;
81
+ const lineH = (computed ? parseFloat(computed.lineHeight) : computeLineHeightPx(effectiveFontPx));
82
+ const naturalH = Math.ceil(textarea.scrollHeight);
83
+ const targetH = Math.min(maxEditorHeightPx, Math.max(MIN_NOTE_EDITOR_H, naturalH));
84
+ textarea.style.height = `${targetH}px`;
85
+ wrapper.style.height = `${targetH}px`;
86
+
87
+ // Центрируем wrapper внутри записки после смены размеров (в CSS-пикселях)
88
+ const left = Math.round(screenPos.x + (noteWidth * sCssLocal) / 2 - (targetW / 2));
89
+ const top = Math.round(screenPos.y + (noteHeight * sCssLocal) / 2 - (targetH / 2));
90
+ wrapper.style.left = `${left}px`;
91
+ wrapper.style.top = `${top}px`;
92
+ };
93
+ // Первый вызов — синхронизировать с текущим содержимым
94
+ autoSizeNote();
95
+
96
+ // Динамическое обновление позиции/размера редактора при зуме/панорамировании/трансформациях
97
+ const updateNoteEditor = createNoteEditorUpdater(controller, {
98
+ objectId,
99
+ position,
100
+ noteWidth,
101
+ noteHeight,
102
+ view,
103
+ textarea,
104
+ wrapper,
105
+ horizontalPadding,
106
+ computeLineHeightPx,
107
+ effectiveFontPx,
108
+ toScreen,
109
+ });
110
+ registerNoteEditorSync(controller, { objectId, updateNoteEditor });
111
+
112
+ return { updateNoteEditor };
113
+ }