@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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Команда изменения стиля фигуры (цвет заливки, тип, обводка, радиус скругления) — одно действие в истории.
|
|
3
|
+
* Поддерживает частичные обновления: хранит только те поля, которые изменились.
|
|
4
|
+
*
|
|
5
|
+
* Снапшот: { color?, properties?: { kind?, cornerRadius?, borderColor?, borderWidth?, borderStyle?, borderOpacity? } }
|
|
6
|
+
*/
|
|
7
|
+
import { BaseCommand } from './BaseCommand.js';
|
|
8
|
+
import { Events } from '../events/Events.js';
|
|
9
|
+
|
|
10
|
+
export class UpdateShapeStyleCommand extends BaseCommand {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} coreMoodboard — ядро доски
|
|
13
|
+
* @param {string} objectId — id объекта-фигуры
|
|
14
|
+
* @param {Object} oldSnapshot — прежние значения (только изменяемые поля)
|
|
15
|
+
* @param {Object} newSnapshot — новые значения (только изменяемые поля)
|
|
16
|
+
*/
|
|
17
|
+
constructor(coreMoodboard, objectId, oldSnapshot, newSnapshot) {
|
|
18
|
+
super('update_shape_style', 'Изменить стиль фигуры');
|
|
19
|
+
this.coreMoodboard = coreMoodboard;
|
|
20
|
+
this.objectId = objectId;
|
|
21
|
+
this.oldSnapshot = oldSnapshot;
|
|
22
|
+
this.newSnapshot = newSnapshot;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
execute() {
|
|
26
|
+
this._apply(this.newSnapshot);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
undo() {
|
|
30
|
+
// Локальный undo отключён: история состояния загружается с сервера по версиям.
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
canMergeWith(other) {
|
|
34
|
+
return other instanceof UpdateShapeStyleCommand &&
|
|
35
|
+
other.objectId === this.objectId &&
|
|
36
|
+
_snapshotKeysMatch(this.newSnapshot, other.newSnapshot);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
mergeWith(other) {
|
|
40
|
+
if (!this.canMergeWith(other)) throw new Error('Cannot merge commands');
|
|
41
|
+
this.newSnapshot = other.newSnapshot;
|
|
42
|
+
this.timestamp = other.timestamp;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_apply(snapshot) {
|
|
46
|
+
const { coreMoodboard, objectId } = this;
|
|
47
|
+
const object = coreMoodboard.state.getObjects().find(o => o.id === objectId);
|
|
48
|
+
if (!object) return;
|
|
49
|
+
|
|
50
|
+
// Обновить состояние
|
|
51
|
+
if ('color' in snapshot) {
|
|
52
|
+
object.color = snapshot.color;
|
|
53
|
+
}
|
|
54
|
+
if (snapshot.properties) {
|
|
55
|
+
if (!object.properties) object.properties = {};
|
|
56
|
+
Object.assign(object.properties, snapshot.properties);
|
|
57
|
+
}
|
|
58
|
+
coreMoodboard.state.markDirty();
|
|
59
|
+
|
|
60
|
+
// Обновить PIXI-инстанс
|
|
61
|
+
const pixiObject = coreMoodboard.pixi?.objects?.get(objectId);
|
|
62
|
+
const instance = pixiObject?._mb?.instance;
|
|
63
|
+
|
|
64
|
+
if (instance) {
|
|
65
|
+
if ('color' in snapshot && instance.setColor) {
|
|
66
|
+
instance.setColor(snapshot.color);
|
|
67
|
+
}
|
|
68
|
+
if (snapshot.properties) {
|
|
69
|
+
const p = snapshot.properties;
|
|
70
|
+
|
|
71
|
+
// kind и cornerRadius — через setProperties (borderStyle идёт только в setStroke)
|
|
72
|
+
if ((p.kind !== undefined || p.cornerRadius !== undefined) && instance.setProperties) {
|
|
73
|
+
instance.setProperties({ kind: p.kind, cornerRadius: p.cornerRadius });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Параметры обводки (borderStyle здесь тоже)
|
|
77
|
+
const hasStroke = p.borderColor !== undefined || p.borderWidth !== undefined ||
|
|
78
|
+
p.borderStyle !== undefined || p.borderOpacity !== undefined;
|
|
79
|
+
if (hasStroke && instance.setStroke) {
|
|
80
|
+
instance.setStroke({
|
|
81
|
+
borderColor: p.borderColor,
|
|
82
|
+
borderWidth: p.borderWidth,
|
|
83
|
+
borderStyle: p.borderStyle,
|
|
84
|
+
borderOpacity: p.borderOpacity,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Синхронизировать _mb.properties с новым состоянием
|
|
91
|
+
if (pixiObject?._mb) {
|
|
92
|
+
if (!pixiObject._mb.properties) pixiObject._mb.properties = {};
|
|
93
|
+
if ('color' in snapshot) pixiObject._mb.color = snapshot.color;
|
|
94
|
+
if (snapshot.properties) Object.assign(pixiObject._mb.properties, snapshot.properties);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Уведомить остальных подписчиков (с флагом чтобы избежать рекурсии в tryCreateShapeStyleCommand)
|
|
98
|
+
const updates = {};
|
|
99
|
+
if ('color' in snapshot) updates.color = snapshot.color;
|
|
100
|
+
if (snapshot.properties) updates.properties = { ...snapshot.properties };
|
|
101
|
+
|
|
102
|
+
coreMoodboard.eventBus.emit(Events.Object.StateChanged, {
|
|
103
|
+
objectId,
|
|
104
|
+
updates,
|
|
105
|
+
_fromCommand: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Проверяет, что два снапшота содержат одинаковый набор ключей — для слияния команд.
|
|
112
|
+
*/
|
|
113
|
+
function _snapshotKeysMatch(a, b) {
|
|
114
|
+
const topA = Object.keys(a).sort().join(',');
|
|
115
|
+
const topB = Object.keys(b).sort().join(',');
|
|
116
|
+
if (topA !== topB) return false;
|
|
117
|
+
if (a.properties && b.properties) {
|
|
118
|
+
return Object.keys(a.properties).sort().join(',') === Object.keys(b.properties).sort().join(',');
|
|
119
|
+
}
|
|
120
|
+
return !(a.properties || b.properties);
|
|
121
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Команда изменения свойств текста
|
|
3
|
-
* Поддерживает: fontFamily, fontSize, color, backgroundColor
|
|
2
|
+
* Команда изменения свойств текста для системы Undo/Redo.
|
|
3
|
+
* Поддерживает: fontFamily, fontSize, color, backgroundColor, markdown,
|
|
4
|
+
* bold, italic, underline, strikethrough, textAlign, lineHeight, listType.
|
|
4
5
|
*/
|
|
5
6
|
import { BaseCommand } from './BaseCommand.js';
|
|
6
7
|
import { Events } from '../events/Events.js';
|
|
7
8
|
import { syncPixiTextProperties } from '../../ui/text-properties/TextPropertiesPanelMapper.js';
|
|
8
9
|
|
|
10
|
+
const PROPERTY_LEVEL = ['fontFamily', 'markdown', 'bold', 'italic', 'underline', 'strikethrough', 'textAlign', 'lineHeight', 'listType'];
|
|
11
|
+
|
|
9
12
|
export class UpdateTextStyleCommand extends BaseCommand {
|
|
10
13
|
/**
|
|
11
14
|
* @param {Object} coreMoodboard — ядро доски
|
|
@@ -52,9 +55,9 @@ export class UpdateTextStyleCommand extends BaseCommand {
|
|
|
52
55
|
|
|
53
56
|
const { property } = this;
|
|
54
57
|
|
|
55
|
-
if (property
|
|
58
|
+
if (PROPERTY_LEVEL.includes(property)) {
|
|
56
59
|
if (!object.properties) object.properties = {};
|
|
57
|
-
object.properties
|
|
60
|
+
object.properties[property] = value;
|
|
58
61
|
} else {
|
|
59
62
|
object[property] = value;
|
|
60
63
|
}
|
|
@@ -69,8 +72,8 @@ export class UpdateTextStyleCommand extends BaseCommand {
|
|
|
69
72
|
|
|
70
73
|
syncPixiTextProperties(this.coreMoodboard.eventBus, this.objectId, { [property]: value });
|
|
71
74
|
|
|
72
|
-
const updates = property
|
|
73
|
-
? { properties: {
|
|
75
|
+
const updates = PROPERTY_LEVEL.includes(property)
|
|
76
|
+
? { properties: { [property]: value } }
|
|
74
77
|
: { [property]: value };
|
|
75
78
|
this.coreMoodboard.eventBus.emit(Events.Object.StateChanged, {
|
|
76
79
|
objectId: this.objectId,
|
|
@@ -85,6 +88,14 @@ function _propertyLabel(property) {
|
|
|
85
88
|
fontSize: 'размер шрифта',
|
|
86
89
|
color: 'цвет текста',
|
|
87
90
|
backgroundColor: 'фон текста',
|
|
91
|
+
markdown: 'markdown-режим',
|
|
92
|
+
bold: 'жирный текст',
|
|
93
|
+
italic: 'курсив',
|
|
94
|
+
underline: 'подчёркивание',
|
|
95
|
+
strikethrough: 'зачёркивание',
|
|
96
|
+
textAlign: 'выравнивание текста',
|
|
97
|
+
lineHeight: 'межстрочный интервал',
|
|
98
|
+
listType: 'тип списка',
|
|
88
99
|
};
|
|
89
100
|
return labels[property] || property;
|
|
90
101
|
}
|
|
@@ -18,3 +18,6 @@ export { UpdateTextStyleCommand } from './UpdateTextStyleCommand.js';
|
|
|
18
18
|
export { UpdateNoteStyleCommand } from './UpdateNoteStyleCommand.js';
|
|
19
19
|
export { UpdateFramePropertiesCommand } from './UpdateFramePropertiesCommand.js';
|
|
20
20
|
export { UpdateFrameTypeCommand } from './UpdateFrameTypeCommand.js';
|
|
21
|
+
export { CreateConnectorCommand } from './CreateConnectorCommand.js';
|
|
22
|
+
export { UpdateConnectorCommand } from './UpdateConnectorCommand.js';
|
|
23
|
+
export { UpdateShapeStyleCommand } from './UpdateShapeStyleCommand.js';
|
|
@@ -9,6 +9,8 @@ export const Events = {
|
|
|
9
9
|
SelectionRemove: 'tool:selection:remove',
|
|
10
10
|
SelectionClear: 'tool:selection:clear',
|
|
11
11
|
SelectionAll: 'tool:selection:all',
|
|
12
|
+
BoxSelectStart: 'tool:box:select:start',
|
|
13
|
+
BoxSelectCommit: 'tool:box:select:commit',
|
|
12
14
|
HitTest: 'tool:hit:test',
|
|
13
15
|
GetSelection: 'tool:get:selection',
|
|
14
16
|
GetAllObjects: 'tool:get:all:objects',
|
|
@@ -153,6 +155,26 @@ export const Events = {
|
|
|
153
155
|
Draw: {
|
|
154
156
|
BrushSet: 'draw:brush:set',
|
|
155
157
|
},
|
|
158
|
+
|
|
159
|
+
Lasso: {
|
|
160
|
+
ModeSet: 'lasso:mode:set',
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
Comment: {
|
|
164
|
+
PinCreated: 'comment:pin:created',
|
|
165
|
+
ThreadOpened: 'comment:thread:opened',
|
|
166
|
+
MessageAdded: 'comment:message:added',
|
|
167
|
+
Resolved: 'comment:resolved',
|
|
168
|
+
ColorChanged: 'comment:color:changed',
|
|
169
|
+
Deleted: 'comment:deleted',
|
|
170
|
+
RemoteUpdated: 'comment:remote:updated',
|
|
171
|
+
OpenDraftAt: 'comment:open:draft:at',
|
|
172
|
+
ThreadDeleted: 'comment:thread:deleted',
|
|
173
|
+
DraftOpened: 'comment:draft:opened',
|
|
174
|
+
DraftClosed: 'comment:draft:closed',
|
|
175
|
+
ResolvedFilterChanged: 'comment:resolved:filter:changed',
|
|
176
|
+
ListOpened: 'comment:list:opened',
|
|
177
|
+
},
|
|
156
178
|
};
|
|
157
179
|
|
|
158
180
|
|
|
@@ -247,6 +247,7 @@ export function setupLayerAndViewportFlow(core) {
|
|
|
247
247
|
const s = world?.scale?.x || 1;
|
|
248
248
|
world.x = view.clientWidth / 2 - worldX * s;
|
|
249
249
|
world.y = view.clientHeight / 2 - worldY * s;
|
|
250
|
+
core.eventBus.emit(Events.Viewport.Changed);
|
|
250
251
|
});
|
|
251
252
|
|
|
252
253
|
core.eventBus.on(Events.Tool.GroupDragStart, (data) => {
|
|
@@ -6,14 +6,24 @@ import {
|
|
|
6
6
|
UpdateTextStyleCommand,
|
|
7
7
|
UpdateNoteStyleCommand,
|
|
8
8
|
UpdateFramePropertiesCommand,
|
|
9
|
+
UpdateConnectorCommand,
|
|
10
|
+
UpdateShapeStyleCommand,
|
|
9
11
|
} from '../commands/index.js';
|
|
10
12
|
|
|
11
|
-
const TEXT_STYLE_PROPS = ['fontFamily', 'fontSize', 'color', 'backgroundColor'];
|
|
13
|
+
const TEXT_STYLE_PROPS = ['fontFamily', 'fontSize', 'color', 'backgroundColor', 'markdown', 'bold', 'italic', 'underline', 'strikethrough', 'textAlign', 'lineHeight', 'listType'];
|
|
14
|
+
const TEXT_STYLE_PROPERTY_LEVEL = ['fontFamily', 'markdown', 'bold', 'italic', 'underline', 'strikethrough', 'textAlign', 'lineHeight', 'listType'];
|
|
12
15
|
const TEXT_STYLE_DEFAULTS = {
|
|
13
16
|
fontFamily: 'Roboto, Arial, sans-serif',
|
|
14
17
|
fontSize: 18,
|
|
15
18
|
color: '#000000',
|
|
16
19
|
backgroundColor: 'transparent',
|
|
20
|
+
markdown: false,
|
|
21
|
+
bold: false,
|
|
22
|
+
italic: false,
|
|
23
|
+
underline: false,
|
|
24
|
+
strikethrough: false,
|
|
25
|
+
textAlign: 'left',
|
|
26
|
+
listType: 'none',
|
|
17
27
|
};
|
|
18
28
|
|
|
19
29
|
const NOTE_STYLE_PROPS = ['fontFamily', 'fontSize', 'textColor', 'backgroundColor'];
|
|
@@ -24,6 +34,64 @@ const NOTE_STYLE_DEFAULTS = {
|
|
|
24
34
|
backgroundColor: 0xfff9c4,
|
|
25
35
|
};
|
|
26
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Если updates содержит color или shape-properties — создаёт UpdateShapeStyleCommand и выполняет её.
|
|
39
|
+
* Защищена от рекурсии: при emit из команды (_fromCommand=true) object уже обновлён,
|
|
40
|
+
* oldValue === newValue для всех полей → hasChanges = false → return false.
|
|
41
|
+
* @returns {boolean} true, если команда создана и применена
|
|
42
|
+
*/
|
|
43
|
+
const SHAPE_PROP_KEYS = ['kind', 'cornerRadius', 'borderColor', 'borderWidth', 'borderStyle', 'borderOpacity'];
|
|
44
|
+
|
|
45
|
+
function tryCreateShapeStyleCommand(core, object, objectId, updates) {
|
|
46
|
+
if (object.type !== 'shape') return false;
|
|
47
|
+
|
|
48
|
+
const hasColor = 'color' in updates;
|
|
49
|
+
const hasProps = updates.properties &&
|
|
50
|
+
Object.keys(updates.properties).some(k => SHAPE_PROP_KEYS.includes(k));
|
|
51
|
+
|
|
52
|
+
if (!hasColor && !hasProps) return false;
|
|
53
|
+
|
|
54
|
+
const newSnapshot = {};
|
|
55
|
+
const oldSnapshot = {};
|
|
56
|
+
let hasChanges = false;
|
|
57
|
+
|
|
58
|
+
if (hasColor) {
|
|
59
|
+
const oldColor = object.color;
|
|
60
|
+
const newColor = updates.color;
|
|
61
|
+
if (oldColor !== newColor) {
|
|
62
|
+
oldSnapshot.color = oldColor;
|
|
63
|
+
newSnapshot.color = newColor;
|
|
64
|
+
hasChanges = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (hasProps) {
|
|
69
|
+
oldSnapshot.properties = {};
|
|
70
|
+
newSnapshot.properties = {};
|
|
71
|
+
for (const key of SHAPE_PROP_KEYS) {
|
|
72
|
+
if (key in updates.properties) {
|
|
73
|
+
const oldVal = object.properties?.[key];
|
|
74
|
+
const newVal = updates.properties[key];
|
|
75
|
+
if (oldVal !== newVal) {
|
|
76
|
+
oldSnapshot.properties[key] = oldVal;
|
|
77
|
+
newSnapshot.properties[key] = newVal;
|
|
78
|
+
hasChanges = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (Object.keys(newSnapshot.properties).length === 0) {
|
|
83
|
+
delete oldSnapshot.properties;
|
|
84
|
+
delete newSnapshot.properties;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!hasChanges) return false;
|
|
89
|
+
|
|
90
|
+
const command = new UpdateShapeStyleCommand(core, objectId, oldSnapshot, newSnapshot);
|
|
91
|
+
core.history.executeCommand(command);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
27
95
|
/**
|
|
28
96
|
* Если updates.properties содержит ровно одно свойство стиля записки — создаёт UpdateNoteStyleCommand.
|
|
29
97
|
* @returns {boolean} true, если команда создана и применена
|
|
@@ -56,9 +124,12 @@ function tryCreateTextStyleCommand(core, object, objectId, updates) {
|
|
|
56
124
|
let property = null;
|
|
57
125
|
let newValue = null;
|
|
58
126
|
|
|
59
|
-
if (updates.properties
|
|
60
|
-
|
|
61
|
-
|
|
127
|
+
if (updates.properties && Object.keys(updates).length === 1) {
|
|
128
|
+
const propKeys = Object.keys(updates.properties);
|
|
129
|
+
if (propKeys.length === 1 && TEXT_STYLE_PROPERTY_LEVEL.includes(propKeys[0])) {
|
|
130
|
+
property = propKeys[0];
|
|
131
|
+
newValue = updates.properties[property];
|
|
132
|
+
}
|
|
62
133
|
} else if (updates.fontSize !== undefined && !updates.properties && Object.keys(updates).length === 1) {
|
|
63
134
|
property = 'fontSize';
|
|
64
135
|
newValue = typeof updates.fontSize === 'string' ? parseInt(updates.fontSize, 10) : updates.fontSize;
|
|
@@ -72,8 +143,8 @@ function tryCreateTextStyleCommand(core, object, objectId, updates) {
|
|
|
72
143
|
|
|
73
144
|
if (!property || !TEXT_STYLE_PROPS.includes(property)) return false;
|
|
74
145
|
|
|
75
|
-
const oldValue = property
|
|
76
|
-
? (object.properties?.
|
|
146
|
+
const oldValue = TEXT_STYLE_PROPERTY_LEVEL.includes(property)
|
|
147
|
+
? (object.properties?.[property] ?? TEXT_STYLE_DEFAULTS[property])
|
|
77
148
|
: (object[property] ?? object.properties?.[property] ?? TEXT_STYLE_DEFAULTS[property]);
|
|
78
149
|
|
|
79
150
|
if (oldValue === newValue) return false;
|
|
@@ -83,6 +154,50 @@ function tryCreateTextStyleCommand(core, object, objectId, updates) {
|
|
|
83
154
|
return true;
|
|
84
155
|
}
|
|
85
156
|
|
|
157
|
+
const CONNECTOR_STYLE_KEYS = ['stroke', 'width', 'dash', 'route', 'head'];
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Если updates.properties.style содержит поля стиля коннектора —
|
|
161
|
+
* создаёт UpdateConnectorCommand и выполняет её через историю.
|
|
162
|
+
* Также обрабатывает start/end (для swap-кнопки).
|
|
163
|
+
* @returns {boolean} true — команда применена, дальнейшая обработка не нужна
|
|
164
|
+
*/
|
|
165
|
+
function tryCreateConnectorStyleCommand(core, object, objectId, updates) {
|
|
166
|
+
if (object.type !== 'connector') return false;
|
|
167
|
+
if (!updates.properties) return false;
|
|
168
|
+
|
|
169
|
+
const { style, start, end } = updates.properties;
|
|
170
|
+
|
|
171
|
+
// Проверяем, что в updates.properties только style/start/end (и нет посторонних ключей)
|
|
172
|
+
const allowedKeys = new Set(['style', 'start', 'end', 'locked']);
|
|
173
|
+
const hasOtherKeys = Object.keys(updates.properties).some(k => !allowedKeys.has(k));
|
|
174
|
+
if (hasOtherKeys) return false;
|
|
175
|
+
// Должно быть хотя бы одно из: style, start, end
|
|
176
|
+
if (!style && start === undefined && end === undefined
|
|
177
|
+
&& updates.properties.locked === undefined) return false;
|
|
178
|
+
|
|
179
|
+
const commandUpdates = {};
|
|
180
|
+
if (style !== undefined) commandUpdates.style = style;
|
|
181
|
+
if (start !== undefined) commandUpdates.start = start;
|
|
182
|
+
if (end !== undefined) commandUpdates.end = end;
|
|
183
|
+
|
|
184
|
+
// locked хранится в properties напрямую, не через UpdateConnectorCommand
|
|
185
|
+
if (updates.properties.locked !== undefined) {
|
|
186
|
+
if (!object.properties) object.properties = {};
|
|
187
|
+
object.properties.locked = updates.properties.locked;
|
|
188
|
+
if (Object.keys(commandUpdates).length === 0) {
|
|
189
|
+
core.state.markDirty();
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (Object.keys(commandUpdates).length === 0) return false;
|
|
195
|
+
|
|
196
|
+
const command = new UpdateConnectorCommand(core, objectId, commandUpdates);
|
|
197
|
+
core.history.executeCommand(command);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
86
201
|
const FRAME_PROP_KEYS = ['title'];
|
|
87
202
|
|
|
88
203
|
/**
|
|
@@ -131,7 +246,18 @@ export function setupObjectLifecycleFlow(core) {
|
|
|
131
246
|
|
|
132
247
|
core.eventBus.on(Events.Tool.HitTest, (data) => {
|
|
133
248
|
const result = core.pixi.hitTest(data.x, data.y);
|
|
134
|
-
|
|
249
|
+
if (result.type !== 'object' && core.connectorLayer) {
|
|
250
|
+
const worldLayer = core.pixi.worldLayer;
|
|
251
|
+
const worldPoint = worldLayer.toLocal({ x: data.x, y: data.y });
|
|
252
|
+
const id = core.connectorLayer.hitTest(worldPoint);
|
|
253
|
+
if (id) {
|
|
254
|
+
data.result = { type: 'object', object: id, pixiObject: core.pixi.objects.get(id) };
|
|
255
|
+
} else {
|
|
256
|
+
data.result = result;
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
data.result = result;
|
|
260
|
+
}
|
|
135
261
|
});
|
|
136
262
|
|
|
137
263
|
core.eventBus.on(Events.Tool.GetObjectPosition, (data) => {
|
|
@@ -228,9 +354,15 @@ export function setupObjectLifecycleFlow(core) {
|
|
|
228
354
|
const noteStyleChange = tryCreateNoteStyleCommand(core, object, objectId, updates);
|
|
229
355
|
if (noteStyleChange) return;
|
|
230
356
|
|
|
357
|
+
const shapeStyleChange = tryCreateShapeStyleCommand(core, object, objectId, updates);
|
|
358
|
+
if (shapeStyleChange) return;
|
|
359
|
+
|
|
231
360
|
const textStyleChange = tryCreateTextStyleCommand(core, object, objectId, updates);
|
|
232
361
|
if (textStyleChange) return;
|
|
233
362
|
|
|
363
|
+
const connectorStyleChange = tryCreateConnectorStyleCommand(core, object, objectId, updates);
|
|
364
|
+
if (connectorStyleChange) return;
|
|
365
|
+
|
|
234
366
|
const framePropsChange = tryCreateFramePropertiesCommand(core, object, objectId, updates);
|
|
235
367
|
if (framePropsChange) return;
|
|
236
368
|
|
|
@@ -282,6 +414,22 @@ export function setupObjectLifecycleFlow(core) {
|
|
|
282
414
|
}
|
|
283
415
|
}
|
|
284
416
|
}
|
|
417
|
+
|
|
418
|
+
if (object.type === 'drawing' && updates.properties && instance.setStyle) {
|
|
419
|
+
const styleUpdates = {};
|
|
420
|
+
if (updates.properties.strokeColor !== undefined) {
|
|
421
|
+
styleUpdates.strokeColor = updates.properties.strokeColor;
|
|
422
|
+
}
|
|
423
|
+
if (updates.properties.strokeWidth !== undefined) {
|
|
424
|
+
styleUpdates.strokeWidth = updates.properties.strokeWidth;
|
|
425
|
+
}
|
|
426
|
+
if (updates.properties.mode !== undefined) {
|
|
427
|
+
styleUpdates.mode = updates.properties.mode;
|
|
428
|
+
}
|
|
429
|
+
if (Object.keys(styleUpdates).length > 0) {
|
|
430
|
+
instance.setStyle(styleUpdates);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
285
433
|
}
|
|
286
434
|
|
|
287
435
|
core.state.markDirty();
|
package/src/core/index.js
CHANGED
|
@@ -346,6 +346,17 @@ export class CoreMoodBoard {
|
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
createObject(type, position, properties = {}, extraData = {}) {
|
|
349
|
+
const validType = typeof type === 'string' && type.length > 0;
|
|
350
|
+
const validPos = position && typeof position === 'object'
|
|
351
|
+
&& typeof position.x === 'number' && typeof position.y === 'number';
|
|
352
|
+
if (!validType || !validPos) {
|
|
353
|
+
console.error(
|
|
354
|
+
'[MoodBoard] createObject: неверные аргументы. Ожидается createObject(type, position, properties, extraData), ' +
|
|
355
|
+
'где type — непустая строка, position — { x: number, y: number }.',
|
|
356
|
+
{ type, position }
|
|
357
|
+
);
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
349
360
|
const exists = (id) => {
|
|
350
361
|
const inState = (this.state.state.objects || []).some(o => o.id === id);
|
|
351
362
|
const inPixi = this.pixi?.objects?.has ? this.pixi.objects.has(id) : false;
|
|
@@ -394,7 +405,10 @@ export class CoreMoodBoard {
|
|
|
394
405
|
properties = { ...(properties || {}), title: 'Фрейм 1' };
|
|
395
406
|
}
|
|
396
407
|
}
|
|
397
|
-
|
|
408
|
+
// Рисованные объекты (drawing) должны оставаться ровно там, где их нарисовали —
|
|
409
|
+
// примагничивание bbox к сетке сдвигало бы готовую линию относительно превью.
|
|
410
|
+
const shouldSnap = this.gridSnapResolver && type !== 'drawing';
|
|
411
|
+
const snappedCreatePos = shouldSnap
|
|
398
412
|
? this.gridSnapResolver.snapWorldTopLeft(position, {
|
|
399
413
|
width: initialWidth,
|
|
400
414
|
height: initialHeight,
|
|
@@ -514,6 +528,19 @@ export class CoreMoodBoard {
|
|
|
514
528
|
}
|
|
515
529
|
} catch (_) { /* no-op */ }
|
|
516
530
|
|
|
531
|
+
const loadId = objectData?.id ?? '(без id)';
|
|
532
|
+
const validType = typeof objectData?.type === 'string' && objectData.type.length > 0;
|
|
533
|
+
const pos = objectData?.position;
|
|
534
|
+
const validPos = pos && typeof pos === 'object'
|
|
535
|
+
&& typeof pos.x === 'number' && typeof pos.y === 'number';
|
|
536
|
+
if (!validType || !validPos) {
|
|
537
|
+
console.warn(
|
|
538
|
+
`[MoodBoard] Пропуск объекта при загрузке (${loadId}): невалидные type или position`,
|
|
539
|
+
objectData
|
|
540
|
+
);
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
|
|
517
544
|
// Инициализируем флаг компенсации пивота для загруженных объектов.
|
|
518
545
|
// В state координаты хранятся как левый-верх. PIXI позиционирует по центру (anchor/pivot по центру),
|
|
519
546
|
// поэтому при создании нужно ДОБАВИТЬ половину ширины/высоты (т.е. pivotCompensated должен быть false),
|
|
@@ -80,9 +80,9 @@ function sanitizeColor(value, fallback = 0xB0B0B0) {
|
|
|
80
80
|
return Math.max(0, Math.min(0xFFFFFF, Math.round(n)));
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const DEFAULT_CROSS_COLOR =
|
|
84
|
-
const DEFAULT_MID_CROSS_COLOR =
|
|
85
|
-
const DEFAULT_SMALL_CROSS_COLOR =
|
|
83
|
+
const DEFAULT_CROSS_COLOR = 0xCFD3DB; // 207,211,219 — крупная сетка, чуть заметнее
|
|
84
|
+
const DEFAULT_MID_CROSS_COLOR = 0xD6DAE1; // 214,218,225 — средний участок
|
|
85
|
+
const DEFAULT_SMALL_CROSS_COLOR = 0xDDE0E6; // 221,224,230 — плотная сетка, самый тихий
|
|
86
86
|
const COLOR_BAND_HIGH = 'high';
|
|
87
87
|
const COLOR_BAND_MID = 'mid';
|
|
88
88
|
const COLOR_BAND_SMALL = 'small';
|
package/src/initNoBundler.js
CHANGED
|
@@ -131,12 +131,40 @@ export class DataManager {
|
|
|
131
131
|
window.moodboardMindmapHtmlTextLayer.rebuildFromState();
|
|
132
132
|
window.moodboardMindmapHtmlTextLayer.updateAll();
|
|
133
133
|
}
|
|
134
|
+
// Нормализуем геометрию майндмапа после загрузки. Устаревшие доски могли
|
|
135
|
+
// сохранить растянутые координаты (раньше зазор между узлами зависел от
|
|
136
|
+
// зума). Перелейаут с фиксированным мировым зазором приводит их к норме.
|
|
137
|
+
// board:loaded для этого не годится: он эмитится при инициализации ядра
|
|
138
|
+
// на пустой доске, до загрузки объектов и подписки слоя ручек.
|
|
139
|
+
this._relayoutMindmapAfterLoad();
|
|
134
140
|
if (window.moodboardMindmapConnectionLayer) {
|
|
135
141
|
window.moodboardMindmapConnectionLayer.updateAll();
|
|
136
142
|
}
|
|
137
143
|
}, 100);
|
|
138
144
|
|
|
139
145
|
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Приводит геометрию майндмапа к норме после загрузки доски.
|
|
149
|
+
* Запускается дважды: сразу (по сохранённым размерам) и с задержкой —
|
|
150
|
+
* чтобы перехватить узлы после авто-фита ширины/высоты текста.
|
|
151
|
+
*/
|
|
152
|
+
_relayoutMindmapAfterLoad() {
|
|
153
|
+
const run = () => {
|
|
154
|
+
const renderer = window.moodboardHtmlHandlesLayer?.domRenderer;
|
|
155
|
+
if (renderer?.relayoutAllMindmapCompounds) {
|
|
156
|
+
renderer.relayoutAllMindmapCompounds();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
run();
|
|
160
|
+
setTimeout(run, 80);
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
run();
|
|
163
|
+
if (window.moodboardMindmapConnectionLayer) {
|
|
164
|
+
window.moodboardMindmapConnectionLayer.updateAll();
|
|
165
|
+
}
|
|
166
|
+
}, 250);
|
|
167
|
+
}
|
|
140
168
|
|
|
141
169
|
/**
|
|
142
170
|
* Загружает настройки viewport (позиция и зум)
|
|
@@ -42,8 +42,18 @@ export class MoodBoard {
|
|
|
42
42
|
onSave: null,
|
|
43
43
|
onLoad: null,
|
|
44
44
|
onDestroy: null,
|
|
45
|
+
enableComments: false,
|
|
46
|
+
currentUser: null,
|
|
47
|
+
comments: null,
|
|
45
48
|
...options
|
|
46
49
|
};
|
|
50
|
+
|
|
51
|
+
/** @type {{ applyRemote: (event: object) => void } | null} */
|
|
52
|
+
this.comments = null;
|
|
53
|
+
this.commentService = null;
|
|
54
|
+
this.commentPinLayer = null;
|
|
55
|
+
this.commentThreadPopover = null;
|
|
56
|
+
this.commentListPanel = null;
|
|
47
57
|
|
|
48
58
|
this.data = data;
|
|
49
59
|
|
|
@@ -183,6 +193,23 @@ export class MoodBoard {
|
|
|
183
193
|
* Очистка ресурсов
|
|
184
194
|
*/
|
|
185
195
|
destroy() {
|
|
196
|
+
if (this.commentPinLayer) {
|
|
197
|
+
this.commentPinLayer.destroy();
|
|
198
|
+
this.commentPinLayer = null;
|
|
199
|
+
}
|
|
200
|
+
if (this.commentThreadPopover) {
|
|
201
|
+
this.commentThreadPopover.destroy();
|
|
202
|
+
this.commentThreadPopover = null;
|
|
203
|
+
}
|
|
204
|
+
if (this.commentListPanel) {
|
|
205
|
+
this.commentListPanel.destroy();
|
|
206
|
+
this.commentListPanel = null;
|
|
207
|
+
}
|
|
208
|
+
if (this.commentService) {
|
|
209
|
+
this.commentService.destroy();
|
|
210
|
+
this.commentService = null;
|
|
211
|
+
}
|
|
212
|
+
this.comments = null;
|
|
186
213
|
destroyMoodBoard(this);
|
|
187
214
|
}
|
|
188
215
|
|