@sequent-org/moodboard 1.3.5 → 1.4.0
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 +6 -1
- package/src/assets/icons/mindmap.svg +3 -0
- package/src/core/SaveManager.js +44 -15
- package/src/core/commands/MindmapStatePatchCommand.js +85 -0
- package/src/core/commands/UpdateContentCommand.js +47 -4
- package/src/core/flows/LayerAndViewportFlow.js +87 -14
- package/src/core/flows/ObjectLifecycleFlow.js +7 -2
- package/src/core/flows/SaveFlow.js +10 -7
- package/src/core/flows/TransformFlow.js +2 -2
- package/src/core/index.js +81 -11
- package/src/core/rendering/ObjectRenderer.js +7 -2
- package/src/grid/BaseGrid.js +65 -0
- package/src/grid/CrossGrid.js +89 -24
- package/src/grid/CrossGridZoomPhases.js +167 -0
- package/src/grid/DotGrid.js +117 -34
- package/src/grid/DotGridZoomPhases.js +214 -16
- package/src/grid/GridDiagnostics.js +80 -0
- package/src/grid/GridFactory.js +13 -11
- package/src/grid/LineGrid.js +176 -37
- package/src/grid/LineGridZoomPhases.js +163 -0
- package/src/grid/ScreenGridPhaseMachine.js +51 -0
- package/src/mindmap/MindmapCompoundContract.js +235 -0
- package/src/moodboard/ActionHandler.js +1 -0
- package/src/moodboard/DataManager.js +57 -0
- package/src/moodboard/bootstrap/MoodBoardUiFactory.js +21 -0
- package/src/moodboard/integration/MoodBoardEventBindings.js +26 -1
- package/src/moodboard/lifecycle/MoodBoardDestroyer.js +15 -0
- package/src/objects/MindmapObject.js +76 -0
- package/src/objects/ObjectFactory.js +3 -1
- package/src/services/BoardService.js +127 -31
- package/src/services/GridSnapResolver.js +60 -0
- package/src/services/MiroZoomLevels.js +39 -0
- package/src/services/SettingsApplier.js +0 -4
- package/src/services/ZoomPanController.js +51 -32
- package/src/tools/object-tools/PlacementTool.js +12 -3
- package/src/tools/object-tools/SelectTool.js +11 -1
- package/src/tools/object-tools/placement/GhostController.js +100 -1
- package/src/tools/object-tools/placement/PlacementEventsBridge.js +2 -0
- package/src/tools/object-tools/placement/PlacementInputRouter.js +2 -2
- package/src/tools/object-tools/selection/FileNameInlineEditorController.js +2 -2
- package/src/tools/object-tools/selection/InlineEditorController.js +15 -0
- package/src/tools/object-tools/selection/MindmapInlineEditorController.js +716 -0
- package/src/tools/object-tools/selection/SelectInputRouter.js +6 -0
- package/src/tools/object-tools/selection/SelectToolSetup.js +2 -0
- package/src/tools/object-tools/selection/TextEditorLifecycleRegistry.js +12 -16
- package/src/ui/ContextMenu.js +6 -6
- package/src/ui/DotGridDebugPanel.js +253 -0
- package/src/ui/HtmlTextLayer.js +1 -1
- package/src/ui/TextPropertiesPanel.js +2 -2
- package/src/ui/handles/GroupSelectionHandlesController.js +4 -1
- package/src/ui/handles/HandlesDomRenderer.js +1485 -14
- package/src/ui/handles/HandlesEventBridge.js +49 -5
- package/src/ui/handles/HandlesInteractionController.js +4 -4
- package/src/ui/mindmap/MindmapConnectionLayer.js +239 -0
- package/src/ui/mindmap/MindmapHtmlTextLayer.js +285 -0
- package/src/ui/mindmap/MindmapLayoutConfig.js +29 -0
- package/src/ui/mindmap/MindmapTextOverlayAdapter.js +144 -0
- package/src/ui/styles/toolbar.css +1 -0
- package/src/ui/styles/workspace.css +100 -0
- package/src/ui/toolbar/ToolbarActionRouter.js +35 -0
- package/src/ui/toolbar/ToolbarPopupsController.js +6 -6
- package/src/ui/toolbar/ToolbarRenderer.js +1 -0
- package/src/ui/toolbar/ToolbarStateController.js +1 -0
- package/src/utils/iconLoader.js +10 -4
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
const ROOT_ROLE = 'root';
|
|
2
|
+
const CHILD_ROLE = 'child';
|
|
3
|
+
const LEFT_SIDE = 'left';
|
|
4
|
+
const RIGHT_SIDE = 'right';
|
|
5
|
+
const BOTTOM_SIDE = 'bottom';
|
|
6
|
+
const DEBUG_STORAGE_KEY = 'mb:mindmap:compound:debug';
|
|
7
|
+
const BRANCH_COLOR_HEX = Object.freeze([
|
|
8
|
+
'EF9A9A', 'CE93D8', '90CAF9',
|
|
9
|
+
'80DEEA', 'A5D6A7', 'E6EE9C',
|
|
10
|
+
'FFE082', 'BCAAA4', 'B0BEC5',
|
|
11
|
+
]);
|
|
12
|
+
export const MINDMAP_BRANCH_COLOR_PALETTE = Object.freeze(
|
|
13
|
+
BRANCH_COLOR_HEX.map((hex) => Number.parseInt(hex, 16))
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
function asObject(value) {
|
|
17
|
+
return value && typeof value === 'object' ? value : {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function asNonEmptyString(value) {
|
|
21
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function asValidRole(value) {
|
|
25
|
+
return value === ROOT_ROLE || value === CHILD_ROLE ? value : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function asValidSide(value) {
|
|
29
|
+
return value === LEFT_SIDE || value === RIGHT_SIDE || value === BOTTOM_SIDE ? value : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function asOrder(value) {
|
|
33
|
+
if (!Number.isFinite(value)) return null;
|
|
34
|
+
const normalized = Math.floor(value);
|
|
35
|
+
return normalized >= 0 ? normalized : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function asBranchOrder(value) {
|
|
39
|
+
if (!Number.isFinite(value)) return null;
|
|
40
|
+
return Number(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function asOptionalNodeId(value) {
|
|
44
|
+
return asNonEmptyString(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function asBranchColor(value) {
|
|
48
|
+
if (!Number.isFinite(value)) return null;
|
|
49
|
+
const normalized = Math.floor(Number(value));
|
|
50
|
+
if (normalized < 0 || normalized > 0xFFFFFF) return null;
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function pickRandomMindmapBranchColor(randomFn = Math.random) {
|
|
55
|
+
const rand = typeof randomFn === 'function' ? randomFn : Math.random;
|
|
56
|
+
const palette = MINDMAP_BRANCH_COLOR_PALETTE;
|
|
57
|
+
if (!Array.isArray(palette) || palette.length === 0) return null;
|
|
58
|
+
const raw = Number(rand());
|
|
59
|
+
const safe = Number.isFinite(raw) ? raw : 0;
|
|
60
|
+
const idx = Math.max(0, Math.min(palette.length - 1, Math.floor(safe * palette.length)));
|
|
61
|
+
return palette[idx];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function pickRandomMindmapBranchColorExcluding({
|
|
65
|
+
excludedColors = [],
|
|
66
|
+
randomFn = Math.random,
|
|
67
|
+
} = {}) {
|
|
68
|
+
const excluded = new Set(
|
|
69
|
+
(Array.isArray(excludedColors) ? excludedColors : [])
|
|
70
|
+
.filter((value) => Number.isFinite(value))
|
|
71
|
+
.map((value) => Math.floor(Number(value)))
|
|
72
|
+
);
|
|
73
|
+
const palette = MINDMAP_BRANCH_COLOR_PALETTE.filter((color) => !excluded.has(color));
|
|
74
|
+
if (palette.length === 0) return pickRandomMindmapBranchColor(randomFn);
|
|
75
|
+
const rand = typeof randomFn === 'function' ? randomFn : Math.random;
|
|
76
|
+
const raw = Number(rand());
|
|
77
|
+
const safe = Number.isFinite(raw) ? raw : 0;
|
|
78
|
+
const idx = Math.max(0, Math.min(palette.length - 1, Math.floor(safe * palette.length)));
|
|
79
|
+
return palette[idx];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getMindmapMetadataFromProperties(properties) {
|
|
83
|
+
const props = asObject(properties);
|
|
84
|
+
return asObject(props.mindmap);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getCompoundIdFromObject(objectData) {
|
|
88
|
+
const meta = getMindmapMetadataFromProperties(objectData?.properties);
|
|
89
|
+
return asNonEmptyString(meta.compoundId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function nextChildOrder(existingObjects, compoundId) {
|
|
93
|
+
if (!Array.isArray(existingObjects)) return 0;
|
|
94
|
+
let maxOrder = -1;
|
|
95
|
+
for (const obj of existingObjects) {
|
|
96
|
+
if (!obj || obj.type !== 'mindmap') continue;
|
|
97
|
+
const meta = getMindmapMetadataFromProperties(obj.properties);
|
|
98
|
+
if (asNonEmptyString(meta.compoundId) !== compoundId) continue;
|
|
99
|
+
if (asValidRole(meta.role) !== CHILD_ROLE) continue;
|
|
100
|
+
const order = asOrder(meta.order);
|
|
101
|
+
if (order !== null && order > maxOrder) {
|
|
102
|
+
maxOrder = order;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return maxOrder + 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function createRootMindmapIntentMetadata() {
|
|
109
|
+
return {
|
|
110
|
+
compoundId: null,
|
|
111
|
+
role: ROOT_ROLE,
|
|
112
|
+
parentId: null,
|
|
113
|
+
side: null,
|
|
114
|
+
order: 0,
|
|
115
|
+
branchOrder: 0,
|
|
116
|
+
branchColor: null,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function createChildMindmapIntentMetadata({ sourceObjectId, sourceProperties, side }) {
|
|
121
|
+
const sourceMeta = getMindmapMetadataFromProperties(sourceProperties);
|
|
122
|
+
const sourceId = asNonEmptyString(sourceObjectId);
|
|
123
|
+
const sourceCompoundId = asNonEmptyString(sourceMeta.compoundId);
|
|
124
|
+
const sourceRole = asValidRole(sourceMeta.role);
|
|
125
|
+
const inheritedColor = asBranchColor(sourceMeta.branchColor)
|
|
126
|
+
?? asBranchColor(sourceProperties?.strokeColor);
|
|
127
|
+
const branchColor = sourceRole === ROOT_ROLE
|
|
128
|
+
? pickRandomMindmapBranchColor()
|
|
129
|
+
: inheritedColor;
|
|
130
|
+
return {
|
|
131
|
+
compoundId: sourceCompoundId || sourceId,
|
|
132
|
+
role: CHILD_ROLE,
|
|
133
|
+
parentId: sourceId,
|
|
134
|
+
side: asValidSide(side),
|
|
135
|
+
order: null,
|
|
136
|
+
branchOrder: null,
|
|
137
|
+
branchColor,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function normalizeMindmapPropertiesForCreate({
|
|
142
|
+
type,
|
|
143
|
+
objectId,
|
|
144
|
+
properties,
|
|
145
|
+
existingObjects = [],
|
|
146
|
+
}) {
|
|
147
|
+
const props = asObject(properties);
|
|
148
|
+
if (type !== 'mindmap') return props;
|
|
149
|
+
|
|
150
|
+
const meta = getMindmapMetadataFromProperties(props);
|
|
151
|
+
let role = asValidRole(meta.role);
|
|
152
|
+
const parentIdRaw = asNonEmptyString(meta.parentId);
|
|
153
|
+
const branchRootIdRaw = asOptionalNodeId(meta.branchRootId);
|
|
154
|
+
const sideRaw = asValidSide(meta.side);
|
|
155
|
+
let compoundId = asNonEmptyString(meta.compoundId);
|
|
156
|
+
let order = asOrder(meta.order);
|
|
157
|
+
const branchOrder = asBranchOrder(meta.branchOrder);
|
|
158
|
+
let branchColor = asBranchColor(meta.branchColor)
|
|
159
|
+
?? asBranchColor(props.strokeColor);
|
|
160
|
+
|
|
161
|
+
if (!role) {
|
|
162
|
+
role = parentIdRaw ? CHILD_ROLE : ROOT_ROLE;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let parentId = parentIdRaw;
|
|
166
|
+
if (role === ROOT_ROLE) {
|
|
167
|
+
parentId = null;
|
|
168
|
+
} else if (!parentId) {
|
|
169
|
+
// Invalid child payload must degrade to root-safe mode.
|
|
170
|
+
role = ROOT_ROLE;
|
|
171
|
+
parentId = null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!compoundId) {
|
|
175
|
+
if (role === CHILD_ROLE && parentId) {
|
|
176
|
+
const parent = Array.isArray(existingObjects)
|
|
177
|
+
? existingObjects.find((obj) => obj && obj.id === parentId && obj.type === 'mindmap')
|
|
178
|
+
: null;
|
|
179
|
+
compoundId = getCompoundIdFromObject(parent) || parentId;
|
|
180
|
+
} else {
|
|
181
|
+
compoundId = asNonEmptyString(objectId);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!compoundId) {
|
|
186
|
+
compoundId = asNonEmptyString(objectId);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (order === null) {
|
|
190
|
+
order = role === ROOT_ROLE ? 0 : nextChildOrder(existingObjects, compoundId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (role === CHILD_ROLE && branchColor === null && parentId) {
|
|
194
|
+
const parent = Array.isArray(existingObjects)
|
|
195
|
+
? existingObjects.find((obj) => obj && obj.id === parentId && obj.type === 'mindmap')
|
|
196
|
+
: null;
|
|
197
|
+
const parentMeta = getMindmapMetadataFromProperties(parent?.properties);
|
|
198
|
+
branchColor = asBranchColor(parentMeta.branchColor)
|
|
199
|
+
?? asBranchColor(parent?.properties?.strokeColor);
|
|
200
|
+
if (branchColor === null && asValidRole(parentMeta.role) === ROOT_ROLE) {
|
|
201
|
+
branchColor = pickRandomMindmapBranchColor();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
...props,
|
|
207
|
+
mindmap: {
|
|
208
|
+
compoundId,
|
|
209
|
+
role,
|
|
210
|
+
parentId: role === ROOT_ROLE ? null : parentId,
|
|
211
|
+
side: role === CHILD_ROLE ? sideRaw : null,
|
|
212
|
+
order,
|
|
213
|
+
branchOrder: role === CHILD_ROLE ? branchOrder : 0,
|
|
214
|
+
branchRootId: role === CHILD_ROLE
|
|
215
|
+
? (branchRootIdRaw || asNonEmptyString(objectId) || null)
|
|
216
|
+
: null,
|
|
217
|
+
branchColor: role === CHILD_ROLE ? branchColor : null,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function isMindmapCompoundDebugEnabled() {
|
|
223
|
+
if (typeof window === 'undefined') return false;
|
|
224
|
+
if (window.__MB_MINDMAP_COMPOUND_DEBUG__ === true) return true;
|
|
225
|
+
try {
|
|
226
|
+
return window.localStorage?.getItem(DEBUG_STORAGE_KEY) === '1';
|
|
227
|
+
} catch (_) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function logMindmapCompoundDebug(eventName, payload) {
|
|
233
|
+
if (!isMindmapCompoundDebugEnabled()) return;
|
|
234
|
+
console.log(`[mindmap:compound] ${eventName}`, payload);
|
|
235
|
+
}
|
|
@@ -23,6 +23,7 @@ export class ActionHandler {
|
|
|
23
23
|
case 'revit-screenshot-img':
|
|
24
24
|
case 'comment':
|
|
25
25
|
case 'file':
|
|
26
|
+
case 'mindmap':
|
|
26
27
|
// Передаем imageId как extraData для изображений, fileId для файлов
|
|
27
28
|
const extraData = action.imageId ? { imageId: action.imageId } :
|
|
28
29
|
action.fileId ? { fileId: action.fileId } : {};
|
|
@@ -1,16 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Управляет загрузкой и сохранением данных MoodBoard
|
|
3
3
|
*/
|
|
4
|
+
import { logMindmapCompoundDebug } from '../mindmap/MindmapCompoundContract.js';
|
|
4
5
|
export class DataManager {
|
|
5
6
|
constructor(coreMoodboard) {
|
|
6
7
|
this.coreMoodboard = coreMoodboard;
|
|
7
8
|
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Проверяет, нужно ли блокировать подозрительную пустую загрузку.
|
|
12
|
+
* Блокируем только когда на доске уже есть объекты, а входящий snapshot пуст.
|
|
13
|
+
* Для явного разрешения пустой загрузки используем data.meta.allowEmptyLoad === true.
|
|
14
|
+
*/
|
|
15
|
+
_shouldBlockSuspiciousEmptyLoad(data) {
|
|
16
|
+
const incomingObjects = Array.isArray(data?.objects) ? data.objects : [];
|
|
17
|
+
const incomingCount = incomingObjects.length;
|
|
18
|
+
const currentCount = Array.isArray(this.coreMoodboard?.objects) ? this.coreMoodboard.objects.length : 0;
|
|
19
|
+
const allowEmptyLoad = data?.meta?.allowEmptyLoad === true;
|
|
20
|
+
|
|
21
|
+
return incomingCount === 0 && currentCount > 0 && !allowEmptyLoad;
|
|
22
|
+
}
|
|
8
23
|
|
|
9
24
|
/**
|
|
10
25
|
* Загружает данные в MoodBoard
|
|
11
26
|
*/
|
|
12
27
|
loadData(data) {
|
|
13
28
|
if (!data) return;
|
|
29
|
+
if (this._shouldBlockSuspiciousEmptyLoad(data)) {
|
|
30
|
+
console.warn('⚠️ DataManager.loadData: пустой snapshot заблокирован защитой (доска не очищена).');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const incomingMindmap = Array.isArray(data?.objects)
|
|
34
|
+
? data.objects
|
|
35
|
+
.filter((obj) => obj?.type === 'mindmap')
|
|
36
|
+
.map((obj) => ({
|
|
37
|
+
id: obj.id || null,
|
|
38
|
+
compoundId: obj.properties?.mindmap?.compoundId || null,
|
|
39
|
+
role: obj.properties?.mindmap?.role || null,
|
|
40
|
+
parentId: obj.properties?.mindmap?.parentId || null,
|
|
41
|
+
}))
|
|
42
|
+
: [];
|
|
43
|
+
if (incomingMindmap.length > 0) {
|
|
44
|
+
logMindmapCompoundDebug('load:roundtrip:input', {
|
|
45
|
+
totalMindmapNodes: incomingMindmap.length,
|
|
46
|
+
sample: incomingMindmap.slice(0, 5),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
14
49
|
|
|
15
50
|
|
|
16
51
|
|
|
@@ -61,6 +96,21 @@ export class DataManager {
|
|
|
61
96
|
}
|
|
62
97
|
});
|
|
63
98
|
}
|
|
99
|
+
if (incomingMindmap.length > 0) {
|
|
100
|
+
const stateObjects = this.coreMoodboard?.state?.state?.objects || [];
|
|
101
|
+
const loadedMindmap = stateObjects
|
|
102
|
+
.filter((obj) => obj?.type === 'mindmap')
|
|
103
|
+
.map((obj) => ({
|
|
104
|
+
id: obj.id || null,
|
|
105
|
+
compoundId: obj.properties?.mindmap?.compoundId || null,
|
|
106
|
+
role: obj.properties?.mindmap?.role || null,
|
|
107
|
+
parentId: obj.properties?.mindmap?.parentId || null,
|
|
108
|
+
}));
|
|
109
|
+
logMindmapCompoundDebug('load:roundtrip:state-after-load', {
|
|
110
|
+
totalMindmapNodes: loadedMindmap.length,
|
|
111
|
+
sample: loadedMindmap.slice(0, 5),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
64
114
|
|
|
65
115
|
// Загружаем viewport
|
|
66
116
|
if (data.viewport) {
|
|
@@ -76,6 +126,13 @@ export class DataManager {
|
|
|
76
126
|
window.moodboardHtmlTextLayer.rebuildFromState();
|
|
77
127
|
window.moodboardHtmlTextLayer.updateAll();
|
|
78
128
|
}
|
|
129
|
+
if (window.moodboardMindmapHtmlTextLayer) {
|
|
130
|
+
window.moodboardMindmapHtmlTextLayer.rebuildFromState();
|
|
131
|
+
window.moodboardMindmapHtmlTextLayer.updateAll();
|
|
132
|
+
}
|
|
133
|
+
if (window.moodboardMindmapConnectionLayer) {
|
|
134
|
+
window.moodboardMindmapConnectionLayer.updateAll();
|
|
135
|
+
}
|
|
79
136
|
}, 100);
|
|
80
137
|
|
|
81
138
|
}
|
|
@@ -3,8 +3,11 @@ import { SaveStatus } from '../../ui/SaveStatus.js';
|
|
|
3
3
|
import { Topbar } from '../../ui/Topbar.js';
|
|
4
4
|
import { ZoomPanel } from '../../ui/ZoomPanel.js';
|
|
5
5
|
import { MapPanel } from '../../ui/MapPanel.js';
|
|
6
|
+
import { DotGridDebugPanel } from '../../ui/DotGridDebugPanel.js';
|
|
6
7
|
import { ContextMenu } from '../../ui/ContextMenu.js';
|
|
7
8
|
import { HtmlTextLayer } from '../../ui/HtmlTextLayer.js';
|
|
9
|
+
import { MindmapHtmlTextLayer } from '../../ui/mindmap/MindmapHtmlTextLayer.js';
|
|
10
|
+
import { MindmapConnectionLayer } from '../../ui/mindmap/MindmapConnectionLayer.js';
|
|
8
11
|
import { HtmlHandlesLayer } from '../../ui/HtmlHandlesLayer.js';
|
|
9
12
|
import { CommentPopover } from '../../ui/CommentPopover.js';
|
|
10
13
|
import { TextPropertiesPanel } from '../../ui/TextPropertiesPanel.js';
|
|
@@ -69,6 +72,13 @@ function initMapbar(board) {
|
|
|
69
72
|
);
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
function initDotGridDebugPanel(board) {
|
|
76
|
+
board.dotGridDebugPanel = new DotGridDebugPanel(
|
|
77
|
+
board.workspaceElement,
|
|
78
|
+
board.coreMoodboard
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
function initContextMenu(board) {
|
|
73
83
|
board.contextMenu = new ContextMenu(
|
|
74
84
|
board.canvasContainer,
|
|
@@ -79,12 +89,18 @@ function initContextMenu(board) {
|
|
|
79
89
|
function initHtmlLayersAndPanels(board) {
|
|
80
90
|
board.htmlTextLayer = new HtmlTextLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
81
91
|
board.htmlTextLayer.attach();
|
|
92
|
+
board.mindmapHtmlTextLayer = new MindmapHtmlTextLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
93
|
+
board.mindmapHtmlTextLayer.attach();
|
|
94
|
+
board.mindmapConnectionLayer = new MindmapConnectionLayer(board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
95
|
+
board.mindmapConnectionLayer.attach();
|
|
82
96
|
|
|
83
97
|
board.htmlHandlesLayer = new HtmlHandlesLayer(board.canvasContainer, board.coreMoodboard.eventBus, board.coreMoodboard);
|
|
84
98
|
board.htmlHandlesLayer.attach();
|
|
85
99
|
|
|
86
100
|
if (typeof window !== 'undefined') {
|
|
87
101
|
window.moodboardHtmlTextLayer = board.htmlTextLayer;
|
|
102
|
+
window.moodboardMindmapHtmlTextLayer = board.mindmapHtmlTextLayer;
|
|
103
|
+
window.moodboardMindmapConnectionLayer = board.mindmapConnectionLayer;
|
|
88
104
|
window.moodboardHtmlHandlesLayer = board.htmlHandlesLayer;
|
|
89
105
|
}
|
|
90
106
|
|
|
@@ -104,6 +120,11 @@ export function createMoodBoardUi(board) {
|
|
|
104
120
|
initTopbar(board);
|
|
105
121
|
initZoombar(board);
|
|
106
122
|
initMapbar(board);
|
|
123
|
+
// Debug-панель сетки оставляем в проекте, но не показываем по умолчанию.
|
|
124
|
+
// Для включения: передать showGridDebugPanel: true в options MoodBoard.
|
|
125
|
+
if (board?.options?.showGridDebugPanel === true) {
|
|
126
|
+
initDotGridDebugPanel(board);
|
|
127
|
+
}
|
|
107
128
|
initContextMenu(board);
|
|
108
129
|
initHtmlLayersAndPanels(board);
|
|
109
130
|
}
|
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
import { Events } from '../../core/events/Events.js';
|
|
2
|
+
import { logMindmapCompoundDebug } from '../../mindmap/MindmapCompoundContract.js';
|
|
2
3
|
|
|
3
4
|
export function bindToolbarEvents(board) {
|
|
4
5
|
board.coreMoodboard.eventBus.on(Events.UI.ToolbarAction, (action) => {
|
|
5
|
-
|
|
6
|
+
if (action?.type === 'mindmap') {
|
|
7
|
+
logMindmapCompoundDebug('toolbar:action', {
|
|
8
|
+
type: action.type,
|
|
9
|
+
position: action.position || null,
|
|
10
|
+
mindmap: action.properties?.mindmap || null,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const createdObject = board.actionHandler.handleToolbarAction(action);
|
|
14
|
+
if (action?.type === 'mindmap' && createdObject?.id) {
|
|
15
|
+
const content = String(createdObject?.properties?.content || '');
|
|
16
|
+
if (content.trim().length === 0) {
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
board.coreMoodboard.eventBus.emit(Events.Keyboard.ToolSelect, { tool: 'select' });
|
|
19
|
+
board.coreMoodboard.eventBus.emit(Events.Tool.ObjectEdit, {
|
|
20
|
+
object: {
|
|
21
|
+
id: createdObject.id,
|
|
22
|
+
type: 'mindmap',
|
|
23
|
+
position: createdObject.position || null,
|
|
24
|
+
properties: createdObject.properties || {},
|
|
25
|
+
},
|
|
26
|
+
create: true,
|
|
27
|
+
});
|
|
28
|
+
}, 20);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
6
31
|
});
|
|
7
32
|
}
|
|
8
33
|
|
|
@@ -49,6 +49,12 @@ export function destroyMoodBoard(board) {
|
|
|
49
49
|
safeDestroy(board.htmlTextLayer, 'htmlTextLayer');
|
|
50
50
|
board.htmlTextLayer = null;
|
|
51
51
|
|
|
52
|
+
safeDestroy(board.mindmapHtmlTextLayer, 'mindmapHtmlTextLayer');
|
|
53
|
+
board.mindmapHtmlTextLayer = null;
|
|
54
|
+
|
|
55
|
+
safeDestroy(board.mindmapConnectionLayer, 'mindmapConnectionLayer');
|
|
56
|
+
board.mindmapConnectionLayer = null;
|
|
57
|
+
|
|
52
58
|
safeDestroy(board.htmlHandlesLayer, 'htmlHandlesLayer');
|
|
53
59
|
board.htmlHandlesLayer = null;
|
|
54
60
|
|
|
@@ -64,6 +70,9 @@ export function destroyMoodBoard(board) {
|
|
|
64
70
|
safeDestroy(board.mapbar, 'mapbar');
|
|
65
71
|
board.mapbar = null;
|
|
66
72
|
|
|
73
|
+
safeDestroy(board.dotGridDebugPanel, 'dotGridDebugPanel');
|
|
74
|
+
board.dotGridDebugPanel = null;
|
|
75
|
+
|
|
67
76
|
safeDestroy(board.coreMoodboard, 'coreMoodboard');
|
|
68
77
|
board.coreMoodboard = null;
|
|
69
78
|
|
|
@@ -82,6 +91,12 @@ export function destroyMoodBoard(board) {
|
|
|
82
91
|
if (window.moodboardHtmlTextLayer === board.htmlTextLayer) {
|
|
83
92
|
window.moodboardHtmlTextLayer = null;
|
|
84
93
|
}
|
|
94
|
+
if (window.moodboardMindmapHtmlTextLayer === board.mindmapHtmlTextLayer) {
|
|
95
|
+
window.moodboardMindmapHtmlTextLayer = null;
|
|
96
|
+
}
|
|
97
|
+
if (window.moodboardMindmapConnectionLayer === board.mindmapConnectionLayer) {
|
|
98
|
+
window.moodboardMindmapConnectionLayer = null;
|
|
99
|
+
}
|
|
85
100
|
if (window.moodboardHtmlHandlesLayer === board.htmlHandlesLayer) {
|
|
86
101
|
window.moodboardHtmlHandlesLayer = null;
|
|
87
102
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as PIXI from 'pixi.js';
|
|
2
|
+
import { MINDMAP_LAYOUT } from '../ui/mindmap/MindmapLayoutConfig.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Простой объект mindmap: прямоугольник с синей обводкой и полупрозрачной синей заливкой.
|
|
6
|
+
*/
|
|
7
|
+
export class MindmapObject {
|
|
8
|
+
constructor(objectData = {}) {
|
|
9
|
+
this.objectData = objectData;
|
|
10
|
+
this.width = objectData.width || objectData.properties?.width || MINDMAP_LAYOUT.width;
|
|
11
|
+
this.height = objectData.height || objectData.properties?.height || MINDMAP_LAYOUT.height;
|
|
12
|
+
const props = objectData.properties || {};
|
|
13
|
+
this.strokeColor = (typeof props.strokeColor === 'number') ? props.strokeColor : 0x2563EB;
|
|
14
|
+
this.fillColor = (typeof props.fillColor === 'number') ? props.fillColor : 0x3B82F6;
|
|
15
|
+
this.fillAlpha = (typeof props.fillAlpha === 'number') ? props.fillAlpha : 0.25;
|
|
16
|
+
this.strokeWidth = (typeof props.strokeWidth === 'number') ? props.strokeWidth : 1;
|
|
17
|
+
this.capsuleBaseHeight = (typeof props.capsuleBaseHeight === 'number')
|
|
18
|
+
? Math.max(1, Math.round(props.capsuleBaseHeight))
|
|
19
|
+
: Math.max(1, Math.round(Math.min(this.height, MINDMAP_LAYOUT.height)));
|
|
20
|
+
|
|
21
|
+
this.graphics = new PIXI.Graphics();
|
|
22
|
+
this.graphics.roundPixels = true;
|
|
23
|
+
this._draw();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getPixi() {
|
|
27
|
+
return this.graphics;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
updateSize(size) {
|
|
31
|
+
if (!size) return;
|
|
32
|
+
this.width = Math.max(1, size.width || this.width);
|
|
33
|
+
this.height = Math.max(1, size.height || this.height);
|
|
34
|
+
this._redrawPreserveTransform();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_redrawPreserveTransform() {
|
|
38
|
+
const g = this.graphics;
|
|
39
|
+
const centerX = g.x;
|
|
40
|
+
const centerY = g.y;
|
|
41
|
+
const rot = g.rotation || 0;
|
|
42
|
+
|
|
43
|
+
this._draw();
|
|
44
|
+
g.pivot.set(Math.floor(this.width / 2), Math.floor(this.height / 2));
|
|
45
|
+
g.x = Math.round(centerX);
|
|
46
|
+
g.y = Math.round(centerY);
|
|
47
|
+
g.rotation = rot;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_draw() {
|
|
51
|
+
const g = this.graphics;
|
|
52
|
+
g.clear();
|
|
53
|
+
const dynamicRadius = Math.max(0, Math.floor(Math.min(this.width, this.height) / 2));
|
|
54
|
+
const fixedBaseRadius = Math.max(0, Math.floor(this.capsuleBaseHeight / 2));
|
|
55
|
+
const capsuleRadius = Math.min(dynamicRadius, fixedBaseRadius);
|
|
56
|
+
|
|
57
|
+
g.beginFill(this.fillColor, this.fillAlpha);
|
|
58
|
+
g.drawRoundedRect(0, 0, this.width, this.height, capsuleRadius);
|
|
59
|
+
g.endFill();
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
g.lineStyle({
|
|
63
|
+
width: this.strokeWidth,
|
|
64
|
+
color: this.strokeColor,
|
|
65
|
+
alpha: 1,
|
|
66
|
+
alignment: 0,
|
|
67
|
+
cap: 'round',
|
|
68
|
+
join: 'round',
|
|
69
|
+
miterLimit: 2,
|
|
70
|
+
});
|
|
71
|
+
} catch (_) {
|
|
72
|
+
g.lineStyle(this.strokeWidth, this.strokeColor, 1, 0);
|
|
73
|
+
}
|
|
74
|
+
g.drawRoundedRect(0, 0, this.width, this.height, capsuleRadius);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -8,6 +8,7 @@ import { RevitScreenshotImageObject } from './RevitScreenshotImageObject.js';
|
|
|
8
8
|
import { CommentObject } from './CommentObject.js';
|
|
9
9
|
import { NoteObject } from './NoteObject.js';
|
|
10
10
|
import { FileObject } from './FileObject.js';
|
|
11
|
+
import { MindmapObject } from './MindmapObject.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Фабрика объектов холста
|
|
@@ -25,7 +26,8 @@ export class ObjectFactory {
|
|
|
25
26
|
['revit-screenshot-img', RevitScreenshotImageObject],
|
|
26
27
|
['comment', CommentObject],
|
|
27
28
|
['note', NoteObject],
|
|
28
|
-
['file', FileObject]
|
|
29
|
+
['file', FileObject],
|
|
30
|
+
['mindmap', MindmapObject]
|
|
29
31
|
]);
|
|
30
32
|
|
|
31
33
|
/**
|