@sequent-org/moodboard 1.4.30 → 1.4.31
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 +3 -1
- package/src/core/PixiEngine.js +34 -5
- package/src/core/bootstrap/CoreInitializer.js +4 -0
- package/src/core/commands/CreateConnectorCommand.js +25 -0
- package/src/core/commands/GroupMoveCommand.js +2 -2
- package/src/core/commands/MoveObjectCommand.js +1 -1
- package/src/core/commands/UpdateConnectorCommand.js +38 -0
- package/src/core/events/Events.js +1 -0
- package/src/mindmap/MindmapCompoundContract.js +1 -0
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +14 -0
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +18 -0
- package/src/objects/ConnectorObject.js +85 -0
- package/src/objects/DrawingObject.js +47 -0
- package/src/objects/MindmapObject.js +21 -3
- package/src/objects/NoteObject.js +16 -8
- package/src/objects/ObjectFactory.js +3 -1
- package/src/objects/ShapeObject.js +1 -1
- package/src/services/ConnectorBindingResolver.js +204 -0
- package/src/services/ai/AiClient.js +30 -2
- package/src/services/ai/ChatSessionController.js +1 -0
- package/src/tools/ToolManager.js +3 -0
- package/src/tools/manager/PointerGestureController.js +206 -0
- package/src/tools/manager/ToolEventRouter.js +10 -0
- package/src/tools/manager/ToolManagerGuards.js +3 -1
- package/src/tools/manager/ToolManagerLifecycle.js +70 -58
- package/src/tools/object-tools/ConnectorTool.js +147 -0
- package/src/tools/object-tools/PlacementTool.js +2 -2
- package/src/tools/object-tools/connector/ConnectorDragController.js +296 -0
- package/src/tools/object-tools/connector/connectorGesture.js +108 -0
- package/src/tools/object-tools/placement/GhostController.js +4 -4
- package/src/tools/object-tools/placement/PlacementEventsBridge.js +2 -2
- package/src/tools/object-tools/placement/PlacementInputRouter.js +5 -5
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +11 -2
- package/src/tools/object-tools/selection/SelectInputRouter.js +33 -4
- package/src/tools/object-tools/selection/SelectToolLifecycleController.js +12 -0
- package/src/tools/object-tools/selection/SelectToolSetup.js +3 -0
- package/src/tools/object-tools/selection/TextEditorDomFactory.js +1 -2
- package/src/tools/object-tools/selection/TextEditorSyncService.js +4 -4
- package/src/tools/object-tools/selection/TextInlineEditorController.js +21 -3
- package/src/tools/object-tools/selection/TransformInteractionController.js +4 -6
- package/src/ui/HtmlTextLayer.js +212 -5
- package/src/ui/animation/HoverLiftController.js +395 -0
- package/src/ui/chat/ChatComposer.js +0 -5
- package/src/ui/chat/ChatWindow.js +167 -35
- package/src/ui/chat/icons.js +17 -1
- package/src/ui/connectors/ConnectionAnchorsLayer.js +231 -0
- package/src/ui/connectors/ConnectorLayer.js +251 -0
- package/src/ui/handles/HandlesDomRenderer.js +11 -7
- package/src/ui/handles/HandlesInteractionController.js +65 -34
- package/src/ui/handles/HandlesPositioningService.js +41 -6
- package/src/ui/mindmap/MindmapCollapseGraph.js +169 -0
- package/src/ui/mindmap/MindmapCollapseLayer.js +380 -0
- package/src/ui/mindmap/MindmapConnectionLayer.js +50 -25
- package/src/ui/mindmap/MindmapHtmlTextLayer.js +223 -2
- package/src/ui/mindmap/MindmapLayoutConfig.js +12 -0
- package/src/ui/styles/chat.css +1 -0
- package/src/ui/styles/toolbar.css +6 -0
- package/src/ui/styles/workspace.css +83 -21
- package/src/ui/toolbar/ToolbarPopupsController.js +1 -1
|
@@ -97,11 +97,6 @@ export class ChatComposer {
|
|
|
97
97
|
if (!trimmed && !hasAttachments) return;
|
|
98
98
|
if (this._send.dataset.state === 'streaming') return;
|
|
99
99
|
const attachments = [...this._attachments];
|
|
100
|
-
this._attachments = [];
|
|
101
|
-
this._textarea.value = '';
|
|
102
|
-
this._resizeTextarea();
|
|
103
|
-
this._renderAttachmentsPreview();
|
|
104
|
-
this._refreshSendState();
|
|
105
100
|
this._handlers.onSubmit?.(trimmed, attachments);
|
|
106
101
|
}
|
|
107
102
|
|
|
@@ -56,6 +56,10 @@ const BOARD_IMAGE_STEP = 320;
|
|
|
56
56
|
const BOARD_IMAGE_GAP = BOARD_IMAGE_STEP - BOARD_IMAGE_WIDTH;
|
|
57
57
|
// Скорость перестановки AI-изображений на доске и въезда заглушек регулируется здесь.
|
|
58
58
|
const BOARD_IMAGE_REARRANGE_MS = 520;
|
|
59
|
+
// На сколько колонок «справа» появляется блок-заглушка перед въездом в финальную позицию.
|
|
60
|
+
const BOARD_IMAGE_PENDING_ENTER_FACTOR = 1.6;
|
|
61
|
+
// Каскад между блоками одного батча (мс): пользователь видит, что они приезжают друг за другом.
|
|
62
|
+
const BOARD_IMAGE_PENDING_STAGGER_MS = 90;
|
|
59
63
|
const REFERENCE_DRAG_PREVIEW_SIZE = 96;
|
|
60
64
|
|
|
61
65
|
const MODEL_OPTIONS = [
|
|
@@ -68,25 +72,25 @@ const MODEL_OPTIONS = [
|
|
|
68
72
|
{
|
|
69
73
|
id: 'yandex',
|
|
70
74
|
label: 'Алиса',
|
|
71
|
-
icon:
|
|
75
|
+
icon: ICONS.modelAlice,
|
|
72
76
|
description: 'YandexGPT'
|
|
73
77
|
},
|
|
74
78
|
{
|
|
75
79
|
id: 'gpt',
|
|
76
80
|
label: 'GPT',
|
|
77
|
-
icon:
|
|
81
|
+
icon: ICONS.modelGpt,
|
|
78
82
|
description: 'OpenAI'
|
|
79
83
|
},
|
|
80
84
|
{
|
|
81
85
|
id: 'google',
|
|
82
86
|
label: 'Google',
|
|
83
|
-
icon:
|
|
87
|
+
icon: ICONS.modelGoogle,
|
|
84
88
|
description: 'Gemini'
|
|
85
89
|
},
|
|
86
90
|
{
|
|
87
91
|
id: 'qwen',
|
|
88
92
|
label: 'Qwen',
|
|
89
|
-
icon:
|
|
93
|
+
icon: ICONS.modelQwen,
|
|
90
94
|
description: 'Alibaba'
|
|
91
95
|
}
|
|
92
96
|
];
|
|
@@ -137,14 +141,16 @@ export class ChatWindow {
|
|
|
137
141
|
this._attached = false;
|
|
138
142
|
this._boardImageMessageIds = new Set();
|
|
139
143
|
this._shiftedForImageBatchKeys = new Set();
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
144
|
+
this._boardImageShiftHistory = new Map();
|
|
145
|
+
this._pendingOverlays = new Map();
|
|
146
|
+
this._pendingOverlayTimers = new Map();
|
|
142
147
|
this._boardImageShiftAnimations = new Map();
|
|
143
148
|
this._boardCursor = null;
|
|
144
149
|
this._draggedReferenceObject = null;
|
|
145
150
|
this._draggedReferenceStartPosition = null;
|
|
146
151
|
this._referenceDragPreview = null;
|
|
147
152
|
this._referenceDragHandlers = null;
|
|
153
|
+
this._clearSelectionOnSendClick = null;
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
attach() {
|
|
@@ -165,10 +171,15 @@ export class ChatWindow {
|
|
|
165
171
|
statusBar: this._refs.statusBar
|
|
166
172
|
},
|
|
167
173
|
{
|
|
168
|
-
onSubmit: (text, attachments) =>
|
|
174
|
+
onSubmit: (text, attachments) => {
|
|
175
|
+
this._clearBoardSelection();
|
|
176
|
+
return this._session.send(text, { ...this._getImageRequestOptions(), referenceImages: attachments });
|
|
177
|
+
},
|
|
169
178
|
onAbort: () => this._session.abort()
|
|
170
179
|
}
|
|
171
180
|
);
|
|
181
|
+
this._clearSelectionOnSendClick = () => this._clearBoardSelection();
|
|
182
|
+
this._refs.send.addEventListener('click', this._clearSelectionOnSendClick);
|
|
172
183
|
this._composer.attach();
|
|
173
184
|
this._attachReferenceDragEvents();
|
|
174
185
|
|
|
@@ -255,9 +266,13 @@ export class ChatWindow {
|
|
|
255
266
|
this._cancelBoardImageShiftAnimations();
|
|
256
267
|
this._clearReferenceDragState();
|
|
257
268
|
if (this._unsubscribe) { this._unsubscribe(); this._unsubscribe = null; }
|
|
269
|
+
if (this._clearSelectionOnSendClick && this._refs?.send) {
|
|
270
|
+
this._refs.send.removeEventListener('click', this._clearSelectionOnSendClick);
|
|
271
|
+
this._clearSelectionOnSendClick = null;
|
|
272
|
+
}
|
|
258
273
|
this._detachReferenceDragEvents();
|
|
259
274
|
this._shiftedForImageBatchKeys.clear();
|
|
260
|
-
this.
|
|
275
|
+
this._boardImageShiftHistory.clear();
|
|
261
276
|
this._composer?.destroy();
|
|
262
277
|
this._extendedPromptModal?.destroy();
|
|
263
278
|
this._contentTypeMenu?.destroy();
|
|
@@ -282,6 +297,15 @@ export class ChatWindow {
|
|
|
282
297
|
this.detach();
|
|
283
298
|
}
|
|
284
299
|
|
|
300
|
+
_clearBoardSelection() {
|
|
301
|
+
if (typeof this._boardCore?.selectTool?.clearSelection === 'function') {
|
|
302
|
+
this._boardCore.selectTool.clearSelection();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
this._boardCore?.eventBus?.emit(Events.Tool.SelectionClear);
|
|
307
|
+
}
|
|
308
|
+
|
|
285
309
|
_updateCountPillIcon() {
|
|
286
310
|
const active = COUNT_OPTIONS.find((o) => o.id === this._countId);
|
|
287
311
|
if (!active) return;
|
|
@@ -314,6 +338,9 @@ export class ChatWindow {
|
|
|
314
338
|
_render(state) {
|
|
315
339
|
if (!this._attached && !this._refs) return;
|
|
316
340
|
this._syncGeneratedImagesToBoard(state.messages);
|
|
341
|
+
if (state.status !== 'streaming') {
|
|
342
|
+
this._revertFailedBatchShifts(state.messages);
|
|
343
|
+
}
|
|
317
344
|
this._messageList.render(state.messages);
|
|
318
345
|
this._contentTypeMenu.refresh();
|
|
319
346
|
this._modelMenu.refresh();
|
|
@@ -327,9 +354,17 @@ export class ChatWindow {
|
|
|
327
354
|
}
|
|
328
355
|
|
|
329
356
|
_updatePendingImages(messages) {
|
|
330
|
-
this._clearPendingOverlays();
|
|
331
|
-
|
|
332
357
|
const pending = (messages || []).filter((m) => m.pending && m.kind === 'image');
|
|
358
|
+
const activeIds = new Set(pending.map((m) => m.id));
|
|
359
|
+
|
|
360
|
+
for (const [id, record] of this._pendingOverlays) {
|
|
361
|
+
if (!activeIds.has(id)) {
|
|
362
|
+
record.el.remove();
|
|
363
|
+
this._pendingOverlays.delete(id);
|
|
364
|
+
this._cancelPendingOverlayTimer(id);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
333
368
|
if (pending.length === 0) return;
|
|
334
369
|
|
|
335
370
|
const world = this._boardCore?.pixi?.worldLayer || this._boardCore?.pixi?.app?.stage;
|
|
@@ -341,27 +376,31 @@ export class ChatWindow {
|
|
|
341
376
|
const ratio = wr / hr;
|
|
342
377
|
const wScreen = Math.round(BOARD_IMAGE_WIDTH * s);
|
|
343
378
|
const hScreen = Math.round(wScreen / ratio);
|
|
379
|
+
const enterDistance = Math.round(BOARD_IMAGE_STEP * s * BOARD_IMAGE_PENDING_ENTER_FACTOR);
|
|
344
380
|
|
|
345
|
-
|
|
381
|
+
let newIndex = 0;
|
|
382
|
+
pending.forEach((message) => {
|
|
346
383
|
const slot = this._getImageBatchSlot(messages, message.id, s);
|
|
347
384
|
const left = Math.round(slot.x - wScreen / 2);
|
|
348
385
|
const top = Math.round(slot.y - hScreen / 2);
|
|
349
386
|
|
|
387
|
+
const existing = this._pendingOverlays.get(message.id);
|
|
388
|
+
if (existing) {
|
|
389
|
+
const el = existing.el;
|
|
390
|
+
el.style.left = `${left}px`;
|
|
391
|
+
el.style.top = `${top}px`;
|
|
392
|
+
el.style.width = `${wScreen}px`;
|
|
393
|
+
el.style.height = `${hScreen}px`;
|
|
394
|
+
el.style.setProperty('--moodboard-chat-board-animation-ms', `${BOARD_IMAGE_REARRANGE_MS}ms`);
|
|
395
|
+
el.style.setProperty('--moodboard-chat-pending-enter-x', `${enterDistance}px`);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
350
399
|
const overlay = document.createElement('div');
|
|
351
|
-
overlay.className = 'moodboard-chat__pending-overlay';
|
|
400
|
+
overlay.className = 'moodboard-chat__pending-overlay moodboard-chat__pending-overlay--enter';
|
|
352
401
|
overlay.style.cssText = `left:${left}px;top:${top}px;width:${wScreen}px;height:${hScreen}px`;
|
|
353
402
|
overlay.style.setProperty('--moodboard-chat-board-animation-ms', `${BOARD_IMAGE_REARRANGE_MS}ms`);
|
|
354
|
-
overlay.style.setProperty('--moodboard-chat-pending-enter-x', `${
|
|
355
|
-
|
|
356
|
-
if (!this._pendingOverlayMessageIds.has(message.id)) {
|
|
357
|
-
overlay.classList.add('moodboard-chat__pending-overlay--enter');
|
|
358
|
-
this._pendingOverlayMessageIds.add(message.id);
|
|
359
|
-
this._scheduleAnimationFrame(() => {
|
|
360
|
-
if (overlay.isConnected) {
|
|
361
|
-
overlay.classList.add('moodboard-chat__pending-overlay--entered');
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
}
|
|
403
|
+
overlay.style.setProperty('--moodboard-chat-pending-enter-x', `${enterDistance}px`);
|
|
365
404
|
|
|
366
405
|
const label = document.createElement('span');
|
|
367
406
|
label.className = 'moodboard-chat__pending-image-label';
|
|
@@ -369,15 +408,52 @@ export class ChatWindow {
|
|
|
369
408
|
overlay.appendChild(label);
|
|
370
409
|
|
|
371
410
|
document.body.appendChild(overlay);
|
|
372
|
-
|
|
411
|
+
|
|
412
|
+
// Принудительный reflow: фиксируем стартовое состояние (translateX справа + opacity 0)
|
|
413
|
+
// в layout до переключения класса. Без этого браузер может смерджить два состояния
|
|
414
|
+
// в один кадр и transition не запустится — заглушка появится мгновенно.
|
|
415
|
+
void overlay.offsetWidth;
|
|
416
|
+
|
|
417
|
+
this._pendingOverlays.set(message.id, { el: overlay });
|
|
418
|
+
|
|
419
|
+
const stagger = newIndex * BOARD_IMAGE_PENDING_STAGGER_MS;
|
|
420
|
+
newIndex += 1;
|
|
421
|
+
|
|
422
|
+
const trigger = () => {
|
|
423
|
+
if (!overlay.isConnected) return;
|
|
424
|
+
overlay.classList.remove('moodboard-chat__pending-overlay--enter');
|
|
425
|
+
overlay.classList.add('moodboard-chat__pending-overlay--entered');
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
if (stagger > 0) {
|
|
429
|
+
const timer = setTimeout(() => {
|
|
430
|
+
this._pendingOverlayTimers.delete(message.id);
|
|
431
|
+
this._scheduleAnimationFrame(trigger);
|
|
432
|
+
}, stagger);
|
|
433
|
+
this._pendingOverlayTimers.set(message.id, timer);
|
|
434
|
+
} else {
|
|
435
|
+
this._scheduleAnimationFrame(trigger);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
_cancelPendingOverlayTimer(id) {
|
|
441
|
+
const timer = this._pendingOverlayTimers.get(id);
|
|
442
|
+
if (timer !== undefined) {
|
|
443
|
+
clearTimeout(timer);
|
|
444
|
+
this._pendingOverlayTimers.delete(id);
|
|
373
445
|
}
|
|
374
446
|
}
|
|
375
447
|
|
|
376
448
|
_clearPendingOverlays() {
|
|
377
|
-
for (const
|
|
378
|
-
el.remove();
|
|
449
|
+
for (const record of this._pendingOverlays.values()) {
|
|
450
|
+
record.el.remove();
|
|
451
|
+
}
|
|
452
|
+
this._pendingOverlays.clear();
|
|
453
|
+
for (const timer of this._pendingOverlayTimers.values()) {
|
|
454
|
+
clearTimeout(timer);
|
|
379
455
|
}
|
|
380
|
-
this.
|
|
456
|
+
this._pendingOverlayTimers.clear();
|
|
381
457
|
}
|
|
382
458
|
|
|
383
459
|
_getImageRequestOptions() {
|
|
@@ -406,11 +482,15 @@ export class ChatWindow {
|
|
|
406
482
|
const onDragEnd = (data) => {
|
|
407
483
|
void this._handleReferenceDragEnd(data);
|
|
408
484
|
};
|
|
485
|
+
const onSelectionAdd = (data) => {
|
|
486
|
+
void this._handleSelectionAdd(data);
|
|
487
|
+
};
|
|
409
488
|
|
|
410
|
-
this._referenceDragHandlers = { onCursorMove, onDragStart, onDragEnd };
|
|
489
|
+
this._referenceDragHandlers = { onCursorMove, onDragStart, onDragEnd, onSelectionAdd };
|
|
411
490
|
eventBus.on(Events.UI.CursorMove, onCursorMove);
|
|
412
491
|
eventBus.on(Events.Tool.DragStart, onDragStart);
|
|
413
492
|
eventBus.on(Events.Tool.DragEnd, onDragEnd);
|
|
493
|
+
eventBus.on(Events.Tool.SelectionAdd, onSelectionAdd);
|
|
414
494
|
}
|
|
415
495
|
|
|
416
496
|
_detachReferenceDragEvents() {
|
|
@@ -421,9 +501,19 @@ export class ChatWindow {
|
|
|
421
501
|
eventBus.off(Events.UI.CursorMove, handlers.onCursorMove);
|
|
422
502
|
eventBus.off(Events.Tool.DragStart, handlers.onDragStart);
|
|
423
503
|
eventBus.off(Events.Tool.DragEnd, handlers.onDragEnd);
|
|
504
|
+
eventBus.off(Events.Tool.SelectionAdd, handlers.onSelectionAdd);
|
|
424
505
|
this._referenceDragHandlers = null;
|
|
425
506
|
}
|
|
426
507
|
|
|
508
|
+
async _handleSelectionAdd(data = {}) {
|
|
509
|
+
const objectId = data?.object;
|
|
510
|
+
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
511
|
+
|
|
512
|
+
if (!isReferenceImageObject(object)) return;
|
|
513
|
+
|
|
514
|
+
await this._addImageObjectAsReference(object);
|
|
515
|
+
}
|
|
516
|
+
|
|
427
517
|
_handleReferenceDragStart(data = {}) {
|
|
428
518
|
const objectId = data?.object;
|
|
429
519
|
const object = this._boardCore?.state?.state?.objects?.find((item) => item?.id === objectId);
|
|
@@ -591,7 +681,7 @@ export class ChatWindow {
|
|
|
591
681
|
if (this._shiftedForImageBatchKeys.has(batchKey)) return;
|
|
592
682
|
|
|
593
683
|
this._shiftedForImageBatchKeys.add(batchKey);
|
|
594
|
-
this._shiftBoardAiImagesLeft(this._getImageBatchWorldBounds(messages, messageId, scale));
|
|
684
|
+
this._shiftBoardAiImagesLeft(this._getImageBatchWorldBounds(messages, messageId, scale), batchKey);
|
|
595
685
|
}
|
|
596
686
|
|
|
597
687
|
_getImageBatchWorldBounds(messages, messageId, scale = 1) {
|
|
@@ -611,7 +701,7 @@ export class ChatWindow {
|
|
|
611
701
|
};
|
|
612
702
|
}
|
|
613
703
|
|
|
614
|
-
_shiftBoardAiImagesLeft(nextBatchBounds) {
|
|
704
|
+
_shiftBoardAiImagesLeft(nextBatchBounds, batchKey) {
|
|
615
705
|
const aiObjects = this._getBoardAiImageObjects();
|
|
616
706
|
if (aiObjects.length === 0 || !nextBatchBounds) return;
|
|
617
707
|
|
|
@@ -621,14 +711,56 @@ export class ChatWindow {
|
|
|
621
711
|
|
|
622
712
|
const ids = new Set(aiObjects.map((object) => object.id));
|
|
623
713
|
const objects = this._boardCore?.state?.state?.objects;
|
|
714
|
+
const shiftRecord = [];
|
|
624
715
|
for (const id of ids) {
|
|
625
716
|
const obj = objects?.find((item) => item.id === id);
|
|
626
717
|
if (obj?.position) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
718
|
+
const from = { x: obj.position.x, y: obj.position.y };
|
|
719
|
+
const to = { x: Math.round(obj.position.x - shift), y: obj.position.y };
|
|
720
|
+
shiftRecord.push({ id, from });
|
|
721
|
+
this._animateBoardImageToPosition(id, from, to);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (batchKey && shiftRecord.length > 0) {
|
|
726
|
+
this._boardImageShiftHistory.set(batchKey, shiftRecord);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
_revertBoardImageShiftForBatch(batchKey) {
|
|
731
|
+
const record = this._boardImageShiftHistory.get(batchKey);
|
|
732
|
+
if (!record) return;
|
|
733
|
+
|
|
734
|
+
const objects = this._boardCore?.state?.state?.objects;
|
|
735
|
+
for (const { id, from } of record) {
|
|
736
|
+
const obj = objects?.find((item) => item.id === id);
|
|
737
|
+
if (obj?.position) {
|
|
738
|
+
this._animateBoardImageToPosition(id, obj.position, from);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
this._boardImageShiftHistory.delete(batchKey);
|
|
743
|
+
this._shiftedForImageBatchKeys.delete(batchKey);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
_revertFailedBatchShifts(messages) {
|
|
747
|
+
if (this._boardImageShiftHistory.size === 0) return;
|
|
748
|
+
|
|
749
|
+
for (const batchKey of [...this._boardImageShiftHistory.keys()]) {
|
|
750
|
+
if (batchKey === 'unknown') continue;
|
|
751
|
+
|
|
752
|
+
const messageIds = batchKey.split('|');
|
|
753
|
+
const batchMessages = messageIds
|
|
754
|
+
.map((id) => messages?.find((m) => m.id === id))
|
|
755
|
+
.filter(Boolean);
|
|
756
|
+
|
|
757
|
+
if (batchMessages.length === 0) continue;
|
|
758
|
+
|
|
759
|
+
const allResolved = batchMessages.every((m) => !m.pending);
|
|
760
|
+
const anyImage = batchMessages.some((m) => Boolean(m.imageBase64));
|
|
761
|
+
|
|
762
|
+
if (allResolved && !anyImage) {
|
|
763
|
+
this._revertBoardImageShiftForBatch(batchKey);
|
|
632
764
|
}
|
|
633
765
|
}
|
|
634
766
|
}
|
package/src/ui/chat/icons.js
CHANGED
|
@@ -45,6 +45,18 @@ const ENHANCE_PROMPT_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="16"
|
|
|
45
45
|
/** public/icons/extend-promt-field.svg */
|
|
46
46
|
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
47
|
|
|
48
|
+
/** public/icons/google.svg — цветной логотип Google 36×36 */
|
|
49
|
+
const MODEL_GOOGLE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="none" viewBox="0 0 36 36"><path fill="#4285F4" d="M29.251 18.49c0-.813-.073-1.596-.209-2.347H18.23v4.445h6.179c-.272 1.43-1.086 2.64-2.307 3.455v2.89h3.726c2.17-2.003 3.423-4.946 3.423-8.442"/><path fill="#34A853" d="M18.229 29.71c3.1 0 5.698-1.023 7.597-2.776l-3.725-2.89c-1.023.688-2.328 1.105-3.872 1.105-2.985 0-5.52-2.014-6.429-4.727H7.98v2.964c1.89 3.746 5.761 6.324 10.249 6.324"/><path fill="#FBBC05" d="M11.801 20.412a6.9 6.9 0 0 1-.365-2.181c0-.762.136-1.492.365-2.181v-2.964h-3.82A11.34 11.34 0 0 0 6.75 18.23c0 1.858.449 3.6 1.231 5.145l2.975-2.317z"/><path fill="#EA4335" d="M18.229 11.321c1.69 0 3.193.585 4.393 1.712l3.288-3.288c-1.994-1.857-4.582-2.995-7.681-2.995-4.488 0-8.36 2.578-10.249 6.335l3.82 2.964c.908-2.714 3.444-4.728 6.429-4.728"/></svg>`;
|
|
50
|
+
|
|
51
|
+
/** Placeholder OpenAI GPT — буква G в круге, 36×36 */
|
|
52
|
+
const MODEL_GPT_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true"><circle cx="18" cy="18" r="16" fill="#10a37f"/><text x="18" y="23" text-anchor="middle" font-size="16" font-family="Arial,sans-serif" fill="#fff" font-weight="bold">G</text></svg>`;
|
|
53
|
+
|
|
54
|
+
/** Placeholder Alibaba Qwen — буква Q в круге, 36×36 */
|
|
55
|
+
const MODEL_QWEN_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true"><circle cx="18" cy="18" r="16" fill="#6e42ca"/><text x="18" y="23" text-anchor="middle" font-size="16" font-family="Arial,sans-serif" fill="#fff" font-weight="bold">Q</text></svg>`;
|
|
56
|
+
|
|
57
|
+
/** Placeholder Yandex Alice — буква А в круге, 36×36 */
|
|
58
|
+
const MODEL_ALICE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true"><circle cx="18" cy="18" r="16" fill="#fc3f1d"/><text x="18" y="23" text-anchor="middle" font-size="16" font-family="Arial,sans-serif" fill="#fff" font-weight="bold">А</text></svg>`;
|
|
59
|
+
|
|
48
60
|
export const ICONS = {
|
|
49
61
|
image: IMAGE_ICON,
|
|
50
62
|
video: VIDEO_ICON,
|
|
@@ -63,7 +75,11 @@ export const ICONS = {
|
|
|
63
75
|
chevronDown: svg('<path d="M6 9l6 6 6-6"/>'),
|
|
64
76
|
trash: svg('<path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M6 6l1 14a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-14"/>'),
|
|
65
77
|
close: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"><path fill="currentColor" d="M13.575 12.726a.6.6 0 0 1-.849.849zm-.849-10.3a.6.6 0 0 1 .849.848L8.848 7.999l4.727 4.727-.425.424-.424.425-4.727-4.727-4.725 4.727a.6.6 0 0 1-.848-.849l4.726-4.727-4.726-4.725a.599.599 0 1 1 .848-.848l4.725 4.726z"></path></svg>`,
|
|
66
|
-
sparkles: svg('<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/>')
|
|
78
|
+
sparkles: svg('<path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/>'),
|
|
79
|
+
modelGoogle: MODEL_GOOGLE_ICON,
|
|
80
|
+
modelGpt: MODEL_GPT_ICON,
|
|
81
|
+
modelQwen: MODEL_QWEN_ICON,
|
|
82
|
+
modelAlice: MODEL_ALICE_ICON,
|
|
67
83
|
};
|
|
68
84
|
|
|
69
85
|
/**
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Events } from '../../core/events/Events.js';
|
|
2
|
+
import { HandlesPositioningService } from '../handles/HandlesPositioningService.js';
|
|
3
|
+
import { ConnectorDragController } from '../../tools/object-tools/connector/ConnectorDragController.js';
|
|
4
|
+
|
|
5
|
+
const ALLOWED_TYPES = new Set(['shape', 'note', 'image', 'text', 'simple-text', 'file']);
|
|
6
|
+
|
|
7
|
+
export class ConnectionAnchorsLayer {
|
|
8
|
+
constructor(container, eventBus, core) {
|
|
9
|
+
this.container = container;
|
|
10
|
+
this.eventBus = eventBus;
|
|
11
|
+
this.core = core;
|
|
12
|
+
this.layer = null;
|
|
13
|
+
this.positioningService = new HandlesPositioningService(this);
|
|
14
|
+
|
|
15
|
+
this.subscriptions = [];
|
|
16
|
+
this._eventsAttached = false;
|
|
17
|
+
|
|
18
|
+
this.hoveredObjectId = null;
|
|
19
|
+
this._dragController = null;
|
|
20
|
+
this._onAnchorPointerDown = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
attach() {
|
|
24
|
+
if (!this.layer) {
|
|
25
|
+
this.layer = document.createElement('div');
|
|
26
|
+
this.layer.className = 'mb-connection-anchors-layer';
|
|
27
|
+
Object.assign(this.layer.style, {
|
|
28
|
+
position: 'absolute',
|
|
29
|
+
left: '0',
|
|
30
|
+
top: '0',
|
|
31
|
+
width: '100%',
|
|
32
|
+
height: '100%',
|
|
33
|
+
pointerEvents: 'none',
|
|
34
|
+
zIndex: '35'
|
|
35
|
+
});
|
|
36
|
+
this.container.appendChild(this.layer);
|
|
37
|
+
|
|
38
|
+
this._dragController = new ConnectorDragController(this.core, this.eventBus);
|
|
39
|
+
this._onAnchorPointerDown = (e) => {
|
|
40
|
+
if (!e.target.dataset.connectorAnchor) return;
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
e.stopPropagation();
|
|
43
|
+
this._dragController.startFromAnchor(e);
|
|
44
|
+
};
|
|
45
|
+
this.layer.addEventListener('pointerdown', this._onAnchorPointerDown);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this._attachEvents();
|
|
49
|
+
this.update();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
destroy() {
|
|
53
|
+
this._detachEvents();
|
|
54
|
+
if (this._onAnchorPointerDown && this.layer) {
|
|
55
|
+
this.layer.removeEventListener('pointerdown', this._onAnchorPointerDown);
|
|
56
|
+
this._onAnchorPointerDown = null;
|
|
57
|
+
}
|
|
58
|
+
if (this._dragController) {
|
|
59
|
+
this._dragController.destroy();
|
|
60
|
+
this._dragController = null;
|
|
61
|
+
}
|
|
62
|
+
if (this.layer && this.layer.parentNode) {
|
|
63
|
+
this.layer.parentNode.removeChild(this.layer);
|
|
64
|
+
}
|
|
65
|
+
this.layer = null;
|
|
66
|
+
this.eventBus = null;
|
|
67
|
+
this.core = null;
|
|
68
|
+
this.container = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_attachEvents() {
|
|
72
|
+
if (this._eventsAttached) return;
|
|
73
|
+
|
|
74
|
+
const bindings = [
|
|
75
|
+
[Events.Object.Hover, (e) => {
|
|
76
|
+
this.hoveredObjectId = e.objectId || null;
|
|
77
|
+
this.update();
|
|
78
|
+
}],
|
|
79
|
+
[Events.Tool.SelectionAdd, () => this.update()],
|
|
80
|
+
[Events.Tool.SelectionRemove, () => this.update()],
|
|
81
|
+
[Events.Tool.SelectionClear, () => this.update()],
|
|
82
|
+
[Events.Object.Created, () => this.update()],
|
|
83
|
+
[Events.Object.Deleted, () => this.update()],
|
|
84
|
+
[Events.Object.Updated, () => this.update()],
|
|
85
|
+
[Events.Object.StateChanged, () => this.update()],
|
|
86
|
+
[Events.Tool.DragUpdate, () => this.update()],
|
|
87
|
+
[Events.Tool.DragEnd, () => this.update()],
|
|
88
|
+
[Events.Tool.ResizeUpdate, () => this.update()],
|
|
89
|
+
[Events.Tool.ResizeEnd, () => this.update()],
|
|
90
|
+
[Events.Tool.GroupDragUpdate, () => this.update()],
|
|
91
|
+
[Events.Tool.GroupResizeUpdate, () => this.update()],
|
|
92
|
+
[Events.Tool.RotateUpdate, () => this.update()],
|
|
93
|
+
[Events.Tool.PanUpdate, () => this.update()],
|
|
94
|
+
[Events.UI.ZoomPercent, () => this.update()],
|
|
95
|
+
[Events.History.Changed, () => this.update()],
|
|
96
|
+
[Events.Board.Loaded, () => this.update()]
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
bindings.forEach(([event, handler]) => {
|
|
100
|
+
this.eventBus.on(event, handler);
|
|
101
|
+
this.subscriptions.push([event, handler]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
this._eventsAttached = true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_detachEvents() {
|
|
108
|
+
if (typeof this.eventBus?.off !== 'function') {
|
|
109
|
+
this.subscriptions = [];
|
|
110
|
+
this._eventsAttached = false;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.subscriptions.forEach(([event, handler]) => {
|
|
114
|
+
this.eventBus.off(event, handler);
|
|
115
|
+
});
|
|
116
|
+
this.subscriptions = [];
|
|
117
|
+
this._eventsAttached = false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_getSingleSelectionWorldBounds(id) {
|
|
121
|
+
const positionData = { objectId: id, position: null };
|
|
122
|
+
const sizeData = { objectId: id, size: null };
|
|
123
|
+
this.eventBus.emit(Events.Tool.GetObjectPosition, positionData);
|
|
124
|
+
this.eventBus.emit(Events.Tool.GetObjectSize, sizeData);
|
|
125
|
+
|
|
126
|
+
if (positionData.position && sizeData.size) {
|
|
127
|
+
return {
|
|
128
|
+
x: positionData.position.x,
|
|
129
|
+
y: positionData.position.y,
|
|
130
|
+
width: sizeData.size.width,
|
|
131
|
+
height: sizeData.size.height,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
update() {
|
|
138
|
+
if (!this.layer) return;
|
|
139
|
+
this.layer.innerHTML = '';
|
|
140
|
+
|
|
141
|
+
const selection = Array.from(this.core?.selectTool?.selectedObjects || []);
|
|
142
|
+
let selectedId = null;
|
|
143
|
+
if (selection.length === 1) {
|
|
144
|
+
selectedId = selection[0];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const targets = new Set();
|
|
148
|
+
if (this.hoveredObjectId) targets.add(this.hoveredObjectId);
|
|
149
|
+
if (selectedId) targets.add(selectedId);
|
|
150
|
+
|
|
151
|
+
targets.forEach(id => {
|
|
152
|
+
this._renderAnchorsFor(id);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
_renderAnchorsFor(id) {
|
|
157
|
+
const req = { objectId: id, pixiObject: null };
|
|
158
|
+
this.eventBus.emit(Events.Tool.GetObjectPixi, req);
|
|
159
|
+
const mbType = req.pixiObject?._mb?.type;
|
|
160
|
+
|
|
161
|
+
if (!mbType || !ALLOWED_TYPES.has(mbType)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const worldBounds = this._getSingleSelectionWorldBounds(id);
|
|
166
|
+
if (!worldBounds) return;
|
|
167
|
+
|
|
168
|
+
const cssRect = this.positioningService.worldBoundsToCssRect(worldBounds);
|
|
169
|
+
|
|
170
|
+
const left = Math.round(cssRect.left);
|
|
171
|
+
const top = Math.round(cssRect.top);
|
|
172
|
+
const width = Math.max(1, Math.round(cssRect.width));
|
|
173
|
+
const height = Math.max(1, Math.round(cssRect.height));
|
|
174
|
+
|
|
175
|
+
const rotationData = { objectId: id, rotation: 0 };
|
|
176
|
+
this.eventBus.emit(Events.Tool.GetObjectRotation, rotationData);
|
|
177
|
+
const rotation = rotationData.rotation || 0;
|
|
178
|
+
|
|
179
|
+
const wrapper = document.createElement('div');
|
|
180
|
+
Object.assign(wrapper.style, {
|
|
181
|
+
position: 'absolute',
|
|
182
|
+
left: `${left}px`,
|
|
183
|
+
top: `${top}px`,
|
|
184
|
+
width: `${width}px`,
|
|
185
|
+
height: `${height}px`,
|
|
186
|
+
pointerEvents: 'none',
|
|
187
|
+
transformOrigin: 'center center',
|
|
188
|
+
transform: `rotate(${rotation}deg)`,
|
|
189
|
+
boxSizing: 'border-box'
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const offset = 12;
|
|
193
|
+
const radius = 5;
|
|
194
|
+
const dotSize = radius * 2;
|
|
195
|
+
|
|
196
|
+
const createDot = (side, x, y, ax, ay) => {
|
|
197
|
+
const dot = document.createElement('div');
|
|
198
|
+
dot.className = 'mb-connection-anchor';
|
|
199
|
+
Object.assign(dot.style, {
|
|
200
|
+
position: 'absolute',
|
|
201
|
+
left: `${Math.round(x - radius)}px`,
|
|
202
|
+
top: `${Math.round(y - radius)}px`,
|
|
203
|
+
width: `${dotSize}px`,
|
|
204
|
+
height: `${dotSize}px`,
|
|
205
|
+
backgroundColor: '#2563EB',
|
|
206
|
+
borderRadius: '50%',
|
|
207
|
+
pointerEvents: 'auto',
|
|
208
|
+
boxSizing: 'border-box',
|
|
209
|
+
border: '2px solid #ffffff'
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
dot.dataset.connectorAnchor = "1";
|
|
213
|
+
dot.dataset.id = id;
|
|
214
|
+
dot.dataset.side = side;
|
|
215
|
+
dot.dataset.anchorX = ax;
|
|
216
|
+
dot.dataset.anchorY = ay;
|
|
217
|
+
|
|
218
|
+
wrapper.appendChild(dot);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const cx = Math.round(width / 2);
|
|
222
|
+
const cy = Math.round(height / 2);
|
|
223
|
+
|
|
224
|
+
createDot('top', cx, -offset, 0.5, 0);
|
|
225
|
+
createDot('right', width + offset, cy, 1, 0.5);
|
|
226
|
+
createDot('bottom', cx, height + offset, 0.5, 1);
|
|
227
|
+
createDot('left', -offset, cy, 0, 0.5);
|
|
228
|
+
|
|
229
|
+
this.layer.appendChild(wrapper);
|
|
230
|
+
}
|
|
231
|
+
}
|