@sequent-org/moodboard 1.4.73 → 1.4.75
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 +1 -1
- package/src/assets/icons/message-square.svg +1 -0
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +0 -6
- package/src/objects/shape/ShapeDrawer.js +21 -0
- package/src/objects/shape/shapeTextArea.js +6 -0
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +21 -0
- package/src/ui/HtmlHandlesLayer.js +1 -0
- package/src/ui/NotePropertiesPanel.js +12 -1
- package/src/ui/handles/HandlesDomRenderer.js +25 -9
- package/src/ui/handles/HandlesInteractionController.js +29 -0
- package/src/ui/styles/chat.css +1 -1
- package/src/ui/styles/workspace.css +5 -1
- package/src/ui/toolbar/ToolbarPopupsController.js +7 -5
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-square-icon lucide-message-square"><path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"/></svg>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Toolbar } from '../../ui/Toolbar.js';
|
|
2
|
-
import { SaveStatus } from '../../ui/SaveStatus.js';
|
|
3
2
|
import { Topbar } from '../../ui/Topbar.js';
|
|
4
3
|
import { ZoomPanel } from '../../ui/ZoomPanel.js';
|
|
5
4
|
import { MapPanel } from '../../ui/MapPanel.js';
|
|
@@ -40,11 +39,6 @@ function initToolbar(board) {
|
|
|
40
39
|
window.reloadIcon = (iconName) => board.toolbar.reloadToolbarIcon(iconName);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
board.saveStatus = new SaveStatus(
|
|
44
|
-
board.workspaceElement,
|
|
45
|
-
board.coreMoodboard.eventBus
|
|
46
|
-
);
|
|
47
|
-
|
|
48
42
|
bindToolbarEvents(board);
|
|
49
43
|
}
|
|
50
44
|
|
|
@@ -191,6 +191,27 @@ function _drawFillShape(g, w, h, kind, cornerRadius) {
|
|
|
191
191
|
g.lineTo(skew, 0);
|
|
192
192
|
break;
|
|
193
193
|
}
|
|
194
|
+
case 'dialog': {
|
|
195
|
+
const r = Math.max(2, Math.min(w, h) * 0.16);
|
|
196
|
+
const bodyBottom = h - Math.max(4, h * 0.22);
|
|
197
|
+
const tailRightBase = w * 0.42;
|
|
198
|
+
const tailLeftBase = w * 0.24;
|
|
199
|
+
const tailTipX = w * 0.18;
|
|
200
|
+
g.moveTo(r, 0);
|
|
201
|
+
g.lineTo(w - r, 0);
|
|
202
|
+
g.arcTo(w, 0, w, r, r);
|
|
203
|
+
g.lineTo(w, bodyBottom - r);
|
|
204
|
+
g.arcTo(w, bodyBottom, w - r, bodyBottom, r);
|
|
205
|
+
g.lineTo(tailRightBase, bodyBottom);
|
|
206
|
+
g.lineTo(tailTipX, h);
|
|
207
|
+
g.lineTo(tailLeftBase, bodyBottom);
|
|
208
|
+
g.lineTo(r, bodyBottom);
|
|
209
|
+
g.arcTo(0, bodyBottom, 0, bodyBottom - r, r);
|
|
210
|
+
g.lineTo(0, r);
|
|
211
|
+
g.arcTo(0, 0, r, 0, r);
|
|
212
|
+
g.closePath();
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
194
215
|
case 'arrow': {
|
|
195
216
|
const shaftH = Math.max(6, h * 0.3);
|
|
196
217
|
const shaftY = (h - shaftH) / 2;
|
|
@@ -63,6 +63,12 @@ export function getShapeTextSafeArea(kind, w, h) {
|
|
|
63
63
|
const shaftY = (H - shaftH) / 2;
|
|
64
64
|
return { left: 0, top: shaftY, width: W * 0.6, height: shaftH };
|
|
65
65
|
}
|
|
66
|
+
case 'dialog': {
|
|
67
|
+
// Тело облачка (без хвостика снизу) — bodyBottom совпадает с ShapeDrawer.
|
|
68
|
+
// Текст центрируется по вертикали внутри тела, а не всего бокса с хвостом.
|
|
69
|
+
const bodyBottom = H - Math.max(4, H * 0.22);
|
|
70
|
+
return { left: 0, top: 0, width: W, height: bodyBottom };
|
|
71
|
+
}
|
|
66
72
|
case 'square':
|
|
67
73
|
case 'rounded':
|
|
68
74
|
default:
|
|
@@ -385,6 +385,13 @@ export function openMindmapEditor(object, create = false) {
|
|
|
385
385
|
if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
|
|
386
386
|
window.requestAnimationFrame(() => {
|
|
387
387
|
alignTextareaLineTop();
|
|
388
|
+
// Каретка позиционируется по textarea.getBoundingClientRect(). После
|
|
389
|
+
// выравнивания раскладка textarea окончательна — пересчитываем каретку,
|
|
390
|
+
// иначе в медленном окружении она остаётся в позиции раннего кадра
|
|
391
|
+
// (когда textarea ещё не сжат/не отцентрован) и «прилипает» к верху капсулы.
|
|
392
|
+
if (this.textEditor && this.textEditor.caret && this.textEditor.textarea === textarea) {
|
|
393
|
+
updateCustomCaret(textarea, this.textEditor.caret);
|
|
394
|
+
}
|
|
388
395
|
});
|
|
389
396
|
}
|
|
390
397
|
};
|
|
@@ -846,6 +853,20 @@ export function openMindmapEditor(object, create = false) {
|
|
|
846
853
|
object,
|
|
847
854
|
textarea,
|
|
848
855
|
});
|
|
856
|
+
|
|
857
|
+
// При открытии редактора textarea сжимается до одной строки и центрируется flex-ом
|
|
858
|
+
// асинхронно; первый расчёт каретки может произойти до устаканивания раскладки
|
|
859
|
+
// (offsetY ≈ 0 → каретка у верха капсулы). Пересчитываем после двух кадров, когда
|
|
860
|
+
// геометрия textarea окончательна — независимо от скорости окружения.
|
|
861
|
+
if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
|
|
862
|
+
window.requestAnimationFrame(() => {
|
|
863
|
+
window.requestAnimationFrame(() => {
|
|
864
|
+
if (this.textEditor && this.textEditor.caret && this.textEditor.textarea === textarea) {
|
|
865
|
+
updateCustomCaret(textarea, this.textEditor.caret);
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
}
|
|
849
870
|
}
|
|
850
871
|
|
|
851
872
|
export function closeMindmapEditor(commit) {
|
|
@@ -227,6 +227,10 @@ export class NotePropertiesPanel {
|
|
|
227
227
|
backgroundColor: '#fff',
|
|
228
228
|
cursor: 'pointer',
|
|
229
229
|
minWidth: '140px',
|
|
230
|
+
// Хост может форсить color-scheme: dark (как futurello), из-за чего
|
|
231
|
+
// нативный select и его опции рендерятся белым текстом на белом фоне.
|
|
232
|
+
colorScheme: 'light',
|
|
233
|
+
color: '#111827',
|
|
230
234
|
});
|
|
231
235
|
this.fontSelect = fontSelect;
|
|
232
236
|
|
|
@@ -249,6 +253,8 @@ export class NotePropertiesPanel {
|
|
|
249
253
|
option.value = font.value;
|
|
250
254
|
option.textContent = font.name;
|
|
251
255
|
option.style.fontFamily = font.value;
|
|
256
|
+
option.style.color = '#111827';
|
|
257
|
+
option.style.backgroundColor = '#fff';
|
|
252
258
|
fontSelect.appendChild(option);
|
|
253
259
|
});
|
|
254
260
|
|
|
@@ -322,7 +328,12 @@ export class NotePropertiesPanel {
|
|
|
322
328
|
border: '1px solid #ccc',
|
|
323
329
|
borderRadius: '4px',
|
|
324
330
|
fontSize: '11px',
|
|
325
|
-
textAlign: 'center'
|
|
331
|
+
textAlign: 'center',
|
|
332
|
+
// Хост может форсить color-scheme: dark (как futurello) — фиксируем
|
|
333
|
+
// светлую схему и явные цвета, иначе число становится белым на чёрном.
|
|
334
|
+
colorScheme: 'light',
|
|
335
|
+
color: '#111827',
|
|
336
|
+
backgroundColor: '#fff'
|
|
326
337
|
});
|
|
327
338
|
|
|
328
339
|
fontSizeInput.addEventListener('change', () => {
|
|
@@ -11,7 +11,7 @@ import { MindmapStatePatchCommand } from '../../core/commands/MindmapStatePatchC
|
|
|
11
11
|
const HANDLES_ACCENT_COLOR = '#80D8FF';
|
|
12
12
|
const VERTICAL_RESIZE_CURSOR_COLOR = '#6B7280';
|
|
13
13
|
const VERTICAL_RESIZE_CURSOR = 'url("/icons/move-vertical.svg") 12 12, ns-resize';
|
|
14
|
-
const VERTICAL_RESIZE_CURSOR_TYPES = new Set(['frame', 'text', 'simple-text', 'image']);
|
|
14
|
+
const VERTICAL_RESIZE_CURSOR_TYPES = new Set(['frame', 'text', 'simple-text', 'image', 'shape']);
|
|
15
15
|
const VERTICAL_RESIZE_CORNER_CURSOR_ANGLES = {
|
|
16
16
|
nw: -45,
|
|
17
17
|
se: -45,
|
|
@@ -50,15 +50,26 @@ function resolveBottomSiblingParentId(sourceObjectId, sourceMeta) {
|
|
|
50
50
|
|
|
51
51
|
function shouldUseVerticalResizeCursor(mbType, handleName) {
|
|
52
52
|
return VERTICAL_RESIZE_CURSOR_TYPES.has(mbType)
|
|
53
|
-
&& (handleName === 'handle'
|
|
53
|
+
&& (handleName === 'handle'
|
|
54
|
+
|| handleName === 'top'
|
|
55
|
+
|| handleName === 'bottom'
|
|
56
|
+
|| handleName === 'left'
|
|
57
|
+
|| handleName === 'right');
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
function
|
|
57
|
-
const baseAngle = VERTICAL_RESIZE_CORNER_CURSOR_ANGLES[dir] || 0;
|
|
58
|
-
const totalAngle = baseAngle + (rotation || 0);
|
|
60
|
+
function buildVerticalResizeArrowCursor(totalAngle, fallback) {
|
|
59
61
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"><g transform="rotate(${totalAngle} 12 12)"><path d="M12 2v20" stroke="${VERTICAL_RESIZE_CURSOR_COLOR}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="m8 18 4 4 4-4" stroke="${VERTICAL_RESIZE_CURSOR_COLOR}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="m8 6 4-4 4 4" stroke="${VERTICAL_RESIZE_CURSOR_COLOR}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g></svg>`;
|
|
60
62
|
const dataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
|
|
61
|
-
return `url("${dataUrl}") 12 12,
|
|
63
|
+
return `url("${dataUrl}") 12 12, ${fallback}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createVerticalResizeCornerCursor(dir, rotation) {
|
|
67
|
+
const baseAngle = VERTICAL_RESIZE_CORNER_CURSOR_ANGLES[dir] || 0;
|
|
68
|
+
return buildVerticalResizeArrowCursor(baseAngle + (rotation || 0), 'nwse-resize');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createHorizontalResizeCursor(rotation) {
|
|
72
|
+
return buildVerticalResizeArrowCursor(90 + (rotation || 0), 'ew-resize');
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
function relayoutMindmapBranchLevel({ core, eventBus, parentId, side }) {
|
|
@@ -1313,9 +1324,14 @@ export class HandlesDomRenderer {
|
|
|
1313
1324
|
|
|
1314
1325
|
const edgeSize = 10;
|
|
1315
1326
|
const makeEdge = (name, style, cursorHandleType) => {
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1327
|
+
let cursor;
|
|
1328
|
+
if (shouldUseVerticalResizeCursor(mbType, name)) {
|
|
1329
|
+
cursor = (name === 'left' || name === 'right')
|
|
1330
|
+
? createHorizontalResizeCursor(rotation)
|
|
1331
|
+
: VERTICAL_RESIZE_CURSOR;
|
|
1332
|
+
} else {
|
|
1333
|
+
cursor = createRotatedResizeCursor(cursorHandleType, rotation);
|
|
1334
|
+
}
|
|
1319
1335
|
const e = document.createElement('div');
|
|
1320
1336
|
e.dataset.edge = name;
|
|
1321
1337
|
e.dataset.id = id;
|
|
@@ -8,6 +8,30 @@ const TEXT_CORNER_MAX_FONT = 512;
|
|
|
8
8
|
export class HandlesInteractionController {
|
|
9
9
|
constructor(host) {
|
|
10
10
|
this.host = host;
|
|
11
|
+
this._dragCursorStyleEl = null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Во время drag указатель уходит с маленького div-а ручки на canvas,
|
|
15
|
+
// и браузер подменяет курсор на дефолтный canvas-курсор — иконка ресайза пропадает.
|
|
16
|
+
// Глобальный <style> с !important держит нужную иконку видимой на всём документе.
|
|
17
|
+
_lockDragCursor(cursorValue) {
|
|
18
|
+
if (!cursorValue || typeof document === 'undefined') return;
|
|
19
|
+
this._unlockDragCursor();
|
|
20
|
+
const styleEl = document.createElement('style');
|
|
21
|
+
styleEl.textContent = `* { cursor: ${cursorValue} !important; }`;
|
|
22
|
+
document.head.appendChild(styleEl);
|
|
23
|
+
this._dragCursorStyleEl = styleEl;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_unlockDragCursor() {
|
|
27
|
+
if (this._dragCursorStyleEl) {
|
|
28
|
+
this._dragCursorStyleEl.remove();
|
|
29
|
+
this._dragCursorStyleEl = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
destroy() {
|
|
34
|
+
this._unlockDragCursor();
|
|
11
35
|
}
|
|
12
36
|
|
|
13
37
|
_parseBoxRotation(box) {
|
|
@@ -191,6 +215,8 @@ export class HandlesInteractionController {
|
|
|
191
215
|
}
|
|
192
216
|
}
|
|
193
217
|
|
|
218
|
+
this._lockDragCursor(e.currentTarget.style.cursor);
|
|
219
|
+
|
|
194
220
|
const world = this.host.core.pixi.worldLayer || this.host.core.pixi.app.stage;
|
|
195
221
|
const s = world?.scale?.x || 1;
|
|
196
222
|
const tx = world?.x || 0;
|
|
@@ -469,6 +495,7 @@ export class HandlesInteractionController {
|
|
|
469
495
|
const onUp = () => {
|
|
470
496
|
document.removeEventListener('pointermove', onMove);
|
|
471
497
|
document.removeEventListener('pointerup', onUp);
|
|
498
|
+
this._unlockDragCursor();
|
|
472
499
|
|
|
473
500
|
if (isTextCornerScale) {
|
|
474
501
|
// Кегль уже сохранён слитой UpdateTextStyleCommand из onMove.
|
|
@@ -546,6 +573,7 @@ export class HandlesInteractionController {
|
|
|
546
573
|
onEdgeResizeDown(e) {
|
|
547
574
|
e.preventDefault();
|
|
548
575
|
e.stopPropagation();
|
|
576
|
+
this._lockDragCursor(e.currentTarget.style.cursor);
|
|
549
577
|
const id = e.currentTarget.dataset.id;
|
|
550
578
|
const isGroup = id === '__group__';
|
|
551
579
|
const edge = e.currentTarget.dataset.edge;
|
|
@@ -756,6 +784,7 @@ export class HandlesInteractionController {
|
|
|
756
784
|
const onUp = () => {
|
|
757
785
|
document.removeEventListener('pointermove', onMove);
|
|
758
786
|
document.removeEventListener('pointerup', onUp);
|
|
787
|
+
this._unlockDragCursor();
|
|
759
788
|
|
|
760
789
|
// Клик по рамке без перетаскивания на текстовом объекте → редактирование
|
|
761
790
|
if (!_edgeHasMoved && isTextTarget && !isGroup) {
|
package/src/ui/styles/chat.css
CHANGED
|
@@ -1386,7 +1386,11 @@
|
|
|
1386
1386
|
.shape-parallelogram {
|
|
1387
1387
|
width: 17px; height: 11px; background: #ffffff; border: 1.5px solid #94a3b8; transform: skewX(-20deg);
|
|
1388
1388
|
}
|
|
1389
|
-
.shape-
|
|
1389
|
+
.shape-dialog {
|
|
1390
|
+
width: 15px; height: 15px; color: #94a3b8;
|
|
1391
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
1392
|
+
}
|
|
1393
|
+
.shape-dialog svg { width: 15px; height: 15px; display: block; }
|
|
1390
1394
|
|
|
1391
1395
|
|
|
1392
1396
|
|
|
@@ -118,6 +118,8 @@ export class ToolbarPopupsController {
|
|
|
118
118
|
const grid = document.createElement('div');
|
|
119
119
|
grid.className = 'moodboard-shapes__grid';
|
|
120
120
|
|
|
121
|
+
const DIALOG_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"/></svg>';
|
|
122
|
+
|
|
121
123
|
const shapes = [
|
|
122
124
|
{ id: 'shape', title: 'Добавить фигуру', isToolbarAction: true },
|
|
123
125
|
{ id: 'rounded-square', title: 'Скругленный квадрат' },
|
|
@@ -125,7 +127,7 @@ export class ToolbarPopupsController {
|
|
|
125
127
|
{ id: 'triangle', title: 'Треугольник' },
|
|
126
128
|
{ id: 'diamond', title: 'Ромб' },
|
|
127
129
|
{ id: 'parallelogram', title: 'Параллелограмм' },
|
|
128
|
-
{ id: '
|
|
130
|
+
{ id: 'dialog', title: 'Диалог', svg: DIALOG_SVG }
|
|
129
131
|
];
|
|
130
132
|
|
|
131
133
|
shapes.forEach((s) => {
|
|
@@ -137,9 +139,9 @@ export class ToolbarPopupsController {
|
|
|
137
139
|
icon.className = 'moodboard-shapes__icon shape-square';
|
|
138
140
|
} else {
|
|
139
141
|
icon.className = `moodboard-shapes__icon shape-${s.id}`;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
}
|
|
143
|
+
if (s.svg) {
|
|
144
|
+
icon.innerHTML = s.svg;
|
|
143
145
|
}
|
|
144
146
|
btn.appendChild(icon);
|
|
145
147
|
btn.addEventListener('click', () => {
|
|
@@ -155,7 +157,7 @@ export class ToolbarPopupsController {
|
|
|
155
157
|
triangle: { kind: 'triangle' },
|
|
156
158
|
diamond: { kind: 'diamond' },
|
|
157
159
|
parallelogram: { kind: 'parallelogram' },
|
|
158
|
-
|
|
160
|
+
dialog: { kind: 'dialog' }
|
|
159
161
|
};
|
|
160
162
|
const props = propsMap[s.id] || { kind: 'square' };
|
|
161
163
|
this.toolbar.eventBus.emit(Events.Place.Set, { type: 'shape', properties: props });
|