@sequent-org/moodboard 1.4.31 → 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 +59 -12
- package/src/ui/chat/ChatExtendedPromptModal.js +1 -12
- package/src/ui/chat/ChatWindow.js +60 -144
- package/src/ui/chat/ChatWindowRenderer.js +1 -8
- package/src/ui/chat/icons.js +0 -4
- 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 +682 -28
- 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
|
@@ -13,7 +13,7 @@ const CLOSE_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12
|
|
|
13
13
|
|
|
14
14
|
export class ChatComposer {
|
|
15
15
|
/**
|
|
16
|
-
* @param {{ textarea: HTMLTextAreaElement, send: HTMLButtonElement, attach: HTMLButtonElement, fileInput: HTMLInputElement, attachmentsPreview: HTMLElement,
|
|
16
|
+
* @param {{ textarea: HTMLTextAreaElement, send: HTMLButtonElement, attach: HTMLButtonElement, fileInput: HTMLInputElement, attachmentsPreview: HTMLElement, statusBar?: HTMLElement }} refs
|
|
17
17
|
* @param {{ onSubmit: (text: string, attachments: File[]) => void, onAbort: () => void }} handlers
|
|
18
18
|
*/
|
|
19
19
|
constructor(refs, handlers) {
|
|
@@ -22,11 +22,15 @@ export class ChatComposer {
|
|
|
22
22
|
this._attach = refs.attach ?? null;
|
|
23
23
|
this._fileInput = refs.fileInput ?? null;
|
|
24
24
|
this._attachmentsPreview = refs.attachmentsPreview ?? null;
|
|
25
|
-
this._enhancePrompt = refs.enhancePrompt ?? null;
|
|
26
25
|
this._statusBar = refs.statusBar ?? null;
|
|
27
26
|
this._handlers = handlers;
|
|
28
27
|
this._listeners = [];
|
|
29
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Внутреннее хранилище вложений. `sourceObjectId` нужен для дедупа
|
|
30
|
+
* reference-картинок, которые приходят из box-select / клика по объекту:
|
|
31
|
+
* один объект на доске = одно превью в композере.
|
|
32
|
+
* @type {{ file: File, sourceObjectId: string|null }[]}
|
|
33
|
+
*/
|
|
30
34
|
this._attachments = [];
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -77,13 +81,54 @@ export class ChatComposer {
|
|
|
77
81
|
this._textarea.focus();
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
|
|
84
|
+
/**
|
|
85
|
+
* @param {File} file
|
|
86
|
+
* @param {{ sourceObjectId?: string|null }} [options] — `sourceObjectId`
|
|
87
|
+
* передаётся для reference-картинок с доски; дубликаты по этому id игнорируются.
|
|
88
|
+
*/
|
|
89
|
+
addAttachment(file, options = {}) {
|
|
81
90
|
if (!file) return;
|
|
82
|
-
|
|
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 });
|
|
83
96
|
this._renderAttachmentsPreview();
|
|
84
97
|
this._refreshSendState();
|
|
85
98
|
}
|
|
86
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
|
+
|
|
87
132
|
destroy() {
|
|
88
133
|
for (const off of this._listeners) off();
|
|
89
134
|
this._listeners = [];
|
|
@@ -96,7 +141,12 @@ export class ChatComposer {
|
|
|
96
141
|
const hasAttachments = this._attachments.length > 0;
|
|
97
142
|
if (!trimmed && !hasAttachments) return;
|
|
98
143
|
if (this._send.dataset.state === 'streaming') return;
|
|
99
|
-
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();
|
|
100
150
|
this._handlers.onSubmit?.(trimmed, attachments);
|
|
101
151
|
}
|
|
102
152
|
|
|
@@ -105,16 +155,13 @@ export class ChatComposer {
|
|
|
105
155
|
const hasAttachments = this._attachments.length > 0;
|
|
106
156
|
this._send.dataset.state = (hasText || hasAttachments) ? 'ready' : 'idle';
|
|
107
157
|
this._send.disabled = false;
|
|
108
|
-
if (this._enhancePrompt) {
|
|
109
|
-
this._enhancePrompt.dataset.empty = hasText ? 'false' : 'true';
|
|
110
|
-
}
|
|
111
158
|
}
|
|
112
159
|
|
|
113
160
|
_handleFileChange() {
|
|
114
161
|
const files = Array.from(this._fileInput.files || []);
|
|
115
162
|
if (!files.length) return;
|
|
116
163
|
for (const file of files) {
|
|
117
|
-
this._attachments.push(file);
|
|
164
|
+
this._attachments.push({ file, sourceObjectId: null });
|
|
118
165
|
}
|
|
119
166
|
this._fileInput.value = '';
|
|
120
167
|
this._renderAttachmentsPreview();
|
|
@@ -138,8 +185,8 @@ export class ChatComposer {
|
|
|
138
185
|
inputRow?.classList.add('has-attachments');
|
|
139
186
|
this._textarea.placeholder = 'Опишите правку, изменение или стилевое направление эталонного изображения';
|
|
140
187
|
for (let i = 0; i < this._attachments.length; i++) {
|
|
141
|
-
const
|
|
142
|
-
const item = this._buildAttachmentItem(file, i);
|
|
188
|
+
const entry = this._attachments[i];
|
|
189
|
+
const item = this._buildAttachmentItem(entry.file, i);
|
|
143
190
|
container.appendChild(item);
|
|
144
191
|
}
|
|
145
192
|
}
|
|
@@ -36,11 +36,6 @@ export class ChatExtendedPromptModal {
|
|
|
36
36
|
this._sourceTextarea.value = '';
|
|
37
37
|
this._sourceTextarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
38
38
|
});
|
|
39
|
-
|
|
40
|
-
this._on(this._refs.enhanceBtn, 'click', () => {
|
|
41
|
-
const originalEnhance = this._sourceTextarea.parentElement?.querySelector('.moodboard-chat__input-icon-btn--enhance-prompt');
|
|
42
|
-
if (originalEnhance) originalEnhance.click();
|
|
43
|
-
});
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
show() {
|
|
@@ -111,13 +106,7 @@ export class ChatExtendedPromptModal {
|
|
|
111
106
|
clearBtn.className = 'moodboard-chat__extended-clear';
|
|
112
107
|
clearBtn.textContent = 'Очистить';
|
|
113
108
|
|
|
114
|
-
const enhanceBtn = document.createElement('button');
|
|
115
|
-
enhanceBtn.type = 'button';
|
|
116
|
-
enhanceBtn.className = 'moodboard-chat__extended-enhance';
|
|
117
|
-
enhanceBtn.innerHTML = `<span class="moodboard-chat__extended-enhance-icon">${ICONS.enhancePrompt}</span> Улучшить`;
|
|
118
|
-
|
|
119
109
|
actions.appendChild(clearBtn);
|
|
120
|
-
actions.appendChild(enhanceBtn);
|
|
121
110
|
|
|
122
111
|
body.appendChild(textarea);
|
|
123
112
|
body.appendChild(actions);
|
|
@@ -126,6 +115,6 @@ export class ChatExtendedPromptModal {
|
|
|
126
115
|
modal.appendChild(body);
|
|
127
116
|
overlay.appendChild(modal);
|
|
128
117
|
|
|
129
|
-
return { overlay, modal, header, title, closeBtn, body, textarea, clearBtn
|
|
118
|
+
return { overlay, modal, header, title, closeBtn, body, textarea, clearBtn };
|
|
130
119
|
}
|
|
131
120
|
}
|
|
@@ -60,7 +60,6 @@ const BOARD_IMAGE_REARRANGE_MS = 520;
|
|
|
60
60
|
const BOARD_IMAGE_PENDING_ENTER_FACTOR = 1.6;
|
|
61
61
|
// Каскад между блоками одного батча (мс): пользователь видит, что они приезжают друг за другом.
|
|
62
62
|
const BOARD_IMAGE_PENDING_STAGGER_MS = 90;
|
|
63
|
-
const REFERENCE_DRAG_PREVIEW_SIZE = 96;
|
|
64
63
|
|
|
65
64
|
const MODEL_OPTIONS = [
|
|
66
65
|
{
|
|
@@ -145,12 +144,12 @@ export class ChatWindow {
|
|
|
145
144
|
this._pendingOverlays = new Map();
|
|
146
145
|
this._pendingOverlayTimers = new Map();
|
|
147
146
|
this._boardImageShiftAnimations = new Map();
|
|
148
|
-
this._boardCursor = null;
|
|
149
|
-
this._draggedReferenceObject = null;
|
|
150
|
-
this._draggedReferenceStartPosition = null;
|
|
151
|
-
this._referenceDragPreview = null;
|
|
152
|
-
this._referenceDragHandlers = null;
|
|
153
147
|
this._clearSelectionOnSendClick = null;
|
|
148
|
+
this._selectionHandlers = null;
|
|
149
|
+
// Окно от BoxSelectStart до BoxSelectCommit: в это время SelectionAdd
|
|
150
|
+
// приходит на каждый mousemove и не должен пушить превью в чат —
|
|
151
|
+
// финальный набор картинок мы получим из BoxSelectCommit по strict-contains.
|
|
152
|
+
this._boxSelectActive = false;
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
attach() {
|
|
@@ -167,7 +166,6 @@ export class ChatWindow {
|
|
|
167
166
|
attach: this._refs.attach,
|
|
168
167
|
fileInput: this._refs.fileInput,
|
|
169
168
|
attachmentsPreview: this._refs.attachmentsPreview,
|
|
170
|
-
enhancePrompt: this._refs.enhancePrompt,
|
|
171
169
|
statusBar: this._refs.statusBar
|
|
172
170
|
},
|
|
173
171
|
{
|
|
@@ -181,7 +179,7 @@ export class ChatWindow {
|
|
|
181
179
|
this._clearSelectionOnSendClick = () => this._clearBoardSelection();
|
|
182
180
|
this._refs.send.addEventListener('click', this._clearSelectionOnSendClick);
|
|
183
181
|
this._composer.attach();
|
|
184
|
-
this.
|
|
182
|
+
this._attachSelectionEvents();
|
|
185
183
|
|
|
186
184
|
this._extendedPromptModal = new ChatExtendedPromptModal(
|
|
187
185
|
this._container,
|
|
@@ -264,13 +262,12 @@ export class ChatWindow {
|
|
|
264
262
|
if (!this._attached) return;
|
|
265
263
|
this._clearPendingOverlays();
|
|
266
264
|
this._cancelBoardImageShiftAnimations();
|
|
267
|
-
this._clearReferenceDragState();
|
|
268
265
|
if (this._unsubscribe) { this._unsubscribe(); this._unsubscribe = null; }
|
|
269
266
|
if (this._clearSelectionOnSendClick && this._refs?.send) {
|
|
270
267
|
this._refs.send.removeEventListener('click', this._clearSelectionOnSendClick);
|
|
271
268
|
this._clearSelectionOnSendClick = null;
|
|
272
269
|
}
|
|
273
|
-
this.
|
|
270
|
+
this._detachSelectionEvents();
|
|
274
271
|
this._shiftedForImageBatchKeys.clear();
|
|
275
272
|
this._boardImageShiftHistory.clear();
|
|
276
273
|
this._composer?.destroy();
|
|
@@ -466,175 +463,94 @@ export class ChatWindow {
|
|
|
466
463
|
};
|
|
467
464
|
}
|
|
468
465
|
|
|
469
|
-
|
|
466
|
+
_attachSelectionEvents() {
|
|
470
467
|
const eventBus = this._boardCore?.eventBus;
|
|
471
|
-
if (!eventBus || typeof eventBus.on !== 'function' || this.
|
|
468
|
+
if (!eventBus || typeof eventBus.on !== 'function' || this._selectionHandlers) return;
|
|
472
469
|
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
this._boardCursor = { x, y };
|
|
476
|
-
this._updateReferenceDragPreview();
|
|
477
|
-
}
|
|
470
|
+
const onSelectionAdd = (data) => {
|
|
471
|
+
void this._handleSelectionAdd(data);
|
|
478
472
|
};
|
|
479
|
-
const
|
|
480
|
-
|
|
473
|
+
const onSelectionRemove = (data) => {
|
|
474
|
+
const objectId = data?.object;
|
|
475
|
+
if (objectId) this._composer?.removeAttachmentForObject?.(objectId);
|
|
481
476
|
};
|
|
482
|
-
const
|
|
483
|
-
|
|
477
|
+
const onSelectionClear = () => {
|
|
478
|
+
this._composer?.removeAllBoardAttachments?.();
|
|
484
479
|
};
|
|
485
|
-
const
|
|
486
|
-
|
|
480
|
+
const onBoxSelectStart = () => {
|
|
481
|
+
this._boxSelectActive = true;
|
|
482
|
+
};
|
|
483
|
+
const onBoxSelectCommit = (data) => {
|
|
484
|
+
void this._handleBoxSelectCommit(data);
|
|
487
485
|
};
|
|
488
486
|
|
|
489
|
-
this.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
487
|
+
this._selectionHandlers = {
|
|
488
|
+
onSelectionAdd,
|
|
489
|
+
onSelectionRemove,
|
|
490
|
+
onSelectionClear,
|
|
491
|
+
onBoxSelectStart,
|
|
492
|
+
onBoxSelectCommit
|
|
493
|
+
};
|
|
493
494
|
eventBus.on(Events.Tool.SelectionAdd, onSelectionAdd);
|
|
495
|
+
eventBus.on(Events.Tool.SelectionRemove, onSelectionRemove);
|
|
496
|
+
eventBus.on(Events.Tool.SelectionClear, onSelectionClear);
|
|
497
|
+
eventBus.on(Events.Tool.BoxSelectStart, onBoxSelectStart);
|
|
498
|
+
eventBus.on(Events.Tool.BoxSelectCommit, onBoxSelectCommit);
|
|
494
499
|
}
|
|
495
500
|
|
|
496
|
-
|
|
501
|
+
_detachSelectionEvents() {
|
|
497
502
|
const eventBus = this._boardCore?.eventBus;
|
|
498
|
-
const handlers = this.
|
|
503
|
+
const handlers = this._selectionHandlers;
|
|
499
504
|
if (!eventBus || typeof eventBus.off !== 'function' || !handlers) return;
|
|
500
505
|
|
|
501
|
-
eventBus.off(Events.UI.CursorMove, handlers.onCursorMove);
|
|
502
|
-
eventBus.off(Events.Tool.DragStart, handlers.onDragStart);
|
|
503
|
-
eventBus.off(Events.Tool.DragEnd, handlers.onDragEnd);
|
|
504
506
|
eventBus.off(Events.Tool.SelectionAdd, handlers.onSelectionAdd);
|
|
505
|
-
|
|
507
|
+
eventBus.off(Events.Tool.SelectionRemove, handlers.onSelectionRemove);
|
|
508
|
+
eventBus.off(Events.Tool.SelectionClear, handlers.onSelectionClear);
|
|
509
|
+
eventBus.off(Events.Tool.BoxSelectStart, handlers.onBoxSelectStart);
|
|
510
|
+
eventBus.off(Events.Tool.BoxSelectCommit, handlers.onBoxSelectCommit);
|
|
511
|
+
this._selectionHandlers = null;
|
|
512
|
+
this._boxSelectActive = false;
|
|
506
513
|
}
|
|
507
514
|
|
|
508
515
|
async _handleSelectionAdd(data = {}) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
await this._addImageObjectAsReference(object);
|
|
515
|
-
}
|
|
516
|
+
// Во время box-select каждый mousemove перевыставляет selection и снова
|
|
517
|
+
// эмитит SelectionAdd для тех же id. Финальный набор reference-картинок
|
|
518
|
+
// мы получим из BoxSelectCommit по strict-contains, поэтому здесь молчим.
|
|
519
|
+
if (this._boxSelectActive) return;
|
|
516
520
|
|
|
517
|
-
_handleReferenceDragStart(data = {}) {
|
|
518
521
|
const objectId = data?.object;
|
|
519
522
|
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
520
|
-
this._draggedReferenceObject = isReferenceImageObject(object) ? object : null;
|
|
521
|
-
this._draggedReferenceStartPosition = this._draggedReferenceObject?.position
|
|
522
|
-
? { ...this._draggedReferenceObject.position }
|
|
523
|
-
: null;
|
|
524
|
-
this._updateReferenceDragPreview();
|
|
525
|
-
}
|
|
526
523
|
|
|
527
|
-
|
|
528
|
-
const isDropTarget = this._isBoardCursorOverInput();
|
|
529
|
-
const objectId = data?.object;
|
|
530
|
-
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
531
|
-
const startPosition = this._draggedReferenceStartPosition;
|
|
532
|
-
this._clearReferenceDragState();
|
|
533
|
-
if (!isDropTarget || !isReferenceImageObject(object)) return null;
|
|
524
|
+
if (!isReferenceImageObject(object)) return;
|
|
534
525
|
|
|
535
|
-
this._restoreReferenceObjectPosition(objectId, startPosition);
|
|
536
526
|
await this._addImageObjectAsReference(object);
|
|
537
527
|
}
|
|
538
528
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (typeof updatePosition === 'function') {
|
|
544
|
-
updatePosition.call(this._boardCore, objectId, position, { snap: false });
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
549
|
-
if (object?.position) {
|
|
550
|
-
object.position = { ...position };
|
|
551
|
-
}
|
|
552
|
-
}
|
|
529
|
+
async _handleBoxSelectCommit(data = {}) {
|
|
530
|
+
this._boxSelectActive = false;
|
|
531
|
+
const ids = Array.isArray(data?.selected) ? data.selected : (Array.isArray(data?.objects) ? data.objects : []);
|
|
532
|
+
if (!ids.length || !this._composer) return;
|
|
553
533
|
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
this._hideReferenceDragPreview();
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
const preview = this._ensureReferenceDragPreview(object, src);
|
|
568
|
-
const { clientX, clientY } = this._getBoardCursorClientPosition();
|
|
569
|
-
preview.style.left = `${Math.round(clientX)}px`;
|
|
570
|
-
preview.style.top = `${Math.round(clientY)}px`;
|
|
571
|
-
this._refs?.textarea?.closest?.('.moodboard-chat__input-row')?.classList.add('is-reference-drop-target');
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
_ensureReferenceDragPreview(object, src) {
|
|
575
|
-
if (!this._referenceDragPreview) {
|
|
576
|
-
const preview = document.createElement('img');
|
|
577
|
-
preview.className = 'moodboard-chat__reference-drag-preview';
|
|
578
|
-
preview.alt = getImageObjectFileName(object, src);
|
|
579
|
-
preview.width = REFERENCE_DRAG_PREVIEW_SIZE;
|
|
580
|
-
preview.height = REFERENCE_DRAG_PREVIEW_SIZE;
|
|
581
|
-
document.body.appendChild(preview);
|
|
582
|
-
this._referenceDragPreview = preview;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
if (this._referenceDragPreview.src !== src) {
|
|
586
|
-
this._referenceDragPreview.src = src;
|
|
534
|
+
const all = this._boardCore?.state?.state?.objects ?? [];
|
|
535
|
+
const byId = new Map(all.map((item) => [item?.id, item]));
|
|
536
|
+
const seen = new Set();
|
|
537
|
+
for (const id of ids) {
|
|
538
|
+
if (!id || seen.has(id)) continue;
|
|
539
|
+
seen.add(id);
|
|
540
|
+
const obj = byId.get(id);
|
|
541
|
+
if (!obj || !isReferenceImageObject(obj)) continue;
|
|
542
|
+
await this._addImageObjectAsReference(obj);
|
|
587
543
|
}
|
|
588
|
-
this._referenceDragPreview.alt = getImageObjectFileName(object, src);
|
|
589
|
-
|
|
590
|
-
return this._referenceDragPreview;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
_hideReferenceDragPreview() {
|
|
594
|
-
this._referenceDragPreview?.remove();
|
|
595
|
-
this._referenceDragPreview = null;
|
|
596
|
-
this._refs?.textarea?.closest?.('.moodboard-chat__input-row')?.classList.remove('is-reference-drop-target');
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
_clearReferenceDragState() {
|
|
600
|
-
this._draggedReferenceObject = null;
|
|
601
|
-
this._draggedReferenceStartPosition = null;
|
|
602
|
-
this._boardCursor = null;
|
|
603
|
-
this._hideReferenceDragPreview();
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
_isBoardCursorOverInput() {
|
|
607
|
-
const cursor = this._boardCursor;
|
|
608
|
-
const inputRow = this._refs?.textarea?.closest?.('.moodboard-chat__input-row');
|
|
609
|
-
if (!cursor || !inputRow) return false;
|
|
610
|
-
|
|
611
|
-
const containerRect = this._container.getBoundingClientRect?.();
|
|
612
|
-
const rect = inputRow.getBoundingClientRect();
|
|
613
|
-
const { clientX, clientY } = this._getBoardCursorClientPosition(containerRect);
|
|
614
|
-
|
|
615
|
-
return clientX >= rect.left
|
|
616
|
-
&& clientX <= rect.right
|
|
617
|
-
&& clientY >= rect.top
|
|
618
|
-
&& clientY <= rect.bottom;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
_getBoardCursorClientPosition(containerRect = null) {
|
|
622
|
-
const rect = containerRect || this._container.getBoundingClientRect?.();
|
|
623
|
-
const cursor = this._boardCursor || { x: 0, y: 0 };
|
|
624
|
-
|
|
625
|
-
return {
|
|
626
|
-
clientX: (rect?.left || 0) + cursor.x,
|
|
627
|
-
clientY: (rect?.top || 0) + cursor.y
|
|
628
|
-
};
|
|
629
544
|
}
|
|
630
545
|
|
|
631
546
|
async _addImageObjectAsReference(object) {
|
|
632
547
|
if (!object || !this._composer) return;
|
|
548
|
+
if (this._composer.hasAttachmentForObject?.(object.id)) return;
|
|
633
549
|
|
|
634
550
|
try {
|
|
635
551
|
const file = await createFileFromImageObject(object);
|
|
636
552
|
if (!file) return;
|
|
637
|
-
this._composer.addAttachment(file);
|
|
553
|
+
this._composer.addAttachment(file, { sourceObjectId: object.id });
|
|
638
554
|
this._composer.focus();
|
|
639
555
|
} catch (err) {
|
|
640
556
|
console.warn('[ChatWindow] cannot add selected image reference:', err);
|
|
@@ -67,26 +67,19 @@ function buildInputRow(collect) {
|
|
|
67
67
|
const promptActionsWrapper = document.createElement('div');
|
|
68
68
|
promptActionsWrapper.className = 'moodboard-chat__pill-wrapper';
|
|
69
69
|
|
|
70
|
-
const enhancePrompt = createInputIconButton(
|
|
71
|
-
'enhance-prompt',
|
|
72
|
-
'Улучшить промпт',
|
|
73
|
-
ICONS.enhancePrompt
|
|
74
|
-
);
|
|
75
|
-
enhancePrompt.dataset.empty = 'true';
|
|
76
70
|
const extendPromptField = createInputIconButton(
|
|
77
71
|
'extend-promt-field',
|
|
78
72
|
'Развернуть поле ввода',
|
|
79
73
|
ICONS.extendPromptField
|
|
80
74
|
);
|
|
81
75
|
|
|
82
|
-
promptActionsWrapper.appendChild(enhancePrompt);
|
|
83
76
|
promptActionsWrapper.appendChild(extendPromptField);
|
|
84
77
|
textareaRow.appendChild(textarea);
|
|
85
78
|
textareaRow.appendChild(promptActionsWrapper);
|
|
86
79
|
row.appendChild(attachmentsPreview);
|
|
87
80
|
row.appendChild(textareaRow);
|
|
88
81
|
|
|
89
|
-
collect({ textarea,
|
|
82
|
+
collect({ textarea, extendPromptField, attachmentsPreview });
|
|
90
83
|
return row;
|
|
91
84
|
}
|
|
92
85
|
|
package/src/ui/chat/icons.js
CHANGED
|
@@ -39,9 +39,6 @@ const ATTACHMENTS_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" hei
|
|
|
39
39
|
/** Кнопка отправки в composer */
|
|
40
40
|
const SEND_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M7.453 1.204a.87.87 0 0 1 1.093 0l.066.06 4.962 4.961a.6.6 0 0 1-.849.849L8.6 2.949v11.45a.6.6 0 0 1-1.199 0V2.946L3.273 7.074a.6.6 0 1 1-.847-.849l4.96-4.962z"/></svg>`;
|
|
41
41
|
|
|
42
|
-
/** public/icons/enhance-prompt.svg */
|
|
43
|
-
const ENHANCE_PROMPT_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M8 11.771a.6.6 0 1 1 0 1.2L1.6 13a.6.6 0 0 1 0-1.2zm2-4.37A.6.6 0 1 1 10 8.6H1.6a.6.6 0 1 1 0-1.2zM14.4 3a.6.6 0 1 1 0 1.2H1.6a.6.6 0 1 1 0-1.2zm-1.101 5.5a.346.346 0 0 0-.643 0l-.174.417a2.98 2.98 0 0 1-1.538 1.59l-.489.218a.362.362 0 0 0 0 .658l.519.231c.675.3 1.216.85 1.516 1.538l.168.387a.347.347 0 0 0 .639 0l.168-.387c.3-.689.841-1.237 1.516-1.538l.519-.231a.362.362 0 0 0 0-.658l-.49-.218a3 3 0 0 1-1.538-1.59z"/></svg>`;
|
|
44
|
-
|
|
45
42
|
/** public/icons/extend-promt-field.svg */
|
|
46
43
|
const EXTEND_PROMPT_FIELD_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M9.696 7.151a.599.599 0 1 1-.847-.847L12.55 2.6H9.272a.6.6 0 0 1 0-1.2H14a.6.6 0 0 1 .6.599v4.728a.6.6 0 1 1-1.2 0V3.449zM1.4 9.272a.6.6 0 1 1 1.2 0v3.28l3.704-3.703a.599.599 0 1 1 .847.847L3.45 13.4h3.279a.6.6 0 0 1 0 1.2H2A.6.6 0 0 1 1.4 14z"/></svg>`;
|
|
47
44
|
|
|
@@ -69,7 +66,6 @@ export const ICONS = {
|
|
|
69
66
|
palette: svg('<path d="M12 3a9 9 0 0 0 0 18c1.7 0 2-1 2-2 0-1.5 1-2 2-2h2a3 3 0 0 0 3-3c0-5-4-9-9-9z"/><circle cx="7.5" cy="11" r="1"/><circle cx="11" cy="7" r="1"/><circle cx="15" cy="7" r="1"/><circle cx="17.5" cy="11" r="1"/>'),
|
|
70
67
|
attach: ATTACHMENTS_ICON,
|
|
71
68
|
send: SEND_ICON,
|
|
72
|
-
enhancePrompt: ENHANCE_PROMPT_ICON,
|
|
73
69
|
extendPromptField: EXTEND_PROMPT_FIELD_ICON,
|
|
74
70
|
sliders: svg('<path d="M4 7h10M18 7h2M4 17h2M10 17h10"/><circle cx="16" cy="7" r="2"/><circle cx="8" cy="17" r="2"/>'),
|
|
75
71
|
chevronDown: svg('<path d="M6 9l6 6 6-6"/>'),
|