@sequent-org/moodboard 1.4.32 → 1.4.33
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 +5 -1
- package/src/assets/fonts/inter/inter-cyrillic-400-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-cyrillic-500-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-latin-400-normal.woff2 +0 -0
- package/src/assets/fonts/inter/inter-latin-500-normal.woff2 +0 -0
- package/src/assets/icons/attachments.svg +3 -1
- package/src/assets/icons/comments.svg +2 -2
- package/src/assets/icons/connector.svg +6 -0
- package/src/assets/icons/emoji.svg +6 -1
- package/src/assets/icons/frame.svg +4 -1
- package/src/assets/icons/image.svg +5 -1
- package/src/assets/icons/laser.svg +1 -0
- package/src/assets/icons/lasso.svg +5 -0
- package/src/assets/icons/mindmap.svg +10 -2
- package/src/assets/icons/note.svg +4 -1
- package/src/assets/icons/pan.svg +5 -2
- package/src/assets/icons/pencil.svg +4 -1
- package/src/assets/icons/reactions.svg +5 -0
- package/src/assets/icons/redo.svg +3 -2
- package/src/assets/icons/select.svg +2 -8
- package/src/assets/icons/shapes.svg +5 -1
- package/src/assets/icons/text-add.svg +15 -1
- package/src/assets/icons/undo.svg +3 -2
- package/src/assets/reactions/1f44d.svg +20 -0
- package/src/assets/reactions/1f44e.svg +20 -0
- package/src/assets/reactions/2705.svg +20 -0
- package/src/assets/reactions/274c.svg +19 -0
- package/src/assets/reactions/2753.svg +20 -0
- package/src/assets/reactions/2764.svg +22 -0
- package/src/assets/reactions/2b50.svg +19 -0
- package/src/assets/reactions/plus-one.svg +25 -0
- package/src/core/PixiEngine.js +23 -0
- package/src/core/bootstrap/CoreInitializer.js +43 -0
- package/src/core/commands/GroupDeleteCommand.js +13 -1
- package/src/core/commands/UpdateShapeStyleCommand.js +121 -0
- package/src/core/commands/UpdateTextStyleCommand.js +17 -6
- package/src/core/commands/index.js +3 -0
- package/src/core/events/Events.js +22 -0
- package/src/core/flows/LayerAndViewportFlow.js +1 -0
- package/src/core/flows/ObjectLifecycleFlow.js +155 -7
- package/src/core/index.js +28 -1
- package/src/grid/CrossGridZoomPhases.js +3 -3
- package/src/initNoBundler.js +1 -1
- package/src/moodboard/DataManager.js +28 -0
- package/src/moodboard/MoodBoard.js +27 -0
- package/src/moodboard/bootstrap/MoodBoardInitializer.js +69 -1
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +22 -4
- package/src/moodboard/integration/MoodBoardEventBindings.js +5 -1
- package/src/moodboard/integration/MoodBoardLoadApi.js +10 -1
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +9 -0
- package/src/objects/ConnectorObject.js +2 -2
- package/src/objects/FrameObject.js +119 -59
- package/src/objects/ShapeObject.js +49 -74
- package/src/objects/shape/ShapeDrawer.js +210 -0
- package/src/services/ConnectorBindingResolver.js +112 -0
- package/src/services/ConnectorRouter.js +210 -0
- package/src/services/comments/CommentService.js +344 -0
- package/src/tools/object-tools/CommentTool.js +85 -0
- package/src/tools/object-tools/DrawingTool.js +110 -10
- package/src/tools/object-tools/LaserPointerTool.js +121 -0
- package/src/tools/object-tools/SelectTool.js +25 -1
- package/src/tools/object-tools/TextTool.js +6 -1
- package/src/tools/object-tools/connector/ConnectorDragController.js +50 -3
- package/src/tools/object-tools/connector/connectorGesture.js +33 -19
- package/src/tools/object-tools/placement/PlacementInputRouter.js +22 -1
- package/src/tools/object-tools/selection/BoxSelectController.js +24 -2
- package/src/tools/object-tools/selection/FrameTitleInlineEditorController.js +139 -0
- package/src/tools/object-tools/selection/InlineEditorController.js +12 -0
- package/src/tools/object-tools/selection/InlineEditorDomFactory.js +36 -0
- package/src/tools/object-tools/selection/LassoSelectController.js +125 -0
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +1 -0
- package/src/tools/object-tools/selection/SelectInputRouter.js +64 -5
- package/src/tools/object-tools/selection/SelectToolLifecycleController.js +11 -1
- package/src/tools/object-tools/selection/SelectToolSetup.js +13 -1
- package/src/tools/object-tools/selection/TextEditorInteractionController.js +46 -12
- package/src/tools/object-tools/selection/TextEditorSyncService.js +1 -0
- package/src/tools/object-tools/selection/TextInlineEditorController.js +65 -6
- package/src/ui/CommentPopover.js +6 -0
- package/src/ui/CommentsBar.js +91 -0
- package/src/ui/ConnectorPropertiesPanel.js +150 -0
- package/src/ui/ContextMenu.js +25 -0
- package/src/ui/DrawingPropertiesPanel.js +362 -0
- package/src/ui/FilePropertiesPanel.js +5 -0
- package/src/ui/FramePropertiesPanel.js +5 -0
- package/src/ui/HtmlTextLayer.js +246 -66
- package/src/ui/NotePropertiesPanel.js +6 -0
- package/src/ui/ShapePropertiesPanel.js +307 -0
- package/src/ui/TextPropertiesPanel.js +100 -1
- package/src/ui/Toolbar.js +25 -2
- package/src/ui/Topbar.js +2 -2
- package/src/ui/animation/HoverLiftController.js +6 -7
- package/src/ui/chat/ChatComposer.js +58 -7
- package/src/ui/chat/ChatWindow.js +60 -143
- package/src/ui/comments/CommentListPanel.js +213 -0
- package/src/ui/comments/CommentPinLayer.js +448 -0
- package/src/ui/comments/CommentThreadPopover.js +539 -0
- package/src/ui/comments/commentFormat.js +32 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelBindings.js +223 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelEventBridge.js +114 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelMapper.js +144 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelRenderer.js +447 -0
- package/src/ui/connector-properties/ConnectorPropertiesPanelState.js +61 -0
- package/src/ui/connectors/ConnectionAnchorsLayer.js +1 -0
- package/src/ui/connectors/ConnectorHandlesLayer.js +321 -0
- package/src/ui/connectors/ConnectorLabelLayer.js +334 -0
- package/src/ui/connectors/ConnectorLayer.js +264 -57
- package/src/ui/handles/HandlesDomRenderer.js +5 -13
- package/src/ui/handles/HandlesEventBridge.js +1 -0
- package/src/ui/handles/SingleSelectionHandlesController.js +4 -0
- package/src/ui/mindmap/MindmapCollapseLayer.js +1 -0
- package/src/ui/mindmap/MindmapConnectionLayer.js +1 -0
- package/src/ui/mindmap/MindmapHtmlTextLayer.js +6 -0
- package/src/ui/shape-properties/ShapePropertiesPanelDom.js +533 -0
- package/src/ui/shape-properties/ShapePropertiesPanelSync.js +132 -0
- package/src/ui/styles/chat.css +709 -19
- package/src/ui/styles/index.css +1 -0
- package/src/ui/styles/panels.css +112 -2
- package/src/ui/styles/shape-properties-panel.css +250 -0
- package/src/ui/styles/toolbar.css +7 -2
- package/src/ui/styles/topbar.css +1 -1
- package/src/ui/styles/workspace.css +257 -6
- package/src/ui/text-properties/TextFormatControls.js +88 -0
- package/src/ui/text-properties/TextListRenderer.js +137 -0
- package/src/ui/text-properties/TextPropertiesPanelBindings.js +27 -0
- package/src/ui/text-properties/TextPropertiesPanelEventBridge.js +3 -1
- package/src/ui/text-properties/TextPropertiesPanelMapper.js +56 -0
- package/src/ui/text-properties/TextPropertiesPanelRenderer.js +24 -0
- package/src/ui/text-properties/TextPropertiesPanelState.js +8 -0
- package/src/ui/toolbar/ReactionsPopupController.js +88 -0
- package/src/ui/toolbar/ToolbarActionRouter.js +71 -5
- package/src/ui/toolbar/ToolbarPopupsController.js +120 -118
- package/src/ui/toolbar/ToolbarRenderer.js +9 -1
- package/src/ui/toolbar/ToolbarStateController.js +4 -1
- package/src/utils/iconLoader.js +17 -16
- package/src/utils/markdown.js +14 -0
- package/src/utils/richText.js +125 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { Events } from '../core/events/Events.js';
|
|
2
|
+
import {
|
|
3
|
+
sep,
|
|
4
|
+
buildShapeGroup,
|
|
5
|
+
buildFillGroup,
|
|
6
|
+
buildBorderGroup,
|
|
7
|
+
buildRadiusGroup,
|
|
8
|
+
buildTextGroup,
|
|
9
|
+
} from './shape-properties/ShapePropertiesPanelDom.js';
|
|
10
|
+
import {
|
|
11
|
+
updateControlsFromObject,
|
|
12
|
+
updateBorderStyleBtns,
|
|
13
|
+
setAlign,
|
|
14
|
+
syncSwatches,
|
|
15
|
+
pixiToHex,
|
|
16
|
+
} from './shape-properties/ShapePropertiesPanelSync.js';
|
|
17
|
+
import './styles/shape-properties-panel.css';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ShapePropertiesPanel — плавающая панель свойств выделенной фигуры.
|
|
21
|
+
* Показывается только при одиночном выделении объекта типа 'shape'.
|
|
22
|
+
* Все изменения эмитятся через Events.Object.StateChanged.
|
|
23
|
+
* Undo/redo обеспечивает UpdateShapeStyleCommand (фаза 2).
|
|
24
|
+
*/
|
|
25
|
+
export class ShapePropertiesPanel {
|
|
26
|
+
constructor(eventBus, container, core = null) {
|
|
27
|
+
this.eventBus = eventBus;
|
|
28
|
+
this.container = container;
|
|
29
|
+
this.core = core;
|
|
30
|
+
this.panel = null;
|
|
31
|
+
this.currentId = null;
|
|
32
|
+
|
|
33
|
+
// Единственный открытый поповер (только один одновременно)
|
|
34
|
+
this._openPopoverEl = null;
|
|
35
|
+
this._boundDocClick = this._onDocumentClick.bind(this);
|
|
36
|
+
|
|
37
|
+
this._attachEvents();
|
|
38
|
+
this._createPanel();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Публичное API ──────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
updateFromSelection() {
|
|
44
|
+
const ids = this.core?.selectTool
|
|
45
|
+
? Array.from(this.core.selectTool.selectedObjects || [])
|
|
46
|
+
: [];
|
|
47
|
+
|
|
48
|
+
if (!ids || ids.length !== 1) { this.hide(); return; }
|
|
49
|
+
|
|
50
|
+
const id = ids[0];
|
|
51
|
+
if (this.currentId === id && this.panel && this.panel.style.display !== 'none') return;
|
|
52
|
+
|
|
53
|
+
const pixi = this.core?.pixi?.objects?.get ? this.core.pixi.objects.get(id) : null;
|
|
54
|
+
const isShape = !!(pixi && pixi._mb && pixi._mb.type === 'shape');
|
|
55
|
+
|
|
56
|
+
if (isShape) { this.showFor(id); } else { this.hide(); }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
showFor(objectId) {
|
|
60
|
+
this.currentId = objectId;
|
|
61
|
+
if (this.panel) {
|
|
62
|
+
this.panel.style.display = 'flex';
|
|
63
|
+
this.reposition();
|
|
64
|
+
this._updateControlsFromObject();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
hide() {
|
|
69
|
+
this.currentId = null;
|
|
70
|
+
if (this.panel) this.panel.style.display = 'none';
|
|
71
|
+
this._closePopover();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
reposition() {
|
|
75
|
+
if (!this.panel || !this.currentId || this.panel.style.display === 'none') return;
|
|
76
|
+
|
|
77
|
+
const ids = this.core?.selectTool
|
|
78
|
+
? Array.from(this.core.selectTool.selectedObjects || [])
|
|
79
|
+
: [];
|
|
80
|
+
if (!ids.includes(this.currentId)) { this.hide(); return; }
|
|
81
|
+
|
|
82
|
+
const posData = { objectId: this.currentId, position: null };
|
|
83
|
+
const sizeData = { objectId: this.currentId, size: null };
|
|
84
|
+
this.eventBus.emit(Events.Tool.GetObjectPosition, posData);
|
|
85
|
+
this.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
|
|
86
|
+
if (!posData.position || !sizeData.size) return;
|
|
87
|
+
|
|
88
|
+
const worldLayer = this.core?.pixi?.worldLayer;
|
|
89
|
+
const scale = worldLayer?.scale?.x || 1;
|
|
90
|
+
const worldX = worldLayer?.x || 0;
|
|
91
|
+
const worldY = worldLayer?.y || 0;
|
|
92
|
+
|
|
93
|
+
const screenX = posData.position.x * scale + worldX;
|
|
94
|
+
const screenY = posData.position.y * scale + worldY;
|
|
95
|
+
const objectWidth = sizeData.size.width * scale;
|
|
96
|
+
const objectHeight = sizeData.size.height * scale;
|
|
97
|
+
|
|
98
|
+
const panelW = this.panel.offsetWidth || 480;
|
|
99
|
+
const panelH = this.panel.offsetHeight || 80;
|
|
100
|
+
let panelX = screenX + (objectWidth / 2) - (panelW / 2);
|
|
101
|
+
let panelY = screenY - panelH - 16;
|
|
102
|
+
|
|
103
|
+
if (panelY < 0) panelY = screenY + objectHeight + 16;
|
|
104
|
+
|
|
105
|
+
const cw = this.container.offsetWidth || window.innerWidth;
|
|
106
|
+
panelX = Math.max(8, Math.min(panelX, cw - panelW - 8));
|
|
107
|
+
panelY = Math.max(8, panelY);
|
|
108
|
+
|
|
109
|
+
this.panel.style.left = `${Math.round(panelX)}px`;
|
|
110
|
+
this.panel.style.top = `${Math.round(panelY)}px`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
destroy() {
|
|
114
|
+
this._closePopover();
|
|
115
|
+
this._cancelRaf();
|
|
116
|
+
|
|
117
|
+
if (this._handlers && this.eventBus?.off) {
|
|
118
|
+
const H = this._handlers;
|
|
119
|
+
this.eventBus.off(Events.Tool.SelectionAdd, H.onSelectionAdd);
|
|
120
|
+
this.eventBus.off(Events.Tool.SelectionRemove, H.onSelectionRemove);
|
|
121
|
+
this.eventBus.off(Events.Tool.SelectionClear, H.onSelectionClear);
|
|
122
|
+
this.eventBus.off(Events.Object.Deleted, H.onDeleted);
|
|
123
|
+
this.eventBus.off(Events.Tool.DragStart, H.onDragStart);
|
|
124
|
+
this.eventBus.off(Events.Tool.DragUpdate, H.onDragUpdate);
|
|
125
|
+
this.eventBus.off(Events.Tool.DragEnd, H.onDragEnd);
|
|
126
|
+
this.eventBus.off(Events.Tool.GroupDragStart, H.onGroupDragStart);
|
|
127
|
+
this.eventBus.off(Events.Tool.GroupDragUpdate, H.onGroupDragUpdate);
|
|
128
|
+
this.eventBus.off(Events.Tool.GroupDragEnd, H.onGroupDragEnd);
|
|
129
|
+
this.eventBus.off(Events.Tool.ResizeUpdate, H.onResizeUpdate);
|
|
130
|
+
this.eventBus.off(Events.Tool.RotateUpdate, H.onRotateUpdate);
|
|
131
|
+
this.eventBus.off(Events.UI.ZoomPercent, H.onZoom);
|
|
132
|
+
this.eventBus.off(Events.Tool.PanUpdate, H.onPan);
|
|
133
|
+
this.eventBus.off(Events.Viewport.Changed, H.onViewportChanged);
|
|
134
|
+
this.eventBus.off(Events.Tool.Activated, H.onActivated);
|
|
135
|
+
this.eventBus.off(Events.Object.StateChanged, H.onStateChanged);
|
|
136
|
+
this.eventBus.off(Events.History.Changed, H.onHistoryChanged);
|
|
137
|
+
this.eventBus.off(Events.Object.TransformUpdated, H.onTransformUpdated);
|
|
138
|
+
this._handlers = null;
|
|
139
|
+
}
|
|
140
|
+
document.removeEventListener('click', this._boundDocClick);
|
|
141
|
+
|
|
142
|
+
if (this.panel?.parentNode) this.panel.parentNode.removeChild(this.panel);
|
|
143
|
+
this.panel = null;
|
|
144
|
+
this.currentId = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Делегаты для подмодуля Sync ────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
_updateControlsFromObject() { updateControlsFromObject(this); }
|
|
150
|
+
_updateBorderStyleBtns(v) { updateBorderStyleBtns(this, v); }
|
|
151
|
+
_setAlign(v) { setAlign(this, v); }
|
|
152
|
+
_syncSwatches(key, hex) { syncSwatches(this[key], hex); }
|
|
153
|
+
_pixiToHex(pixi) { return pixiToHex(pixi); }
|
|
154
|
+
|
|
155
|
+
// ── Подписки ───────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
_attachEvents() {
|
|
158
|
+
const H = this._handlers = {};
|
|
159
|
+
|
|
160
|
+
H.onSelectionAdd = () => this.updateFromSelection();
|
|
161
|
+
H.onSelectionRemove = () => this.updateFromSelection();
|
|
162
|
+
H.onSelectionClear = () => this.hide();
|
|
163
|
+
H.onDeleted = (objectId) => {
|
|
164
|
+
if (this.currentId && objectId === this.currentId) this.hide();
|
|
165
|
+
};
|
|
166
|
+
H.onDragStart = () => this.hide();
|
|
167
|
+
H.onDragUpdate = () => this._repositionThrottled();
|
|
168
|
+
H.onDragEnd = () => this.updateFromSelection();
|
|
169
|
+
H.onGroupDragStart = () => this.hide();
|
|
170
|
+
H.onGroupDragUpdate = () => this._repositionThrottled();
|
|
171
|
+
H.onGroupDragEnd = () => this.updateFromSelection();
|
|
172
|
+
H.onResizeUpdate = () => this._repositionThrottled();
|
|
173
|
+
H.onRotateUpdate = () => this._repositionThrottled();
|
|
174
|
+
H.onZoom = () => { if (this.currentId) this._repositionThrottled(); };
|
|
175
|
+
H.onPan = () => { if (this.currentId) this._repositionThrottled(); };
|
|
176
|
+
H.onViewportChanged = () => { if (this.currentId) this._repositionThrottled(); };
|
|
177
|
+
H.onActivated = ({ tool }) => { if (tool !== 'select') this.hide(); };
|
|
178
|
+
H.onStateChanged = ({ objectId }) => {
|
|
179
|
+
if (this.currentId && objectId === this.currentId &&
|
|
180
|
+
this.panel?.style.display !== 'none') {
|
|
181
|
+
this._updateControlsFromObject();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
H.onHistoryChanged = () => {
|
|
185
|
+
if (this.currentId && this.panel?.style.display !== 'none') {
|
|
186
|
+
this._updateControlsFromObject();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
H.onTransformUpdated = ({ objectId }) => {
|
|
190
|
+
if (this.currentId && objectId === this.currentId &&
|
|
191
|
+
this.panel?.style.display !== 'none') {
|
|
192
|
+
this._repositionThrottled();
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
this.eventBus.on(Events.Tool.SelectionAdd, H.onSelectionAdd);
|
|
197
|
+
this.eventBus.on(Events.Tool.SelectionRemove, H.onSelectionRemove);
|
|
198
|
+
this.eventBus.on(Events.Tool.SelectionClear, H.onSelectionClear);
|
|
199
|
+
this.eventBus.on(Events.Object.Deleted, H.onDeleted);
|
|
200
|
+
this.eventBus.on(Events.Tool.DragStart, H.onDragStart);
|
|
201
|
+
this.eventBus.on(Events.Tool.DragUpdate, H.onDragUpdate);
|
|
202
|
+
this.eventBus.on(Events.Tool.DragEnd, H.onDragEnd);
|
|
203
|
+
this.eventBus.on(Events.Tool.GroupDragStart, H.onGroupDragStart);
|
|
204
|
+
this.eventBus.on(Events.Tool.GroupDragUpdate, H.onGroupDragUpdate);
|
|
205
|
+
this.eventBus.on(Events.Tool.GroupDragEnd, H.onGroupDragEnd);
|
|
206
|
+
this.eventBus.on(Events.Tool.ResizeUpdate, H.onResizeUpdate);
|
|
207
|
+
this.eventBus.on(Events.Tool.RotateUpdate, H.onRotateUpdate);
|
|
208
|
+
this.eventBus.on(Events.UI.ZoomPercent, H.onZoom);
|
|
209
|
+
this.eventBus.on(Events.Tool.PanUpdate, H.onPan);
|
|
210
|
+
this.eventBus.on(Events.Viewport.Changed, H.onViewportChanged);
|
|
211
|
+
this.eventBus.on(Events.Tool.Activated, H.onActivated);
|
|
212
|
+
this.eventBus.on(Events.Object.StateChanged, H.onStateChanged);
|
|
213
|
+
this.eventBus.on(Events.History.Changed, H.onHistoryChanged);
|
|
214
|
+
this.eventBus.on(Events.Object.TransformUpdated, H.onTransformUpdated);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Построение DOM ─────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
_createPanel() {
|
|
220
|
+
const panel = document.createElement('div');
|
|
221
|
+
panel.className = 'shape-properties-panel';
|
|
222
|
+
panel.id = 'shape-properties-panel';
|
|
223
|
+
|
|
224
|
+
// Строка 1: форма, заливка, рамка, фаска
|
|
225
|
+
const row1 = document.createElement('div');
|
|
226
|
+
row1.className = 'spp-row';
|
|
227
|
+
row1.appendChild(buildShapeGroup(this));
|
|
228
|
+
row1.appendChild(sep());
|
|
229
|
+
const [fillLabel, fillWrap] = buildFillGroup(this);
|
|
230
|
+
row1.appendChild(fillLabel);
|
|
231
|
+
row1.appendChild(fillWrap);
|
|
232
|
+
row1.appendChild(sep());
|
|
233
|
+
row1.appendChild(buildBorderGroup(this));
|
|
234
|
+
row1.appendChild(sep());
|
|
235
|
+
const [rLabel, rGroup] = buildRadiusGroup(this);
|
|
236
|
+
row1.appendChild(rLabel);
|
|
237
|
+
row1.appendChild(rGroup);
|
|
238
|
+
panel.appendChild(row1);
|
|
239
|
+
|
|
240
|
+
// Строка 2: текстовые свойства
|
|
241
|
+
const row2 = document.createElement('div');
|
|
242
|
+
row2.className = 'spp-row';
|
|
243
|
+
buildTextGroup(this).forEach(n => row2.appendChild(n));
|
|
244
|
+
panel.appendChild(row2);
|
|
245
|
+
|
|
246
|
+
this.panel = panel;
|
|
247
|
+
this.container.appendChild(panel);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Поповеры ───────────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
_togglePopover(popoverEl) {
|
|
253
|
+
if (this._openPopoverEl === popoverEl) {
|
|
254
|
+
this._closePopover();
|
|
255
|
+
} else {
|
|
256
|
+
this._closePopover();
|
|
257
|
+
popoverEl.style.display = 'block';
|
|
258
|
+
this._openPopoverEl = popoverEl;
|
|
259
|
+
setTimeout(() => document.addEventListener('click', this._boundDocClick), 0);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
_closePopover() {
|
|
264
|
+
if (this._openPopoverEl) {
|
|
265
|
+
this._openPopoverEl.style.display = 'none';
|
|
266
|
+
this._openPopoverEl = null;
|
|
267
|
+
}
|
|
268
|
+
document.removeEventListener('click', this._boundDocClick);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_onDocumentClick(e) {
|
|
272
|
+
if (!this._openPopoverEl || !e.target) return;
|
|
273
|
+
if (!this._openPopoverEl.contains(e.target) && !this.panel.contains(e.target)) {
|
|
274
|
+
this._closePopover();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Emit helper ────────────────────────────────────────────────────────────
|
|
279
|
+
|
|
280
|
+
_emit(payload) {
|
|
281
|
+
if (!this.currentId) return;
|
|
282
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
283
|
+
objectId: this.currentId,
|
|
284
|
+
...payload,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── RAF-позиционирование ───────────────────────────────────────────────────
|
|
289
|
+
|
|
290
|
+
_repositionThrottled() {
|
|
291
|
+
if (this._repositionScheduled) return;
|
|
292
|
+
this._repositionScheduled = true;
|
|
293
|
+
this._repositionRafId = requestAnimationFrame(() => {
|
|
294
|
+
this._repositionScheduled = false;
|
|
295
|
+
this._repositionRafId = null;
|
|
296
|
+
if (this.panel) this.reposition();
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
_cancelRaf() {
|
|
301
|
+
if (this._repositionRafId != null) {
|
|
302
|
+
cancelAnimationFrame(this._repositionRafId);
|
|
303
|
+
this._repositionRafId = null;
|
|
304
|
+
}
|
|
305
|
+
this._repositionScheduled = false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -12,12 +12,14 @@ import {
|
|
|
12
12
|
buildBackgroundColorUpdate,
|
|
13
13
|
buildFontFamilyUpdate,
|
|
14
14
|
buildFontSizeUpdate,
|
|
15
|
+
buildMarkdownUpdate,
|
|
15
16
|
buildTextColorUpdate,
|
|
16
17
|
getControlValuesFromProperties,
|
|
17
18
|
getFallbackControlValues,
|
|
18
19
|
getObjectGeometry,
|
|
19
20
|
getObjectProperties,
|
|
20
21
|
getSelectedTextObjectId,
|
|
22
|
+
LINE_HEIGHT_DEFAULT,
|
|
21
23
|
syncPixiTextProperties,
|
|
22
24
|
} from './text-properties/TextPropertiesPanelMapper.js';
|
|
23
25
|
import {
|
|
@@ -76,7 +78,6 @@ export class TextPropertiesPanel {
|
|
|
76
78
|
|
|
77
79
|
updateFromSelection() {
|
|
78
80
|
if (this.isTextEditing) {
|
|
79
|
-
this.hide();
|
|
80
81
|
return;
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -213,6 +214,80 @@ export class TextPropertiesPanel {
|
|
|
213
214
|
this._updateTextAppearance(this.currentId, { backgroundColor });
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
_toggleFormat(prop) {
|
|
218
|
+
if (!this.currentId) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const properties = getObjectProperties(this.eventBus, this.currentId);
|
|
223
|
+
const newValue = !(properties ? Boolean(properties[prop]) : false);
|
|
224
|
+
|
|
225
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
226
|
+
objectId: this.currentId,
|
|
227
|
+
updates: { properties: { [prop]: newValue } },
|
|
228
|
+
});
|
|
229
|
+
this._updateTextAppearance(this.currentId, { [prop]: newValue });
|
|
230
|
+
|
|
231
|
+
const btnMap = {
|
|
232
|
+
bold: this.boldBtn,
|
|
233
|
+
italic: this.italicBtn,
|
|
234
|
+
underline: this.underlineBtn,
|
|
235
|
+
strikethrough: this.strikethroughBtn,
|
|
236
|
+
};
|
|
237
|
+
if (btnMap[prop]) {
|
|
238
|
+
btnMap[prop].classList.toggle('is-active', newValue);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
_changeTextAlign(v) {
|
|
243
|
+
if (!this.currentId) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
248
|
+
objectId: this.currentId,
|
|
249
|
+
updates: { properties: { textAlign: v } },
|
|
250
|
+
});
|
|
251
|
+
this._updateTextAppearance(this.currentId, { textAlign: v });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_changeListType(v) {
|
|
255
|
+
if (!this.currentId) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
260
|
+
objectId: this.currentId,
|
|
261
|
+
updates: { properties: { listType: v } },
|
|
262
|
+
});
|
|
263
|
+
this._updateTextAppearance(this.currentId, { listType: v });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
_changeLineHeight(n) {
|
|
267
|
+
if (!this.currentId) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
272
|
+
objectId: this.currentId,
|
|
273
|
+
updates: { properties: { lineHeight: n } },
|
|
274
|
+
});
|
|
275
|
+
this._updateTextAppearance(this.currentId, { lineHeight: n });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
_changeMarkdown(markdown) {
|
|
279
|
+
if (!this.currentId) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this.eventBus.emit(Events.Object.StateChanged, {
|
|
284
|
+
objectId: this.currentId,
|
|
285
|
+
updates: buildMarkdownUpdate(markdown),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
this._updateTextAppearance(this.currentId, { markdown });
|
|
289
|
+
}
|
|
290
|
+
|
|
216
291
|
_updateTextAppearance(objectId, properties) {
|
|
217
292
|
applyTextAppearanceToDom(objectId, properties);
|
|
218
293
|
syncPixiTextProperties(this.eventBus, objectId, properties);
|
|
@@ -236,6 +311,19 @@ export class TextPropertiesPanel {
|
|
|
236
311
|
this.fontSizeSelect.value = values.fontSize;
|
|
237
312
|
this._updateCurrentColorButton(values.color);
|
|
238
313
|
this._updateCurrentBgColorButton(values.backgroundColor);
|
|
314
|
+
if (this.markdownToggle) {
|
|
315
|
+
this.markdownToggle.checked = values.markdown;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (this.boldBtn) this.boldBtn.classList.toggle('is-active', values.bold);
|
|
319
|
+
if (this.italicBtn) this.italicBtn.classList.toggle('is-active', values.italic);
|
|
320
|
+
if (this.underlineBtn) this.underlineBtn.classList.toggle('is-active', values.underline);
|
|
321
|
+
if (this.strikethroughBtn) this.strikethroughBtn.classList.toggle('is-active', values.strikethrough);
|
|
322
|
+
if (this.alignControl) this.alignControl.value = values.textAlign;
|
|
323
|
+
if (this.listControl) this.listControl.value = values.listType;
|
|
324
|
+
if (this.lineHeightSlider) {
|
|
325
|
+
this.lineHeightSlider.value = String(values.lineHeight !== null ? values.lineHeight : LINE_HEIGHT_DEFAULT);
|
|
326
|
+
}
|
|
239
327
|
}
|
|
240
328
|
|
|
241
329
|
reposition() {
|
|
@@ -277,6 +365,17 @@ export class TextPropertiesPanel {
|
|
|
277
365
|
return;
|
|
278
366
|
}
|
|
279
367
|
|
|
368
|
+
if (typeof event.target.closest === 'function' && event.target.closest('.moodboard-text-editor')) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Клики внутри canvas-контейнера управляются через EventBus (SelectionClear).
|
|
373
|
+
// Без этой проверки тот же mousedown, который вызвал SelectionAdd → showFor,
|
|
374
|
+
// немедленно дотекает до capture-listener и закрывает только что открытую панель.
|
|
375
|
+
if (this.container.contains(event.target)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
280
379
|
this.container.getBoundingClientRect();
|
|
281
380
|
this.hide();
|
|
282
381
|
}
|
package/src/ui/Toolbar.js
CHANGED
|
@@ -6,6 +6,7 @@ import { IconLoader } from '../utils/iconLoader.js';
|
|
|
6
6
|
import { ToolbarDialogsController } from './toolbar/ToolbarDialogsController.js';
|
|
7
7
|
import { ToolbarPopupsController } from './toolbar/ToolbarPopupsController.js';
|
|
8
8
|
import { ToolbarActionRouter } from './toolbar/ToolbarActionRouter.js';
|
|
9
|
+
import { ReactionsPopupController } from './toolbar/ReactionsPopupController.js';
|
|
9
10
|
import { ToolbarTooltipController } from './toolbar/ToolbarTooltipController.js';
|
|
10
11
|
import { ToolbarStateController } from './toolbar/ToolbarStateController.js';
|
|
11
12
|
import { ToolbarRenderer } from './toolbar/ToolbarRenderer.js';
|
|
@@ -27,6 +28,7 @@ export class Toolbar {
|
|
|
27
28
|
|
|
28
29
|
this.dialogsController = new ToolbarDialogsController(this);
|
|
29
30
|
this.popupsController = new ToolbarPopupsController(this);
|
|
31
|
+
this.reactionsController = new ReactionsPopupController(this);
|
|
30
32
|
this.actionRouter = new ToolbarActionRouter(this);
|
|
31
33
|
this.tooltipController = new ToolbarTooltipController(this);
|
|
32
34
|
this.stateController = new ToolbarStateController(this);
|
|
@@ -48,7 +50,6 @@ export class Toolbar {
|
|
|
48
50
|
|
|
49
51
|
this._toolActivatedHandler = ({ tool }) => {
|
|
50
52
|
this.setActiveToolbarButton(tool);
|
|
51
|
-
// Draw palette must stay open only while draw tool is active.
|
|
52
53
|
if (tool !== 'draw') {
|
|
53
54
|
this.closeDrawPopup();
|
|
54
55
|
}
|
|
@@ -135,18 +136,21 @@ export class Toolbar {
|
|
|
135
136
|
const isInsideDrawPopup = this.drawPopupEl && this.drawPopupEl.contains(e.target);
|
|
136
137
|
const isInsideEmojiPopup = this.emojiPopupEl && this.emojiPopupEl.contains(e.target);
|
|
137
138
|
const isInsideFramePopup = this.framePopupEl && this.framePopupEl.contains(e.target);
|
|
139
|
+
const isInsideReactionsPopup = this.reactionsPopupEl && this.reactionsPopupEl.contains(e.target);
|
|
138
140
|
const isShapesButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--shapes');
|
|
139
141
|
const isDrawButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--pencil');
|
|
140
142
|
const isEmojiButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--emoji');
|
|
141
143
|
const isFrameButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--frame');
|
|
144
|
+
const isReactionsButton = e.target.closest && e.target.closest('.moodboard-toolbar__button--reactions');
|
|
142
145
|
const isDrawActive = !!(this.element && this.element.querySelector('.moodboard-toolbar__button--pencil.moodboard-toolbar__button--active'));
|
|
143
146
|
|
|
144
|
-
if (!isInsideToolbar && !isInsideShapesPopup && !isShapesButton && !isInsideDrawPopup && !isDrawButton && !isInsideEmojiPopup && !isEmojiButton && !isInsideFramePopup && !isFrameButton) {
|
|
147
|
+
if (!isInsideToolbar && !isInsideShapesPopup && !isShapesButton && !isInsideDrawPopup && !isDrawButton && !isInsideEmojiPopup && !isEmojiButton && !isInsideFramePopup && !isFrameButton && !isInsideReactionsPopup && !isReactionsButton) {
|
|
145
148
|
this.closeShapesPopup();
|
|
146
149
|
if (!isDrawActive) {
|
|
147
150
|
this.closeDrawPopup();
|
|
148
151
|
}
|
|
149
152
|
this.closeEmojiPopup();
|
|
153
|
+
this.closeReactionsPopup();
|
|
150
154
|
this.closeFramePopup();
|
|
151
155
|
}
|
|
152
156
|
};
|
|
@@ -251,6 +255,25 @@ export class Toolbar {
|
|
|
251
255
|
return this.popupsController.closeEmojiPopup();
|
|
252
256
|
}
|
|
253
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Всплывающая панель реакций (UI)
|
|
260
|
+
*/
|
|
261
|
+
createReactionsPopup() {
|
|
262
|
+
return this.reactionsController.createReactionsPopup();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
toggleReactionsPopup(anchorButton) {
|
|
266
|
+
return this.reactionsController.toggleReactionsPopup(anchorButton);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
openReactionsPopup(anchorButton) {
|
|
270
|
+
return this.reactionsController.openReactionsPopup(anchorButton);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
closeReactionsPopup() {
|
|
274
|
+
return this.reactionsController.closeReactionsPopup();
|
|
275
|
+
}
|
|
276
|
+
|
|
254
277
|
/**
|
|
255
278
|
* Показывает диалог подтверждения очистки холста
|
|
256
279
|
*/
|
package/src/ui/Topbar.js
CHANGED
|
@@ -16,7 +16,7 @@ export class Topbar {
|
|
|
16
16
|
this.icons = this.iconLoader.icons;
|
|
17
17
|
// Палитра кнопки заливки и соответствие цвету фона доски
|
|
18
18
|
this._palette = [
|
|
19
|
-
{ id: 1, name: 'default-light', btnHex: '#
|
|
19
|
+
{ id: 1, name: 'default-light', btnHex: '#d6e8f7', board: '#f0f6fc' },
|
|
20
20
|
{ id: 2, name: 'mint-light', btnHex: '#E8F5E9', board: '#f8fff7' },
|
|
21
21
|
{ id: 3, name: 'peach-light', btnHex: '#FFF3E0', board: '#fffcf7' },
|
|
22
22
|
{ id: 4, name: 'gray-light', btnHex: '#f5f5f5', board: '#f5f5f5' },
|
|
@@ -246,7 +246,7 @@ export class Topbar {
|
|
|
246
246
|
const pop = document.createElement('div');
|
|
247
247
|
pop.className = 'moodboard-topbar__paint-popover';
|
|
248
248
|
// Пять цветов кнопок-палитры и соответствующие цвета фона доски
|
|
249
|
-
// 1: фон #
|
|
249
|
+
// 1: фон #f0f6fc, кнопка #d6e8f7
|
|
250
250
|
// 2: фон #f8fff7, кнопка #E8F5E9
|
|
251
251
|
// 3: фон #fffcf7, кнопка #FFF3E0
|
|
252
252
|
// 4: фон #f5f5f5, кнопка #f5f5f5
|
|
@@ -108,11 +108,12 @@ export class HoverLiftController {
|
|
|
108
108
|
const hasStaticShadow = type === 'image' || type === 'frame';
|
|
109
109
|
const restAlpha = hasStaticShadow ? IMAGE_REST_ALPHA : 0;
|
|
110
110
|
const restDistance = hasStaticShadow ? IMAGE_REST_DISTANCE : 8;
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
111
|
+
// Hover-«pop» (scale + подъём) включён в т.ч. для фрейма. Скачок в
|
|
112
|
+
// переходе hover→resize/drag снимается мгновенным snapBack по событиям
|
|
113
|
+
// ResizeStart/DragStart/SelectionAdd (см. _snapBack* ниже): к моменту
|
|
114
|
+
// нажатия объект уже возвращён к базе. Логические габариты фрейма для
|
|
115
|
+
// resize считаются из state, а не из scaled-pixi, поэтому ресайз с
|
|
116
|
+
// координатной математикой hover не пересекается.
|
|
116
117
|
|
|
117
118
|
const shadow = createShadowFilter(restAlpha, restDistance);
|
|
118
119
|
pixiObject.filters = [...(pixiObject.filters || []), shadow];
|
|
@@ -129,8 +130,6 @@ export class HoverLiftController {
|
|
|
129
130
|
};
|
|
130
131
|
this._entries.set(pixiObject, entry);
|
|
131
132
|
|
|
132
|
-
if (!liftEnabled) return;
|
|
133
|
-
|
|
134
133
|
const w = objectData?.width ?? objectData?.properties?.width ?? 100;
|
|
135
134
|
const h = objectData?.height ?? objectData?.properties?.height ?? 100;
|
|
136
135
|
const preset = getPreset(w, h);
|
|
@@ -25,7 +25,12 @@ export class ChatComposer {
|
|
|
25
25
|
this._statusBar = refs.statusBar ?? null;
|
|
26
26
|
this._handlers = handlers;
|
|
27
27
|
this._listeners = [];
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Внутреннее хранилище вложений. `sourceObjectId` нужен для дедупа
|
|
30
|
+
* reference-картинок, которые приходят из box-select / клика по объекту:
|
|
31
|
+
* один объект на доске = одно превью в композере.
|
|
32
|
+
* @type {{ file: File, sourceObjectId: string|null }[]}
|
|
33
|
+
*/
|
|
29
34
|
this._attachments = [];
|
|
30
35
|
}
|
|
31
36
|
|
|
@@ -76,13 +81,54 @@ export class ChatComposer {
|
|
|
76
81
|
this._textarea.focus();
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
/**
|
|
85
|
+
* @param {File} file
|
|
86
|
+
* @param {{ sourceObjectId?: string|null }} [options] — `sourceObjectId`
|
|
87
|
+
* передаётся для reference-картинок с доски; дубликаты по этому id игнорируются.
|
|
88
|
+
*/
|
|
89
|
+
addAttachment(file, options = {}) {
|
|
80
90
|
if (!file) return;
|
|
81
|
-
|
|
91
|
+
const sourceObjectId = options?.sourceObjectId ?? null;
|
|
92
|
+
if (sourceObjectId && this._attachments.some((entry) => entry.sourceObjectId === sourceObjectId)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this._attachments.push({ file, sourceObjectId });
|
|
82
96
|
this._renderAttachmentsPreview();
|
|
83
97
|
this._refreshSendState();
|
|
84
98
|
}
|
|
85
99
|
|
|
100
|
+
hasAttachmentForObject(sourceObjectId) {
|
|
101
|
+
if (!sourceObjectId) return false;
|
|
102
|
+
return this._attachments.some((entry) => entry.sourceObjectId === sourceObjectId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Удаляет превью конкретного объекта доски. Вызывается при снятии фокуса с изображения.
|
|
107
|
+
* @param {string} sourceObjectId
|
|
108
|
+
*/
|
|
109
|
+
removeAttachmentForObject(sourceObjectId) {
|
|
110
|
+
if (!sourceObjectId) return;
|
|
111
|
+
const before = this._attachments.length;
|
|
112
|
+
this._attachments = this._attachments.filter((entry) => entry.sourceObjectId !== sourceObjectId);
|
|
113
|
+
if (this._attachments.length !== before) {
|
|
114
|
+
this._renderAttachmentsPreview();
|
|
115
|
+
this._refreshSendState();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Удаляет все превью, добавленные с доски (sourceObjectId !== null).
|
|
121
|
+
* Файловые вложения (скрепка) не затрагиваются.
|
|
122
|
+
*/
|
|
123
|
+
removeAllBoardAttachments() {
|
|
124
|
+
const before = this._attachments.length;
|
|
125
|
+
this._attachments = this._attachments.filter((entry) => entry.sourceObjectId === null);
|
|
126
|
+
if (this._attachments.length !== before) {
|
|
127
|
+
this._renderAttachmentsPreview();
|
|
128
|
+
this._refreshSendState();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
86
132
|
destroy() {
|
|
87
133
|
for (const off of this._listeners) off();
|
|
88
134
|
this._listeners = [];
|
|
@@ -95,7 +141,12 @@ export class ChatComposer {
|
|
|
95
141
|
const hasAttachments = this._attachments.length > 0;
|
|
96
142
|
if (!trimmed && !hasAttachments) return;
|
|
97
143
|
if (this._send.dataset.state === 'streaming') return;
|
|
98
|
-
const attachments =
|
|
144
|
+
const attachments = this._attachments.map((entry) => entry.file);
|
|
145
|
+
this._textarea.value = '';
|
|
146
|
+
this._attachments = [];
|
|
147
|
+
this._resizeTextarea();
|
|
148
|
+
if (this._attachmentsPreview) this._renderAttachmentsPreview();
|
|
149
|
+
this._refreshSendState();
|
|
99
150
|
this._handlers.onSubmit?.(trimmed, attachments);
|
|
100
151
|
}
|
|
101
152
|
|
|
@@ -110,7 +161,7 @@ export class ChatComposer {
|
|
|
110
161
|
const files = Array.from(this._fileInput.files || []);
|
|
111
162
|
if (!files.length) return;
|
|
112
163
|
for (const file of files) {
|
|
113
|
-
this._attachments.push(file);
|
|
164
|
+
this._attachments.push({ file, sourceObjectId: null });
|
|
114
165
|
}
|
|
115
166
|
this._fileInput.value = '';
|
|
116
167
|
this._renderAttachmentsPreview();
|
|
@@ -134,8 +185,8 @@ export class ChatComposer {
|
|
|
134
185
|
inputRow?.classList.add('has-attachments');
|
|
135
186
|
this._textarea.placeholder = 'Опишите правку, изменение или стилевое направление эталонного изображения';
|
|
136
187
|
for (let i = 0; i < this._attachments.length; i++) {
|
|
137
|
-
const
|
|
138
|
-
const item = this._buildAttachmentItem(file, i);
|
|
188
|
+
const entry = this._attachments[i];
|
|
189
|
+
const item = this._buildAttachmentItem(entry.file, i);
|
|
139
190
|
container.appendChild(item);
|
|
140
191
|
}
|
|
141
192
|
}
|