@sequent-org/moodboard 1.4.74 → 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/ui/HtmlHandlesLayer.js +1 -0
- package/src/ui/handles/HandlesDomRenderer.js +25 -9
- package/src/ui/handles/HandlesInteractionController.js +29 -0
- 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:
|
|
@@ -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) {
|
|
@@ -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 });
|