@mindexec/cli 0.2.478 → 0.2.479
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/README.md +323 -323
- package/codex-model-catalog.js +202 -202
- package/codex-runtime.js +1608 -1608
- package/desktop-workspace-state.cjs +120 -120
- package/electron/main.cjs +517 -517
- package/electron/preload.cjs +17 -17
- package/electron/recurring-update-owner-smoke.mjs +55 -55
- package/electron/recurring-update-owner.cjs +97 -97
- package/electron/source-smoke.mjs +108 -108
- package/electron/update-feed-resolver.cjs +79 -79
- package/electron/update-feed.json +10 -10
- package/electron/update-manager-smoke.mjs +146 -146
- package/electron/update-manager.cjs +297 -297
- package/electron/windows-package-smoke.mjs +156 -156
- package/launch-bridge.cjs +236 -236
- package/lzo1x.js +110 -110
- package/mode4-atlas-worker.js +440 -440
- package/mode4-atlas.js +130 -130
- package/package.json +1 -1
- package/port-guard.cjs +285 -285
- package/remote-fast/osx-arm64/MacScreenCaptureKitH264.swift +339 -339
- package/remote-fast/osx-arm64/MacSystemAudioCapture.swift +214 -214
- package/remote-fast/osx-arm64/UnixRawCaptureHelper.mjs +80 -80
- package/remote-fast/osx-arm64/livedesk-raw-capture-resampler.mjs +73 -73
- package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/osx-x64/MacScreenCaptureKitH264.swift +339 -339
- package/remote-fast/osx-x64/MacSystemAudioCapture.swift +214 -214
- package/remote-fast/osx-x64/UnixRawCaptureHelper.mjs +80 -80
- package/remote-fast/osx-x64/livedesk-raw-capture-resampler.mjs +73 -73
- package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
- package/remote-hub.js +3698 -3698
- package/scripts/auth-session-smoke.mjs +262 -262
- package/scripts/board-render-ownership-smoke.mjs +85 -85
- package/scripts/codex-model-catalog-smoke.mjs +150 -150
- package/scripts/codex-runtime-home-smoke.mjs +58 -58
- package/scripts/codex-sdk-image-live-smoke.mjs +43 -43
- package/scripts/codex-sdk-image-runtime-smoke.mjs +122 -122
- package/scripts/codex-sdk-runtime-smoke.mjs +89 -89
- package/scripts/desktop-update-publisher-smoke.mjs +89 -89
- package/scripts/desktop-update-worker-smoke.mjs +72 -72
- package/scripts/desktop-workspace-state-smoke.mjs +81 -81
- package/scripts/drive-installed-update-smoke.mjs +67 -67
- package/scripts/npm-fresh-install-smoke.mjs +216 -216
- package/scripts/publish-mindexec-desktop-updates.mjs +224 -224
- package/scripts/remote-agent-managed-smoke.mjs +318 -318
- package/scripts/remote-agent-package-smoke.mjs +64 -64
- package/scripts/remote-agent-ws-smoke.mjs +269 -269
- package/scripts/remote-fast-live-rate-smoke.mjs +343 -343
- package/scripts/remote-fast-mdm-browser-smoke.mjs +453 -453
- package/scripts/remote-fleet-render-smoke.mjs +1350 -1350
- package/scripts/remote-frame-ws-smoke.mjs +234 -234
- package/scripts/remote-http-smoke.mjs +592 -592
- package/scripts/remote-hub-identity-smoke.mjs +146 -146
- package/scripts/remote-hub-scale-smoke.mjs +124 -124
- package/scripts/remote-hub-smoke.mjs +699 -699
- package/scripts/remote-input-ws-smoke.mjs +263 -263
- package/scripts/remote-registry-follower-smoke.mjs +815 -815
- package/scripts/setup-tree-sitter-grammars.mjs +59 -59
- package/server.js +7510 -7510
- package/start-bridge.bat +32 -32
- package/start-bridge.sh +81 -81
- package/tree-sitter-grammars/README.md +18 -0
- package/tree-sitter-grammars/tree-sitter-c_sharp.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-go.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-java.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-javascript.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-python.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-rust.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-tsx.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-typescript.wasm +0 -0
- package/wwwroot/_content/MindExecution.Plugins.Admin/css/admin-dashboard.css +546 -546
- package/wwwroot/_content/MindExecution.Plugins.Directory/background.png +0 -0
- package/wwwroot/_content/MindExecution.Plugins.YouTube/css/youtube-search.css +251 -251
- package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +3789 -3789
- package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Bold.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Regular.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/js/agent-visualization.js +27 -27
- package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1449 -1449
- package/wwwroot/_content/MindExecution.Shared/js/code-master.js +8316 -8316
- package/wwwroot/_content/MindExecution.Shared/js/file-system-helper.js +338 -338
- package/wwwroot/_content/MindExecution.Shared/js/marked.min.js +69 -69
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-animated-image-preview.js +270 -270
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-automation-edge-motion.js +170 -170
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-board-render-ownership.js +81 -81
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +10506 -10506
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js.backup +1059 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-hotpath.js +902 -902
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +24637 -24637
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dev-guards.js +325 -325
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js +1491 -1491
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js.bak +434 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-glow-shader.js +70 -70
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-image-policy.js +218 -218
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +7078 -7078
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js +160 -160
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +9958 -9958
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-video-placeholder.js +107 -107
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +1004 -1004
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +1351 -1351
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-multi-select.js +865 -865
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +554 -554
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +4744 -4744
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-normal-boundary-optimizer.js +624 -624
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js +241 -241
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js.backup +372 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +2290 -2290
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-remote-codecs.js +202 -202
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-render-plan.js +1971 -1971
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-lod-system.js +93 -93
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +2391 -2391
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js.backup +1258 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-video-playback-controls.js +95 -95
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-visibility-worker.js +895 -895
- package/wwwroot/_content/MindExecution.Shared/js/mindmap-toolbar.js +636 -636
- package/wwwroot/_content/MindExecution.Shared/js/native-drop-handler.js +45 -45
- package/wwwroot/_content/MindExecution.Shared/js/plan-master.js +117 -117
- package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +114 -114
- package/wwwroot/_content/MindExecution.Shared/js/texture-worker-manager.js +10 -10
- package/wwwroot/_content/MindExecution.Shared/js/three.min.js +6 -6
- package/wwwroot/_content/MindExecution.Shared/js/token-manager.js +7 -7
- package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js +6 -6
- package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js.map +2 -2
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/css/all.min.css +8 -8
- package/wwwroot/_content/MindExecution.Shared/models/vocab.txt +30522 -30522
- package/wwwroot/_framework/{Google.Protobuf.33g2gb5is0.dll → Google.Protobuf.9h59ukbel7.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Core.z100xo4mg0.dll → MindExecution.Core.xxsj2s456b.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.i9cldrx8yc.dll → MindExecution.Kernel.5jofnc0wrd.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.48lzqddb0a.dll → MindExecution.Plugins.Admin.t7m4jnqjg3.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.7m8u1q142p.dll → MindExecution.Plugins.Business.6yjlhhavap.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.cl5wmr53od.dll → MindExecution.Plugins.Concept.iikqq5zxu0.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.slipi39041.dll → MindExecution.Plugins.Directory.c5ribyne4m.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.qfge05l671.dll → MindExecution.Plugins.PlanMaster.365bbuifr9.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.tn7v29ut77.dll → MindExecution.Plugins.YouTube.ax415cilpk.dll} +0 -0
- package/wwwroot/_framework/MindExecution.Shared.21e2nvf86u.dll +0 -0
- package/wwwroot/_framework/{MindExecution.Web.rwawrxxk38.dll → MindExecution.Web.ppgm8rey59.dll} +0 -0
- package/wwwroot/_framework/{System.Collections.23yxgetbju.dll → System.Collections.zyvbaq6mpd.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +25 -25
- package/wwwroot/_headers +40 -40
- package/wwwroot/_redirects +1 -1
- package/wwwroot/index.html +772 -772
- package/wwwroot/js/marketing-tool.js +180 -180
- package/wwwroot/manifest.webmanifest +23 -23
- package/wwwroot/service-worker-assets.js +109 -81
- package/wwwroot/service-worker.js +33 -33
- package/wwwroot/_framework/MindExecution.Shared.rjqlf0umjn.dll +0 -0
|
@@ -4,55 +4,55 @@
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
6
|
let _module = null;
|
|
7
|
-
let multiSelectOverlay = null;
|
|
8
|
-
let multiSelectMenu = null;
|
|
9
|
-
let singleImageOverlay = null;
|
|
10
|
-
let currentBounds = null;
|
|
7
|
+
let multiSelectOverlay = null;
|
|
8
|
+
let multiSelectMenu = null;
|
|
9
|
+
let singleImageOverlay = null;
|
|
10
|
+
let currentBounds = null;
|
|
11
11
|
let isResizing = false;
|
|
12
12
|
let resizeHandle = null;
|
|
13
13
|
let resizeStartBounds = null;
|
|
14
14
|
let resizeStartMouse = null;
|
|
15
15
|
let initialNodeSizes = new Map(); // Store initial sizes for proportional scaling
|
|
16
16
|
|
|
17
|
-
const SINGLE_IMAGE_HANDLE_SIZE = 10;
|
|
18
|
-
const MULTI_SELECT_SCREEN_PADDING_PX = 10;
|
|
19
|
-
|
|
20
|
-
// ▼▼▼ [Perf] Throttling and caching for drag performance ▼▼▼
|
|
21
|
-
const UPDATE_THROTTLE_MS = 32; // ~30fps for overlay updates during drag
|
|
22
|
-
let lastUpdateTime = 0;
|
|
23
|
-
let cachedImageNodeIds = null; // Cached list of selected image node IDs
|
|
24
|
-
let isDragging = false; // Derived from module.isDraggingMultipleNodes
|
|
25
|
-
// ▲▲▲ [Perf] ▲▲▲
|
|
26
|
-
|
|
27
|
-
function setBrowserSelectionLock(active, reason) {
|
|
28
|
-
if (window.MindMapInteractions?.setBrowserSelectionLock) {
|
|
29
|
-
window.MindMapInteractions.setBrowserSelectionLock(active, reason || 'multi-select-resize');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
document?.body?.classList?.toggle?.('mindcanvas-browser-selection-locked', !!active);
|
|
34
|
-
document?.documentElement?.classList?.toggle?.('mindcanvas-browser-selection-locked', !!active);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function init(module) {
|
|
38
|
-
_module = module;
|
|
39
|
-
createMultiSelectOverlay();
|
|
40
|
-
createSingleImageOverlay();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function createMultiSelectOverlay() {
|
|
44
|
-
if (multiSelectOverlay?.isConnected) return;
|
|
45
|
-
|
|
46
|
-
// Create overlay container (absolutely positioned in 3D space via CSS3DObject)
|
|
47
|
-
multiSelectOverlay = document.createElement('div');
|
|
48
|
-
multiSelectOverlay.className = 'multi-select-overlay';
|
|
49
|
-
multiSelectOverlay.style.cssText = `
|
|
50
|
-
position: fixed;
|
|
51
|
-
pointer-events: none;
|
|
52
|
-
border: 2px dashed #2563eb;
|
|
53
|
-
background: rgba(37, 99, 235, 0.05);
|
|
54
|
-
border-radius: 8px;
|
|
55
|
-
display: none;
|
|
17
|
+
const SINGLE_IMAGE_HANDLE_SIZE = 10;
|
|
18
|
+
const MULTI_SELECT_SCREEN_PADDING_PX = 10;
|
|
19
|
+
|
|
20
|
+
// ▼▼▼ [Perf] Throttling and caching for drag performance ▼▼▼
|
|
21
|
+
const UPDATE_THROTTLE_MS = 32; // ~30fps for overlay updates during drag
|
|
22
|
+
let lastUpdateTime = 0;
|
|
23
|
+
let cachedImageNodeIds = null; // Cached list of selected image node IDs
|
|
24
|
+
let isDragging = false; // Derived from module.isDraggingMultipleNodes
|
|
25
|
+
// ▲▲▲ [Perf] ▲▲▲
|
|
26
|
+
|
|
27
|
+
function setBrowserSelectionLock(active, reason) {
|
|
28
|
+
if (window.MindMapInteractions?.setBrowserSelectionLock) {
|
|
29
|
+
window.MindMapInteractions.setBrowserSelectionLock(active, reason || 'multi-select-resize');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
document?.body?.classList?.toggle?.('mindcanvas-browser-selection-locked', !!active);
|
|
34
|
+
document?.documentElement?.classList?.toggle?.('mindcanvas-browser-selection-locked', !!active);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function init(module) {
|
|
38
|
+
_module = module;
|
|
39
|
+
createMultiSelectOverlay();
|
|
40
|
+
createSingleImageOverlay();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createMultiSelectOverlay() {
|
|
44
|
+
if (multiSelectOverlay?.isConnected) return;
|
|
45
|
+
|
|
46
|
+
// Create overlay container (absolutely positioned in 3D space via CSS3DObject)
|
|
47
|
+
multiSelectOverlay = document.createElement('div');
|
|
48
|
+
multiSelectOverlay.className = 'multi-select-overlay';
|
|
49
|
+
multiSelectOverlay.style.cssText = `
|
|
50
|
+
position: fixed;
|
|
51
|
+
pointer-events: none;
|
|
52
|
+
border: 2px dashed #2563eb;
|
|
53
|
+
background: rgba(37, 99, 235, 0.05);
|
|
54
|
+
border-radius: 8px;
|
|
55
|
+
display: none;
|
|
56
56
|
z-index: 1000;
|
|
57
57
|
`;
|
|
58
58
|
|
|
@@ -105,59 +105,59 @@
|
|
|
105
105
|
// Initial empty menu - will be updated by updateMenuButtons()
|
|
106
106
|
|
|
107
107
|
multiSelectOverlay.appendChild(multiSelectMenu);
|
|
108
|
-
|
|
109
|
-
// Append to body (will be positioned in screen space)
|
|
110
|
-
document.body.appendChild(multiSelectOverlay);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function createSingleImageOverlay() {
|
|
114
|
-
if (singleImageOverlay?.isConnected) return;
|
|
115
|
-
|
|
116
|
-
singleImageOverlay = document.createElement('div');
|
|
117
|
-
singleImageOverlay.className = 'single-image-selection-overlay';
|
|
118
|
-
singleImageOverlay.style.cssText = `
|
|
119
|
-
position: fixed;
|
|
120
|
-
pointer-events: none;
|
|
121
|
-
background: transparent;
|
|
122
|
-
box-sizing: border-box;
|
|
123
|
-
outline: 1px dashed #2563eb;
|
|
124
|
-
outline-offset: 1px;
|
|
125
|
-
display: none;
|
|
126
|
-
visibility: hidden;
|
|
127
|
-
z-index: 999;
|
|
128
|
-
`;
|
|
129
|
-
|
|
130
|
-
const corners = ['nw', 'ne', 'sw', 'se'];
|
|
131
|
-
const handleOffset = SINGLE_IMAGE_HANDLE_SIZE / 2;
|
|
132
|
-
corners.forEach(corner => {
|
|
133
|
-
const handle = document.createElement('div');
|
|
134
|
-
handle.className = `single-image-selection-handle-${corner}`;
|
|
135
|
-
handle.style.cssText = `
|
|
136
|
-
position: absolute;
|
|
137
|
-
width: ${SINGLE_IMAGE_HANDLE_SIZE}px;
|
|
138
|
-
height: ${SINGLE_IMAGE_HANDLE_SIZE}px;
|
|
139
|
-
background: #2563eb;
|
|
140
|
-
pointer-events: none;
|
|
141
|
-
`;
|
|
142
|
-
|
|
143
|
-
if (corner.includes('n')) handle.style.top = `${-handleOffset}px`;
|
|
144
|
-
if (corner.includes('s')) handle.style.bottom = `${-handleOffset}px`;
|
|
145
|
-
if (corner.includes('w')) handle.style.left = `${-handleOffset}px`;
|
|
146
|
-
if (corner.includes('e')) handle.style.right = `${-handleOffset}px`;
|
|
147
|
-
|
|
148
|
-
singleImageOverlay.appendChild(handle);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
document.body.appendChild(singleImageOverlay);
|
|
152
|
-
}
|
|
108
|
+
|
|
109
|
+
// Append to body (will be positioned in screen space)
|
|
110
|
+
document.body.appendChild(multiSelectOverlay);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function createSingleImageOverlay() {
|
|
114
|
+
if (singleImageOverlay?.isConnected) return;
|
|
115
|
+
|
|
116
|
+
singleImageOverlay = document.createElement('div');
|
|
117
|
+
singleImageOverlay.className = 'single-image-selection-overlay';
|
|
118
|
+
singleImageOverlay.style.cssText = `
|
|
119
|
+
position: fixed;
|
|
120
|
+
pointer-events: none;
|
|
121
|
+
background: transparent;
|
|
122
|
+
box-sizing: border-box;
|
|
123
|
+
outline: 1px dashed #2563eb;
|
|
124
|
+
outline-offset: 1px;
|
|
125
|
+
display: none;
|
|
126
|
+
visibility: hidden;
|
|
127
|
+
z-index: 999;
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
const corners = ['nw', 'ne', 'sw', 'se'];
|
|
131
|
+
const handleOffset = SINGLE_IMAGE_HANDLE_SIZE / 2;
|
|
132
|
+
corners.forEach(corner => {
|
|
133
|
+
const handle = document.createElement('div');
|
|
134
|
+
handle.className = `single-image-selection-handle-${corner}`;
|
|
135
|
+
handle.style.cssText = `
|
|
136
|
+
position: absolute;
|
|
137
|
+
width: ${SINGLE_IMAGE_HANDLE_SIZE}px;
|
|
138
|
+
height: ${SINGLE_IMAGE_HANDLE_SIZE}px;
|
|
139
|
+
background: #2563eb;
|
|
140
|
+
pointer-events: none;
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
if (corner.includes('n')) handle.style.top = `${-handleOffset}px`;
|
|
144
|
+
if (corner.includes('s')) handle.style.bottom = `${-handleOffset}px`;
|
|
145
|
+
if (corner.includes('w')) handle.style.left = `${-handleOffset}px`;
|
|
146
|
+
if (corner.includes('e')) handle.style.right = `${-handleOffset}px`;
|
|
147
|
+
|
|
148
|
+
singleImageOverlay.appendChild(handle);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
document.body.appendChild(singleImageOverlay);
|
|
152
|
+
}
|
|
153
153
|
|
|
154
154
|
// Resize move/end are handled by MindMapInteractions (centralized)
|
|
155
155
|
|
|
156
156
|
function showMultiSelectBounds(selectedImageNodeIds) {
|
|
157
157
|
if (!_module || selectedImageNodeIds.length < 2) {
|
|
158
|
-
hide();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
158
|
+
hide();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
161
|
|
|
162
162
|
// Calculate bounding box of selected nodes
|
|
163
163
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
@@ -182,12 +182,12 @@
|
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
if (minX === Infinity) {
|
|
185
|
-
hide();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
185
|
+
hide();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
188
|
|
|
189
189
|
// Add padding
|
|
190
|
-
const padding = 10;
|
|
190
|
+
const padding = 10;
|
|
191
191
|
minX -= padding;
|
|
192
192
|
minY -= padding;
|
|
193
193
|
maxX += padding;
|
|
@@ -212,86 +212,86 @@
|
|
|
212
212
|
}
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
-
// Convert node bounds to screen coordinates
|
|
216
|
-
updateOverlayPosition(selectedImageNodeIds);
|
|
217
|
-
multiSelectOverlay.style.display = 'block';
|
|
218
|
-
multiSelectOverlay.style.visibility = 'visible';
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function getNodeScreenRect(nodeEntry) {
|
|
222
|
-
if (!nodeEntry || !_module) return null;
|
|
223
|
-
|
|
224
|
-
if (nodeEntry.currentType === 'CSS') {
|
|
225
|
-
const element = nodeEntry.cssObject?.element;
|
|
226
|
-
if (element?.isConnected) {
|
|
227
|
-
const rect = element.getBoundingClientRect();
|
|
228
|
-
if (Number.isFinite(rect.width) && Number.isFinite(rect.height) && rect.width > 0 && rect.height > 0) {
|
|
229
|
-
return {
|
|
230
|
-
left: rect.left,
|
|
231
|
-
top: rect.top,
|
|
232
|
-
right: rect.right,
|
|
233
|
-
bottom: rect.bottom
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const obj = nodeEntry.glObject || nodeEntry.cssObject;
|
|
240
|
-
if (!obj || !_module.camera || !_module.container) return null;
|
|
241
|
-
|
|
242
|
-
const width = Math.max(1, Number(obj.userData?.worldWidth || nodeEntry.model?.width || 0) || 1);
|
|
243
|
-
const height = Math.max(1, Number(obj.userData?.worldHeight || nodeEntry.model?.height || 0) || 1);
|
|
244
|
-
const topLeft = worldToScreen(obj.position.x, obj.position.y, _module.camera, _module.container);
|
|
245
|
-
const bottomRight = worldToScreen(obj.position.x + width, obj.position.y - height, _module.camera, _module.container);
|
|
246
|
-
const left = Math.min(topLeft.x, bottomRight.x);
|
|
247
|
-
const top = Math.min(topLeft.y, bottomRight.y);
|
|
248
|
-
const right = Math.max(topLeft.x, bottomRight.x);
|
|
249
|
-
const bottom = Math.max(topLeft.y, bottomRight.y);
|
|
250
|
-
|
|
251
|
-
if (![left, top, right, bottom].every(Number.isFinite)) return null;
|
|
252
|
-
|
|
253
|
-
return { left, top, right, bottom };
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function applyOverlayRect(left, top, right, bottom) {
|
|
257
|
-
multiSelectOverlay.style.left = `${left}px`;
|
|
258
|
-
multiSelectOverlay.style.top = `${top}px`;
|
|
259
|
-
multiSelectOverlay.style.width = `${Math.max(0, right - left)}px`;
|
|
260
|
-
multiSelectOverlay.style.height = `${Math.max(0, bottom - top)}px`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function updateOverlayPosition(selectedNodeIds = null, forceWorldProjection = false) {
|
|
264
|
-
if (!currentBounds || !_module || !_module.camera || !_module.container) return;
|
|
265
|
-
|
|
266
|
-
if (!forceWorldProjection && Array.isArray(selectedNodeIds) && selectedNodeIds.length > 0) {
|
|
267
|
-
let minLeft = Infinity;
|
|
268
|
-
let minTop = Infinity;
|
|
269
|
-
let maxRight = -Infinity;
|
|
270
|
-
let maxBottom = -Infinity;
|
|
271
|
-
|
|
272
|
-
selectedNodeIds.forEach(nodeId => {
|
|
273
|
-
const rect = getNodeScreenRect(_module.nodeObjectsById.get(nodeId));
|
|
274
|
-
if (!rect) return;
|
|
275
|
-
|
|
276
|
-
minLeft = Math.min(minLeft, rect.left);
|
|
277
|
-
minTop = Math.min(minTop, rect.top);
|
|
278
|
-
maxRight = Math.max(maxRight, rect.right);
|
|
279
|
-
maxBottom = Math.max(maxBottom, rect.bottom);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
if (minLeft !== Infinity) {
|
|
283
|
-
applyOverlayRect(
|
|
284
|
-
minLeft - MULTI_SELECT_SCREEN_PADDING_PX,
|
|
285
|
-
minTop - MULTI_SELECT_SCREEN_PADDING_PX,
|
|
286
|
-
maxRight + MULTI_SELECT_SCREEN_PADDING_PX,
|
|
287
|
-
maxBottom + MULTI_SELECT_SCREEN_PADDING_PX
|
|
288
|
-
);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const camera = _module.camera;
|
|
294
|
-
const container = _module.container;
|
|
215
|
+
// Convert node bounds to screen coordinates
|
|
216
|
+
updateOverlayPosition(selectedImageNodeIds);
|
|
217
|
+
multiSelectOverlay.style.display = 'block';
|
|
218
|
+
multiSelectOverlay.style.visibility = 'visible';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getNodeScreenRect(nodeEntry) {
|
|
222
|
+
if (!nodeEntry || !_module) return null;
|
|
223
|
+
|
|
224
|
+
if (nodeEntry.currentType === 'CSS') {
|
|
225
|
+
const element = nodeEntry.cssObject?.element;
|
|
226
|
+
if (element?.isConnected) {
|
|
227
|
+
const rect = element.getBoundingClientRect();
|
|
228
|
+
if (Number.isFinite(rect.width) && Number.isFinite(rect.height) && rect.width > 0 && rect.height > 0) {
|
|
229
|
+
return {
|
|
230
|
+
left: rect.left,
|
|
231
|
+
top: rect.top,
|
|
232
|
+
right: rect.right,
|
|
233
|
+
bottom: rect.bottom
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const obj = nodeEntry.glObject || nodeEntry.cssObject;
|
|
240
|
+
if (!obj || !_module.camera || !_module.container) return null;
|
|
241
|
+
|
|
242
|
+
const width = Math.max(1, Number(obj.userData?.worldWidth || nodeEntry.model?.width || 0) || 1);
|
|
243
|
+
const height = Math.max(1, Number(obj.userData?.worldHeight || nodeEntry.model?.height || 0) || 1);
|
|
244
|
+
const topLeft = worldToScreen(obj.position.x, obj.position.y, _module.camera, _module.container);
|
|
245
|
+
const bottomRight = worldToScreen(obj.position.x + width, obj.position.y - height, _module.camera, _module.container);
|
|
246
|
+
const left = Math.min(topLeft.x, bottomRight.x);
|
|
247
|
+
const top = Math.min(topLeft.y, bottomRight.y);
|
|
248
|
+
const right = Math.max(topLeft.x, bottomRight.x);
|
|
249
|
+
const bottom = Math.max(topLeft.y, bottomRight.y);
|
|
250
|
+
|
|
251
|
+
if (![left, top, right, bottom].every(Number.isFinite)) return null;
|
|
252
|
+
|
|
253
|
+
return { left, top, right, bottom };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function applyOverlayRect(left, top, right, bottom) {
|
|
257
|
+
multiSelectOverlay.style.left = `${left}px`;
|
|
258
|
+
multiSelectOverlay.style.top = `${top}px`;
|
|
259
|
+
multiSelectOverlay.style.width = `${Math.max(0, right - left)}px`;
|
|
260
|
+
multiSelectOverlay.style.height = `${Math.max(0, bottom - top)}px`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function updateOverlayPosition(selectedNodeIds = null, forceWorldProjection = false) {
|
|
264
|
+
if (!currentBounds || !_module || !_module.camera || !_module.container) return;
|
|
265
|
+
|
|
266
|
+
if (!forceWorldProjection && Array.isArray(selectedNodeIds) && selectedNodeIds.length > 0) {
|
|
267
|
+
let minLeft = Infinity;
|
|
268
|
+
let minTop = Infinity;
|
|
269
|
+
let maxRight = -Infinity;
|
|
270
|
+
let maxBottom = -Infinity;
|
|
271
|
+
|
|
272
|
+
selectedNodeIds.forEach(nodeId => {
|
|
273
|
+
const rect = getNodeScreenRect(_module.nodeObjectsById.get(nodeId));
|
|
274
|
+
if (!rect) return;
|
|
275
|
+
|
|
276
|
+
minLeft = Math.min(minLeft, rect.left);
|
|
277
|
+
minTop = Math.min(minTop, rect.top);
|
|
278
|
+
maxRight = Math.max(maxRight, rect.right);
|
|
279
|
+
maxBottom = Math.max(maxBottom, rect.bottom);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (minLeft !== Infinity) {
|
|
283
|
+
applyOverlayRect(
|
|
284
|
+
minLeft - MULTI_SELECT_SCREEN_PADDING_PX,
|
|
285
|
+
minTop - MULTI_SELECT_SCREEN_PADDING_PX,
|
|
286
|
+
maxRight + MULTI_SELECT_SCREEN_PADDING_PX,
|
|
287
|
+
maxBottom + MULTI_SELECT_SCREEN_PADDING_PX
|
|
288
|
+
);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const camera = _module.camera;
|
|
294
|
+
const container = _module.container;
|
|
295
295
|
|
|
296
296
|
// Convert world corners to screen coordinates
|
|
297
297
|
const topLeft = worldToScreen(currentBounds.minX, currentBounds.maxY, camera, container);
|
|
@@ -302,8 +302,8 @@
|
|
|
302
302
|
const width = Math.abs(bottomRight.x - topLeft.x);
|
|
303
303
|
const height = Math.abs(bottomRight.y - topLeft.y);
|
|
304
304
|
|
|
305
|
-
applyOverlayRect(left, top, left + width, top + height);
|
|
306
|
-
}
|
|
305
|
+
applyOverlayRect(left, top, left + width, top + height);
|
|
306
|
+
}
|
|
307
307
|
|
|
308
308
|
function worldToScreen(worldX, worldY, camera, container) {
|
|
309
309
|
const THREE = globalThis.THREE;
|
|
@@ -319,108 +319,108 @@
|
|
|
319
319
|
};
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
function hideSingleImageOverlay() {
|
|
323
|
-
if (singleImageOverlay) {
|
|
324
|
-
singleImageOverlay.style.display = 'none';
|
|
325
|
-
singleImageOverlay.style.visibility = 'hidden';
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function getActiveSelectedNodeIds() {
|
|
330
|
-
if (!_module) return [];
|
|
331
|
-
if (_module.multiSelectedNodeIds?.size > 0) {
|
|
332
|
-
return Array.from(_module.multiSelectedNodeIds);
|
|
333
|
-
}
|
|
334
|
-
return _module.selectedNodeIdJs ? [_module.selectedNodeIdJs] : [];
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function getSingleSelectedImageNodeId() {
|
|
338
|
-
const selectedIds = getActiveSelectedNodeIds();
|
|
339
|
-
if (selectedIds.length !== 1) return null;
|
|
340
|
-
|
|
341
|
-
const nodeId = selectedIds[0];
|
|
342
|
-
const nodeEntry = _module?.nodeObjectsById?.get(nodeId);
|
|
343
|
-
return nodeEntry?.model?.contentType === 'image' ? nodeId : null;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function updateSingleImageOverlay(forceWorldProjection = false) {
|
|
347
|
-
if (!_module || !singleImageOverlay || !_module.camera || !_module.container) return;
|
|
348
|
-
|
|
349
|
-
const nodeId = getSingleSelectedImageNodeId();
|
|
350
|
-
if (!nodeId) {
|
|
351
|
-
hideSingleImageOverlay();
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const nodeEntry = _module.nodeObjectsById.get(nodeId);
|
|
356
|
-
const obj = nodeEntry?.glObject || nodeEntry?.cssObject;
|
|
357
|
-
const model = nodeEntry?.model;
|
|
358
|
-
if (!obj || !model) {
|
|
359
|
-
hideSingleImageOverlay();
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Camera-only motion must stay on the projection fast path. Reading the
|
|
364
|
-
// CSS node rect here can force layout and may still reflect the previous
|
|
365
|
-
// CSS3D camera transform when this overlay is updated in the same frame.
|
|
366
|
-
const screenRect = forceWorldProjection ? null : getNodeScreenRect(nodeEntry);
|
|
367
|
-
if (screenRect) {
|
|
368
|
-
const screenWidth = Math.max(0, screenRect.right - screenRect.left);
|
|
369
|
-
const screenHeight = Math.max(0, screenRect.bottom - screenRect.top);
|
|
370
|
-
if (screenWidth > 0 && screenHeight > 0) {
|
|
371
|
-
singleImageOverlay.style.left = `${screenRect.left}px`;
|
|
372
|
-
singleImageOverlay.style.top = `${screenRect.top}px`;
|
|
373
|
-
singleImageOverlay.style.width = `${screenWidth}px`;
|
|
374
|
-
singleImageOverlay.style.height = `${screenHeight}px`;
|
|
375
|
-
singleImageOverlay.style.display = 'block';
|
|
376
|
-
singleImageOverlay.style.visibility = 'visible';
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const x = obj.position.x;
|
|
382
|
-
const y = obj.position.y;
|
|
383
|
-
const width = Math.max(1, Number(model.width || obj.userData?.worldWidth || 0) || 1);
|
|
384
|
-
const height = Math.max(1, Number(model.height || obj.userData?.worldHeight || 0) || 1);
|
|
385
|
-
|
|
386
|
-
const topLeft = worldToScreen(x, y, _module.camera, _module.container);
|
|
387
|
-
const bottomRight = worldToScreen(x + width, y - height, _module.camera, _module.container);
|
|
388
|
-
|
|
389
|
-
const left = Math.min(topLeft.x, bottomRight.x);
|
|
390
|
-
const top = Math.min(topLeft.y, bottomRight.y);
|
|
391
|
-
const screenWidth = Math.abs(bottomRight.x - topLeft.x);
|
|
392
|
-
const screenHeight = Math.abs(bottomRight.y - topLeft.y);
|
|
393
|
-
|
|
394
|
-
if (![left, top, screenWidth, screenHeight].every(Number.isFinite) || screenWidth <= 0 || screenHeight <= 0) {
|
|
395
|
-
hideSingleImageOverlay();
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
singleImageOverlay.style.left = `${left}px`;
|
|
400
|
-
singleImageOverlay.style.top = `${top}px`;
|
|
401
|
-
singleImageOverlay.style.width = `${screenWidth}px`;
|
|
402
|
-
singleImageOverlay.style.height = `${screenHeight}px`;
|
|
403
|
-
singleImageOverlay.style.display = 'block';
|
|
404
|
-
singleImageOverlay.style.visibility = 'visible';
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function hide() {
|
|
408
|
-
if (multiSelectOverlay) {
|
|
409
|
-
multiSelectOverlay.style.display = 'none';
|
|
410
|
-
multiSelectOverlay.style.visibility = 'hidden';
|
|
411
|
-
}
|
|
412
|
-
hideSingleImageOverlay();
|
|
413
|
-
currentBounds = null;
|
|
414
|
-
initialNodeSizes.clear();
|
|
415
|
-
}
|
|
322
|
+
function hideSingleImageOverlay() {
|
|
323
|
+
if (singleImageOverlay) {
|
|
324
|
+
singleImageOverlay.style.display = 'none';
|
|
325
|
+
singleImageOverlay.style.visibility = 'hidden';
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function getActiveSelectedNodeIds() {
|
|
330
|
+
if (!_module) return [];
|
|
331
|
+
if (_module.multiSelectedNodeIds?.size > 0) {
|
|
332
|
+
return Array.from(_module.multiSelectedNodeIds);
|
|
333
|
+
}
|
|
334
|
+
return _module.selectedNodeIdJs ? [_module.selectedNodeIdJs] : [];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getSingleSelectedImageNodeId() {
|
|
338
|
+
const selectedIds = getActiveSelectedNodeIds();
|
|
339
|
+
if (selectedIds.length !== 1) return null;
|
|
340
|
+
|
|
341
|
+
const nodeId = selectedIds[0];
|
|
342
|
+
const nodeEntry = _module?.nodeObjectsById?.get(nodeId);
|
|
343
|
+
return nodeEntry?.model?.contentType === 'image' ? nodeId : null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function updateSingleImageOverlay(forceWorldProjection = false) {
|
|
347
|
+
if (!_module || !singleImageOverlay || !_module.camera || !_module.container) return;
|
|
348
|
+
|
|
349
|
+
const nodeId = getSingleSelectedImageNodeId();
|
|
350
|
+
if (!nodeId) {
|
|
351
|
+
hideSingleImageOverlay();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const nodeEntry = _module.nodeObjectsById.get(nodeId);
|
|
356
|
+
const obj = nodeEntry?.glObject || nodeEntry?.cssObject;
|
|
357
|
+
const model = nodeEntry?.model;
|
|
358
|
+
if (!obj || !model) {
|
|
359
|
+
hideSingleImageOverlay();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Camera-only motion must stay on the projection fast path. Reading the
|
|
364
|
+
// CSS node rect here can force layout and may still reflect the previous
|
|
365
|
+
// CSS3D camera transform when this overlay is updated in the same frame.
|
|
366
|
+
const screenRect = forceWorldProjection ? null : getNodeScreenRect(nodeEntry);
|
|
367
|
+
if (screenRect) {
|
|
368
|
+
const screenWidth = Math.max(0, screenRect.right - screenRect.left);
|
|
369
|
+
const screenHeight = Math.max(0, screenRect.bottom - screenRect.top);
|
|
370
|
+
if (screenWidth > 0 && screenHeight > 0) {
|
|
371
|
+
singleImageOverlay.style.left = `${screenRect.left}px`;
|
|
372
|
+
singleImageOverlay.style.top = `${screenRect.top}px`;
|
|
373
|
+
singleImageOverlay.style.width = `${screenWidth}px`;
|
|
374
|
+
singleImageOverlay.style.height = `${screenHeight}px`;
|
|
375
|
+
singleImageOverlay.style.display = 'block';
|
|
376
|
+
singleImageOverlay.style.visibility = 'visible';
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const x = obj.position.x;
|
|
382
|
+
const y = obj.position.y;
|
|
383
|
+
const width = Math.max(1, Number(model.width || obj.userData?.worldWidth || 0) || 1);
|
|
384
|
+
const height = Math.max(1, Number(model.height || obj.userData?.worldHeight || 0) || 1);
|
|
385
|
+
|
|
386
|
+
const topLeft = worldToScreen(x, y, _module.camera, _module.container);
|
|
387
|
+
const bottomRight = worldToScreen(x + width, y - height, _module.camera, _module.container);
|
|
388
|
+
|
|
389
|
+
const left = Math.min(topLeft.x, bottomRight.x);
|
|
390
|
+
const top = Math.min(topLeft.y, bottomRight.y);
|
|
391
|
+
const screenWidth = Math.abs(bottomRight.x - topLeft.x);
|
|
392
|
+
const screenHeight = Math.abs(bottomRight.y - topLeft.y);
|
|
393
|
+
|
|
394
|
+
if (![left, top, screenWidth, screenHeight].every(Number.isFinite) || screenWidth <= 0 || screenHeight <= 0) {
|
|
395
|
+
hideSingleImageOverlay();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
singleImageOverlay.style.left = `${left}px`;
|
|
400
|
+
singleImageOverlay.style.top = `${top}px`;
|
|
401
|
+
singleImageOverlay.style.width = `${screenWidth}px`;
|
|
402
|
+
singleImageOverlay.style.height = `${screenHeight}px`;
|
|
403
|
+
singleImageOverlay.style.display = 'block';
|
|
404
|
+
singleImageOverlay.style.visibility = 'visible';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function hide() {
|
|
408
|
+
if (multiSelectOverlay) {
|
|
409
|
+
multiSelectOverlay.style.display = 'none';
|
|
410
|
+
multiSelectOverlay.style.visibility = 'hidden';
|
|
411
|
+
}
|
|
412
|
+
hideSingleImageOverlay();
|
|
413
|
+
currentBounds = null;
|
|
414
|
+
initialNodeSizes.clear();
|
|
415
|
+
}
|
|
416
416
|
|
|
417
417
|
function onResizeStart(e) {
|
|
418
418
|
e.stopPropagation();
|
|
419
419
|
e.preventDefault();
|
|
420
420
|
|
|
421
|
-
isResizing = true;
|
|
422
|
-
setBrowserSelectionLock(true, 'resize');
|
|
423
|
-
resizeHandle = e.target.dataset.corner;
|
|
421
|
+
isResizing = true;
|
|
422
|
+
setBrowserSelectionLock(true, 'resize');
|
|
423
|
+
resizeHandle = e.target.dataset.corner;
|
|
424
424
|
resizeStartBounds = { ...currentBounds };
|
|
425
425
|
resizeStartMouse = { x: e.clientX, y: e.clientY };
|
|
426
426
|
|
|
@@ -464,8 +464,8 @@
|
|
|
464
464
|
return;
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
-
currentBounds = newBounds;
|
|
468
|
-
updateOverlayPosition(null, true);
|
|
467
|
+
currentBounds = newBounds;
|
|
468
|
+
updateOverlayPosition(null, true);
|
|
469
469
|
|
|
470
470
|
// Scale and reposition selected nodes proportionally
|
|
471
471
|
scaleSelectedNodes();
|
|
@@ -474,9 +474,9 @@
|
|
|
474
474
|
function onResizeEnd(e) {
|
|
475
475
|
if (!isResizing) return;
|
|
476
476
|
|
|
477
|
-
isResizing = false;
|
|
478
|
-
setBrowserSelectionLock(false, 'resize');
|
|
479
|
-
resizeHandle = null;
|
|
477
|
+
isResizing = false;
|
|
478
|
+
setBrowserSelectionLock(false, 'resize');
|
|
479
|
+
resizeHandle = null;
|
|
480
480
|
resizeStartBounds = null;
|
|
481
481
|
resizeStartMouse = null;
|
|
482
482
|
multiSelectOverlay.style.cursor = '';
|
|
@@ -584,9 +584,9 @@
|
|
|
584
584
|
case 'stack':
|
|
585
585
|
await mergeSelectedImagesIntoStack();
|
|
586
586
|
break;
|
|
587
|
-
case 'expand':
|
|
588
|
-
await fitTextNodesToContentAndReflowY(selectedIds);
|
|
589
|
-
break;
|
|
587
|
+
case 'expand':
|
|
588
|
+
await fitTextNodesToContentAndReflowY(selectedIds);
|
|
589
|
+
break;
|
|
590
590
|
case 'delete':
|
|
591
591
|
deleteSelectedNodes(selectedIds);
|
|
592
592
|
break;
|
|
@@ -594,9 +594,9 @@
|
|
|
594
594
|
}
|
|
595
595
|
|
|
596
596
|
// ▼▼▼ [New] Supported node types for multi-select ▼▼▼
|
|
597
|
-
const SUPPORTED_TYPES = ['image', 'text', 'markdown', 'note', 'code'];
|
|
598
|
-
const TEXT_TYPES = ['text', 'markdown', 'note', 'code'];
|
|
599
|
-
const TEXT_FIT_MAX_HEIGHT = 12000;
|
|
597
|
+
const SUPPORTED_TYPES = ['image', 'text', 'markdown', 'note', 'code'];
|
|
598
|
+
const TEXT_TYPES = ['text', 'markdown', 'note', 'code'];
|
|
599
|
+
const TEXT_FIT_MAX_HEIGHT = 12000;
|
|
600
600
|
// ▲▲▲ [New] ▲▲▲
|
|
601
601
|
|
|
602
602
|
function getSelectedNodeIds() {
|
|
@@ -766,12 +766,12 @@
|
|
|
766
766
|
.catch(err => console.warn('[MultiSelect] Failed to refresh node after tidy:', item.nodeId, err));
|
|
767
767
|
});
|
|
768
768
|
|
|
769
|
-
if (_module.markLODPositionsDirty) {
|
|
770
|
-
_module.markLODPositionsDirty(selectedIds);
|
|
771
|
-
}
|
|
772
|
-
if (resized.length > 0 && _module.markLODDirty) {
|
|
773
|
-
_module.markLODDirty('update-node', resized.map(item => item.nodeId));
|
|
774
|
-
}
|
|
769
|
+
if (_module.markLODPositionsDirty) {
|
|
770
|
+
_module.markLODPositionsDirty(selectedIds);
|
|
771
|
+
}
|
|
772
|
+
if (resized.length > 0 && _module.markLODDirty) {
|
|
773
|
+
_module.markLODDirty('update-node', resized.map(item => item.nodeId));
|
|
774
|
+
}
|
|
775
775
|
if (_module.updateVisibility && _module.camera) {
|
|
776
776
|
_module.updateVisibility(_module.camera);
|
|
777
777
|
}
|
|
@@ -824,17 +824,17 @@
|
|
|
824
824
|
if (!result) return;
|
|
825
825
|
|
|
826
826
|
const removedIds = result.removedNodeIds || result.RemovedNodeIds || [];
|
|
827
|
-
removedIds.forEach(nodeId => {
|
|
828
|
-
if (!nodeId) return;
|
|
829
|
-
if (_module.multiSelectedNodeIds?.delete) {
|
|
830
|
-
_module.multiSelectedNodeIds.delete(nodeId);
|
|
831
|
-
}
|
|
832
|
-
});
|
|
833
|
-
if (window.MindMapNodes?.removeNodes) {
|
|
834
|
-
window.MindMapNodes.removeNodes(_module, removedIds);
|
|
835
|
-
} else {
|
|
836
|
-
removedIds.forEach(nodeId => window.MindMapNodes?.removeNode?.(_module, nodeId));
|
|
837
|
-
}
|
|
827
|
+
removedIds.forEach(nodeId => {
|
|
828
|
+
if (!nodeId) return;
|
|
829
|
+
if (_module.multiSelectedNodeIds?.delete) {
|
|
830
|
+
_module.multiSelectedNodeIds.delete(nodeId);
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
if (window.MindMapNodes?.removeNodes) {
|
|
834
|
+
window.MindMapNodes.removeNodes(_module, removedIds);
|
|
835
|
+
} else {
|
|
836
|
+
removedIds.forEach(nodeId => window.MindMapNodes?.removeNode?.(_module, nodeId));
|
|
837
|
+
}
|
|
838
838
|
|
|
839
839
|
const primaryNodeId = result.primaryNodeId || result.PrimaryNodeId || null;
|
|
840
840
|
if (primaryNodeId) {
|
|
@@ -858,33 +858,33 @@
|
|
|
858
858
|
|
|
859
859
|
function copySelectedNodes(selectedIds) {
|
|
860
860
|
// Copy nodes to clipboard (as internal format)
|
|
861
|
-
const copiedNodes = [];
|
|
862
|
-
selectedIds.forEach(nodeId => {
|
|
863
|
-
const nodeEntry = _module.nodeObjectsById.get(nodeId);
|
|
864
|
-
if (nodeEntry?.model) {
|
|
865
|
-
const model = nodeEntry.model;
|
|
866
|
-
copiedNodes.push({
|
|
867
|
-
contentType: model.contentType,
|
|
868
|
-
prompt: model.prompt,
|
|
869
|
-
response: model.response,
|
|
870
|
-
width: model.width,
|
|
871
|
-
height: model.height,
|
|
872
|
-
positionX: model.positionX,
|
|
873
|
-
positionY: model.positionY,
|
|
874
|
-
positionZ: model.positionZ,
|
|
875
|
-
isAiEnabled: model.isAiEnabled !== false,
|
|
876
|
-
isWordWrap: !!model.isWordWrap,
|
|
877
|
-
isChunkedText: model.isChunkedText,
|
|
878
|
-
totalLineCount: model.totalLineCount,
|
|
879
|
-
sourceFilePath: model.sourceFilePath,
|
|
880
|
-
scrollOffset: model.scrollOffset,
|
|
881
|
-
maxScroll: model.maxScroll,
|
|
882
|
-
isExpanded: !!model.isExpanded,
|
|
883
|
-
originalHeight: model.originalHeight,
|
|
884
|
-
metadata: model.metadata
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
});
|
|
861
|
+
const copiedNodes = [];
|
|
862
|
+
selectedIds.forEach(nodeId => {
|
|
863
|
+
const nodeEntry = _module.nodeObjectsById.get(nodeId);
|
|
864
|
+
if (nodeEntry?.model) {
|
|
865
|
+
const model = nodeEntry.model;
|
|
866
|
+
copiedNodes.push({
|
|
867
|
+
contentType: model.contentType,
|
|
868
|
+
prompt: model.prompt,
|
|
869
|
+
response: model.response,
|
|
870
|
+
width: model.width,
|
|
871
|
+
height: model.height,
|
|
872
|
+
positionX: model.positionX,
|
|
873
|
+
positionY: model.positionY,
|
|
874
|
+
positionZ: model.positionZ,
|
|
875
|
+
isAiEnabled: model.isAiEnabled !== false,
|
|
876
|
+
isWordWrap: !!model.isWordWrap,
|
|
877
|
+
isChunkedText: model.isChunkedText,
|
|
878
|
+
totalLineCount: model.totalLineCount,
|
|
879
|
+
sourceFilePath: model.sourceFilePath,
|
|
880
|
+
scrollOffset: model.scrollOffset,
|
|
881
|
+
maxScroll: model.maxScroll,
|
|
882
|
+
isExpanded: !!model.isExpanded,
|
|
883
|
+
originalHeight: model.originalHeight,
|
|
884
|
+
metadata: model.metadata
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
888
|
|
|
889
889
|
_module.copiedNodes = copiedNodes;
|
|
890
890
|
_module.copiedNodesTimestamp = Date.now();
|
|
@@ -902,73 +902,73 @@
|
|
|
902
902
|
console.log(`[MultiSelect] Copied ${copiedNodes.length} nodes`);
|
|
903
903
|
}
|
|
904
904
|
|
|
905
|
-
function readCssPixelValue(value) {
|
|
906
|
-
const parsed = Number.parseFloat(String(value || '0'));
|
|
907
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
function getVerticalBoxExtras(element) {
|
|
911
|
-
if (!element || typeof window === 'undefined' || !window.getComputedStyle) return 0;
|
|
912
|
-
|
|
913
|
-
const computed = window.getComputedStyle(element);
|
|
914
|
-
return readCssPixelValue(computed.paddingTop)
|
|
915
|
-
+ readCssPixelValue(computed.paddingBottom)
|
|
916
|
-
+ readCssPixelValue(computed.borderTopWidth)
|
|
917
|
-
+ readCssPixelValue(computed.borderBottomWidth);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
function getVerticalMargins(element) {
|
|
921
|
-
if (!element || typeof window === 'undefined' || !window.getComputedStyle) return 0;
|
|
922
|
-
|
|
923
|
-
const computed = window.getComputedStyle(element);
|
|
924
|
-
return readCssPixelValue(computed.marginTop) + readCssPixelValue(computed.marginBottom);
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function forceNaturalBlockHeight(element, preserveScrollMetrics = false) {
|
|
928
|
-
if (!element?.style) return;
|
|
929
|
-
|
|
930
|
-
element.style.setProperty('height', 'auto', 'important');
|
|
931
|
-
element.style.setProperty('min-height', '0', 'important');
|
|
932
|
-
element.style.setProperty('max-height', 'none', 'important');
|
|
933
|
-
element.style.setProperty('flex', '0 0 auto', 'important');
|
|
934
|
-
|
|
935
|
-
if (preserveScrollMetrics) {
|
|
936
|
-
// Keep scrollbar-gutter and right padding active so wrapping matches the real CSS3D node.
|
|
937
|
-
element.style.setProperty('overflow-x', 'hidden', 'important');
|
|
938
|
-
element.style.setProperty('overflow-y', 'auto', 'important');
|
|
939
|
-
} else {
|
|
940
|
-
element.style.setProperty('overflow', 'visible', 'important');
|
|
941
|
-
element.style.setProperty('overflow-y', 'visible', 'important');
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
function measureOuterHeight(element, preferScrollHeight = false) {
|
|
946
|
-
if (!element) return 0;
|
|
947
|
-
|
|
948
|
-
let height = 0;
|
|
949
|
-
const push = (value) => {
|
|
950
|
-
const numeric = Number(value);
|
|
951
|
-
if (Number.isFinite(numeric) && numeric > height) {
|
|
952
|
-
height = numeric;
|
|
953
|
-
}
|
|
954
|
-
};
|
|
955
|
-
|
|
956
|
-
if (typeof element.getBoundingClientRect === 'function') {
|
|
957
|
-
push(element.getBoundingClientRect().height);
|
|
958
|
-
}
|
|
959
|
-
push(element.offsetHeight);
|
|
960
|
-
push(element.clientHeight);
|
|
961
|
-
if (preferScrollHeight) {
|
|
962
|
-
push(element.scrollHeight);
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
return Math.ceil(height + getVerticalMargins(element));
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
function measureExpandedHeightFromCssDom(nodeEntry, extraBuffer = 0) {
|
|
969
|
-
const wrapper = nodeEntry?.cssObject?.element;
|
|
970
|
-
const source = wrapper?.firstElementChild;
|
|
971
|
-
if (!source || typeof document === 'undefined') return null;
|
|
905
|
+
function readCssPixelValue(value) {
|
|
906
|
+
const parsed = Number.parseFloat(String(value || '0'));
|
|
907
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function getVerticalBoxExtras(element) {
|
|
911
|
+
if (!element || typeof window === 'undefined' || !window.getComputedStyle) return 0;
|
|
912
|
+
|
|
913
|
+
const computed = window.getComputedStyle(element);
|
|
914
|
+
return readCssPixelValue(computed.paddingTop)
|
|
915
|
+
+ readCssPixelValue(computed.paddingBottom)
|
|
916
|
+
+ readCssPixelValue(computed.borderTopWidth)
|
|
917
|
+
+ readCssPixelValue(computed.borderBottomWidth);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function getVerticalMargins(element) {
|
|
921
|
+
if (!element || typeof window === 'undefined' || !window.getComputedStyle) return 0;
|
|
922
|
+
|
|
923
|
+
const computed = window.getComputedStyle(element);
|
|
924
|
+
return readCssPixelValue(computed.marginTop) + readCssPixelValue(computed.marginBottom);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function forceNaturalBlockHeight(element, preserveScrollMetrics = false) {
|
|
928
|
+
if (!element?.style) return;
|
|
929
|
+
|
|
930
|
+
element.style.setProperty('height', 'auto', 'important');
|
|
931
|
+
element.style.setProperty('min-height', '0', 'important');
|
|
932
|
+
element.style.setProperty('max-height', 'none', 'important');
|
|
933
|
+
element.style.setProperty('flex', '0 0 auto', 'important');
|
|
934
|
+
|
|
935
|
+
if (preserveScrollMetrics) {
|
|
936
|
+
// Keep scrollbar-gutter and right padding active so wrapping matches the real CSS3D node.
|
|
937
|
+
element.style.setProperty('overflow-x', 'hidden', 'important');
|
|
938
|
+
element.style.setProperty('overflow-y', 'auto', 'important');
|
|
939
|
+
} else {
|
|
940
|
+
element.style.setProperty('overflow', 'visible', 'important');
|
|
941
|
+
element.style.setProperty('overflow-y', 'visible', 'important');
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function measureOuterHeight(element, preferScrollHeight = false) {
|
|
946
|
+
if (!element) return 0;
|
|
947
|
+
|
|
948
|
+
let height = 0;
|
|
949
|
+
const push = (value) => {
|
|
950
|
+
const numeric = Number(value);
|
|
951
|
+
if (Number.isFinite(numeric) && numeric > height) {
|
|
952
|
+
height = numeric;
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
if (typeof element.getBoundingClientRect === 'function') {
|
|
957
|
+
push(element.getBoundingClientRect().height);
|
|
958
|
+
}
|
|
959
|
+
push(element.offsetHeight);
|
|
960
|
+
push(element.clientHeight);
|
|
961
|
+
if (preferScrollHeight) {
|
|
962
|
+
push(element.scrollHeight);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
return Math.ceil(height + getVerticalMargins(element));
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function measureExpandedHeightFromCssDom(nodeEntry, extraBuffer = 0) {
|
|
969
|
+
const wrapper = nodeEntry?.cssObject?.element;
|
|
970
|
+
const source = wrapper?.firstElementChild;
|
|
971
|
+
if (!source || typeof document === 'undefined') return null;
|
|
972
972
|
|
|
973
973
|
let probe = null;
|
|
974
974
|
try {
|
|
@@ -997,56 +997,56 @@
|
|
|
997
997
|
boxSizing: 'border-box'
|
|
998
998
|
});
|
|
999
999
|
|
|
1000
|
-
const contentWrapper = probe.querySelector('.node-content-wrapper');
|
|
1001
|
-
if (contentWrapper) {
|
|
1002
|
-
forceNaturalBlockHeight(contentWrapper);
|
|
1003
|
-
contentWrapper.style.display = 'block';
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
const contentSelectors = [
|
|
1007
|
-
'[id^="node-response-"]',
|
|
1008
|
-
'.node-response',
|
|
1009
|
-
'.note-content',
|
|
1010
|
-
'.markdown-body',
|
|
1011
|
-
'.code-body',
|
|
1012
|
-
'.code-body-content',
|
|
1013
|
-
'.note-textarea',
|
|
1014
|
-
'textarea'
|
|
1015
|
-
].join(', ');
|
|
1016
|
-
const contentElements = Array.from(probe.querySelectorAll(contentSelectors));
|
|
1017
|
-
contentElements.forEach(element => {
|
|
1018
|
-
if (element.style) {
|
|
1019
|
-
element.style.display = element.tagName === 'TEXTAREA' ? 'block' : (element.style.display || 'block');
|
|
1020
|
-
}
|
|
1021
|
-
forceNaturalBlockHeight(element, true);
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
document.body.appendChild(probe);
|
|
1025
|
-
|
|
1026
|
-
const measuredWhole = Math.max(
|
|
1027
|
-
measureOuterHeight(probe, true),
|
|
1028
|
-
Math.ceil(probe.scrollHeight || 0)
|
|
1029
|
-
);
|
|
1030
|
-
|
|
1031
|
-
let measuredByChildren = 0;
|
|
1032
|
-
if (contentWrapper) {
|
|
1033
|
-
measuredByChildren += getVerticalBoxExtras(probe);
|
|
1034
|
-
measuredByChildren += getVerticalBoxExtras(contentWrapper);
|
|
1035
|
-
|
|
1036
|
-
Array.from(contentWrapper.children || []).forEach(child => {
|
|
1037
|
-
if (child instanceof HTMLElement && window.getComputedStyle(child).display === 'none') {
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
const isContent = child.matches?.(contentSelectors);
|
|
1042
|
-
measuredByChildren += measureOuterHeight(child, !!isContent);
|
|
1043
|
-
});
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
const measured = Math.ceil(Math.max(measuredWhole, measuredByChildren));
|
|
1047
|
-
if (!Number.isFinite(measured) || measured <= 0) return null;
|
|
1048
|
-
|
|
1049
|
-
return measured + Math.max(0, extraBuffer);
|
|
1000
|
+
const contentWrapper = probe.querySelector('.node-content-wrapper');
|
|
1001
|
+
if (contentWrapper) {
|
|
1002
|
+
forceNaturalBlockHeight(contentWrapper);
|
|
1003
|
+
contentWrapper.style.display = 'block';
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const contentSelectors = [
|
|
1007
|
+
'[id^="node-response-"]',
|
|
1008
|
+
'.node-response',
|
|
1009
|
+
'.note-content',
|
|
1010
|
+
'.markdown-body',
|
|
1011
|
+
'.code-body',
|
|
1012
|
+
'.code-body-content',
|
|
1013
|
+
'.note-textarea',
|
|
1014
|
+
'textarea'
|
|
1015
|
+
].join(', ');
|
|
1016
|
+
const contentElements = Array.from(probe.querySelectorAll(contentSelectors));
|
|
1017
|
+
contentElements.forEach(element => {
|
|
1018
|
+
if (element.style) {
|
|
1019
|
+
element.style.display = element.tagName === 'TEXTAREA' ? 'block' : (element.style.display || 'block');
|
|
1020
|
+
}
|
|
1021
|
+
forceNaturalBlockHeight(element, true);
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
document.body.appendChild(probe);
|
|
1025
|
+
|
|
1026
|
+
const measuredWhole = Math.max(
|
|
1027
|
+
measureOuterHeight(probe, true),
|
|
1028
|
+
Math.ceil(probe.scrollHeight || 0)
|
|
1029
|
+
);
|
|
1030
|
+
|
|
1031
|
+
let measuredByChildren = 0;
|
|
1032
|
+
if (contentWrapper) {
|
|
1033
|
+
measuredByChildren += getVerticalBoxExtras(probe);
|
|
1034
|
+
measuredByChildren += getVerticalBoxExtras(contentWrapper);
|
|
1035
|
+
|
|
1036
|
+
Array.from(contentWrapper.children || []).forEach(child => {
|
|
1037
|
+
if (child instanceof HTMLElement && window.getComputedStyle(child).display === 'none') {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const isContent = child.matches?.(contentSelectors);
|
|
1042
|
+
measuredByChildren += measureOuterHeight(child, !!isContent);
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const measured = Math.ceil(Math.max(measuredWhole, measuredByChildren));
|
|
1047
|
+
if (!Number.isFinite(measured) || measured <= 0) return null;
|
|
1048
|
+
|
|
1049
|
+
return measured + Math.max(0, extraBuffer);
|
|
1050
1050
|
} catch {
|
|
1051
1051
|
return null;
|
|
1052
1052
|
} finally {
|
|
@@ -1056,334 +1056,334 @@
|
|
|
1056
1056
|
}
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
|
-
function getTextFitMinHeight(model) {
|
|
1060
|
-
const contentType = String(model?.contentType || '').toLowerCase();
|
|
1061
|
-
if (contentType === 'note') return 96;
|
|
1062
|
-
if (contentType === 'code') return 120;
|
|
1063
|
-
return 88;
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
async function measureTextFitHeight(nodeEntry) {
|
|
1067
|
-
const model = nodeEntry?.model;
|
|
1068
|
-
if (!model) return null;
|
|
1069
|
-
|
|
1070
|
-
const FIT_BUFFER = 18;
|
|
1071
|
-
const minHeight = getTextFitMinHeight(model);
|
|
1072
|
-
|
|
1073
|
-
const domHeight = measureExpandedHeightFromCssDom(nodeEntry, FIT_BUFFER);
|
|
1074
|
-
let factoryHeight = null;
|
|
1075
|
-
|
|
1076
|
-
if (window.MindMapTextureFactory?.measureHtmlHeightAsync) {
|
|
1077
|
-
const zoomLevel = 2;
|
|
1078
|
-
const measureModel = {
|
|
1079
|
-
...model,
|
|
1080
|
-
response: model.response ?? model.Response ?? model.content ?? ''
|
|
1081
|
-
};
|
|
1082
|
-
const { height: measuredHeight } = await window.MindMapTextureFactory.measureHtmlHeightAsync(
|
|
1083
|
-
measureModel,
|
|
1084
|
-
zoomLevel,
|
|
1085
|
-
(model.width || 400) * zoomLevel,
|
|
1086
|
-
null,
|
|
1087
|
-
TEXT_FIT_MAX_HEIGHT
|
|
1088
|
-
);
|
|
1089
|
-
factoryHeight = measuredHeight + FIT_BUFFER;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
const requiredHeight = Math.max(
|
|
1093
|
-
Number.isFinite(domHeight) ? domHeight : 0,
|
|
1094
|
-
Number.isFinite(factoryHeight) ? factoryHeight : 0
|
|
1095
|
-
);
|
|
1096
|
-
|
|
1097
|
-
if (!Number.isFinite(requiredHeight) || requiredHeight <= 0) {
|
|
1098
|
-
return null;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
return Math.min(TEXT_FIT_MAX_HEIGHT, Math.max(minHeight, Math.ceil(requiredHeight)));
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
function readRenderedTextOverflowAdjustment(item) {
|
|
1105
|
-
const entry = item?.entry;
|
|
1106
|
-
const root = entry?.cssObject?.element?.firstElementChild || entry?.cssObject?.element;
|
|
1107
|
-
if (!root) return 0;
|
|
1108
|
-
|
|
1109
|
-
const contentEl = root.querySelector?.('[id^="node-response-"]')
|
|
1110
|
-
|| root.querySelector?.('.node-response')
|
|
1111
|
-
|| root.querySelector?.('.note-textarea')
|
|
1112
|
-
|| root.querySelector?.('textarea');
|
|
1113
|
-
if (!contentEl) return 0;
|
|
1114
|
-
|
|
1115
|
-
const scrollHeight = Math.ceil(Number(contentEl.scrollHeight || 0));
|
|
1116
|
-
const clientHeight = Math.ceil(Number(contentEl.clientHeight || 0));
|
|
1117
|
-
const overflow = scrollHeight - clientHeight;
|
|
1118
|
-
if (!Number.isFinite(overflow) || overflow <= 1) return 0;
|
|
1119
|
-
|
|
1120
|
-
return overflow + 12;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
function refineTextFitItemsFromRenderedDom(items) {
|
|
1124
|
-
if (!Array.isArray(items) || items.length === 0) return 0;
|
|
1125
|
-
|
|
1126
|
-
let adjusted = 0;
|
|
1127
|
-
for (const item of items) {
|
|
1128
|
-
const correction = readRenderedTextOverflowAdjustment(item);
|
|
1129
|
-
if (correction <= 0) continue;
|
|
1130
|
-
|
|
1131
|
-
item.newHeight = Math.min(TEXT_FIT_MAX_HEIGHT, Math.ceil(item.newHeight + correction));
|
|
1132
|
-
adjusted++;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
return adjusted;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
function getNodeFrame(nodeId) {
|
|
1139
|
-
const entry = _module?.nodeObjectsById?.get?.(nodeId);
|
|
1140
|
-
const model = entry?.model;
|
|
1141
|
-
if (!entry || !model) return null;
|
|
1142
|
-
|
|
1143
|
-
const x = Number(model.positionX ?? model.x ?? entry.glObject?.position?.x ?? entry.cssObject?.position?.x ?? 0);
|
|
1144
|
-
const y = Number(model.positionY ?? model.y ?? entry.glObject?.position?.y ?? entry.cssObject?.position?.y ?? 0);
|
|
1145
|
-
const width = Math.max(1, Math.round(Number(model.width || 400)));
|
|
1146
|
-
const height = Math.max(1, Math.round(Number(model.height || 200)));
|
|
1147
|
-
|
|
1148
|
-
return {
|
|
1149
|
-
nodeId,
|
|
1150
|
-
entry,
|
|
1151
|
-
model,
|
|
1152
|
-
x,
|
|
1153
|
-
y,
|
|
1154
|
-
width,
|
|
1155
|
-
height,
|
|
1156
|
-
left: x,
|
|
1157
|
-
right: x + width,
|
|
1158
|
-
centerX: x + width / 2
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
function horizontalOverlapRatio(a, b) {
|
|
1163
|
-
const overlap = Math.min(a.right, b.right) - Math.max(a.left, b.left);
|
|
1164
|
-
if (overlap <= 0) return 0;
|
|
1165
|
-
return overlap / Math.max(1, Math.min(a.width, b.width));
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
function groupTextFramesByColumn(items) {
|
|
1169
|
-
const groups = [];
|
|
1170
|
-
const ordered = [...items].sort((a, b) => a.left - b.left || b.y - a.y);
|
|
1171
|
-
|
|
1172
|
-
for (const item of ordered) {
|
|
1173
|
-
let target = null;
|
|
1174
|
-
let bestScore = 0;
|
|
1175
|
-
|
|
1176
|
-
for (const group of groups) {
|
|
1177
|
-
const groupFrame = {
|
|
1178
|
-
left: group.left,
|
|
1179
|
-
right: group.right,
|
|
1180
|
-
width: Math.max(1, group.right - group.left)
|
|
1181
|
-
};
|
|
1182
|
-
const overlapScore = horizontalOverlapRatio(item, groupFrame);
|
|
1183
|
-
const centerDistance = Math.abs(item.centerX - group.centerX);
|
|
1184
|
-
const centerThreshold = Math.max(80, Math.min(item.width, groupFrame.width) * 0.35);
|
|
1185
|
-
const score = overlapScore > 0 ? overlapScore : (centerDistance <= centerThreshold ? 0.2 : 0);
|
|
1186
|
-
|
|
1187
|
-
if (score > bestScore) {
|
|
1188
|
-
bestScore = score;
|
|
1189
|
-
target = group;
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
if (!target || bestScore < 0.15) {
|
|
1194
|
-
target = {
|
|
1195
|
-
items: [],
|
|
1196
|
-
left: item.left,
|
|
1197
|
-
right: item.right,
|
|
1198
|
-
centerX: item.centerX
|
|
1199
|
-
};
|
|
1200
|
-
groups.push(target);
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
target.items.push(item);
|
|
1204
|
-
target.left = Math.min(target.left, item.left);
|
|
1205
|
-
target.right = Math.max(target.right, item.right);
|
|
1206
|
-
target.centerX = target.items.reduce((sum, n) => sum + n.centerX, 0) / target.items.length;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
return groups;
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
function buildTextFitLayoutUpdates(items) {
|
|
1213
|
-
const DEFAULT_GAP = 24;
|
|
1214
|
-
const MIN_GAP = 12;
|
|
1215
|
-
const groups = groupTextFramesByColumn(items);
|
|
1216
|
-
const updates = [];
|
|
1217
|
-
|
|
1218
|
-
for (const group of groups) {
|
|
1219
|
-
const ordered = [...group.items].sort((a, b) => b.y - a.y || a.x - b.x);
|
|
1220
|
-
|
|
1221
|
-
let previous = null;
|
|
1222
|
-
for (const item of ordered) {
|
|
1223
|
-
let y = item.y;
|
|
1224
|
-
|
|
1225
|
-
if (previous) {
|
|
1226
|
-
const originalGap = previous.y - previous.height - item.y;
|
|
1227
|
-
const gap = Number.isFinite(originalGap) && originalGap >= 0
|
|
1228
|
-
? Math.max(MIN_GAP, originalGap)
|
|
1229
|
-
: DEFAULT_GAP;
|
|
1230
|
-
y = previous.newY - previous.newHeight - gap;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
item.newY = y;
|
|
1234
|
-
previous = item;
|
|
1235
|
-
|
|
1236
|
-
updates.push({
|
|
1237
|
-
nodeId: item.nodeId,
|
|
1238
|
-
x: item.x,
|
|
1239
|
-
y,
|
|
1240
|
-
width: item.width,
|
|
1241
|
-
height: item.newHeight
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
return updates;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
async function applyTextFitLayoutUpdates(updates) {
|
|
1250
|
-
if (!_module || !Array.isArray(updates) || updates.length === 0) return;
|
|
1251
|
-
|
|
1252
|
-
const renderPromises = [];
|
|
1253
|
-
|
|
1254
|
-
for (const update of updates) {
|
|
1255
|
-
const entry = _module.nodeObjectsById.get(update.nodeId);
|
|
1256
|
-
if (!entry || !entry.model) continue;
|
|
1257
|
-
|
|
1258
|
-
const width = Math.max(1, Math.round(Number(update.width || entry.model.width || 400)));
|
|
1259
|
-
const height = Math.max(1, Math.round(Number(update.height || entry.model.height || 200)));
|
|
1260
|
-
const x = Number(update.x ?? entry.model.positionX ?? entry.model.x ?? 0);
|
|
1261
|
-
const y = Number(update.y ?? entry.model.positionY ?? entry.model.y ?? 0);
|
|
1262
|
-
|
|
1263
|
-
entry.model.positionX = x;
|
|
1264
|
-
entry.model.positionY = y;
|
|
1265
|
-
entry.model.x = x;
|
|
1266
|
-
entry.model.y = y;
|
|
1267
|
-
entry.model.width = width;
|
|
1268
|
-
entry.model.height = height;
|
|
1269
|
-
entry.model.originalHeight = height;
|
|
1270
|
-
entry.model.isExpanded = false;
|
|
1271
|
-
entry.model.scrollOffset = 0;
|
|
1272
|
-
entry.model.maxScroll = 0;
|
|
1273
|
-
|
|
1274
|
-
if (entry.glObject) {
|
|
1275
|
-
entry.glObject.position.x = x;
|
|
1276
|
-
entry.glObject.position.y = y;
|
|
1277
|
-
if (entry.glObject.userData) {
|
|
1278
|
-
entry.glObject.userData.worldWidth = width;
|
|
1279
|
-
entry.glObject.userData.worldHeight = height;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
if (entry.cssObject) {
|
|
1283
|
-
entry.cssObject.position.x = x;
|
|
1284
|
-
entry.cssObject.position.y = y;
|
|
1285
|
-
if (entry.cssObject.userData) {
|
|
1286
|
-
entry.cssObject.userData.worldWidth = width;
|
|
1287
|
-
entry.cssObject.userData.worldHeight = height;
|
|
1288
|
-
}
|
|
1289
|
-
window.MindMapCss3DManager?.syncCss3dContent?.(entry.model, entry.cssObject);
|
|
1290
|
-
entry.cssObject.updateMatrixWorld?.(true);
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
window.MindMapNodes?.updateNodeSpatialGrid?.(_module, update.nodeId);
|
|
1294
|
-
window.MindMapTextLOD?.invalidateNodeCache?.(update.nodeId, false);
|
|
1295
|
-
window.MindMapTextOverlayV2?.invalidateNode?.(_module, update.nodeId, 'layout');
|
|
1296
|
-
|
|
1297
|
-
if (window.MindMapNodes?.pendingNodeUpdates && window.MindMapPipeline?.processRenderQueue) {
|
|
1298
|
-
window.MindMapNodes.pendingNodeUpdates.set(update.nodeId, {
|
|
1299
|
-
content: entry.model.response ?? entry.model.Response ?? entry.model.content ?? '',
|
|
1300
|
-
width,
|
|
1301
|
-
height,
|
|
1302
|
-
skipMeasure: true,
|
|
1303
|
-
skipPersistDimensions: true
|
|
1304
|
-
});
|
|
1305
|
-
renderPromises.push(window.MindMapPipeline.processRenderQueue(_module, update.nodeId));
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
const RENDER_CHUNK_SIZE = 10;
|
|
1310
|
-
for (let i = 0; i < renderPromises.length; i += RENDER_CHUNK_SIZE) {
|
|
1311
|
-
await Promise.all(renderPromises.slice(i, i + RENDER_CHUNK_SIZE));
|
|
1312
|
-
if (i + RENDER_CHUNK_SIZE < renderPromises.length) {
|
|
1313
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
for (const update of updates) {
|
|
1318
|
-
window.MindMapTextOverlayV2?.updateNodePlacement?.(_module, update.nodeId);
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
_module.markLODPositionsDirty?.();
|
|
1322
|
-
_module.markLODDirty?.();
|
|
1323
|
-
_module.updateVisibility?.(_module.camera);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
// Fit selected text nodes to their CSS3D content and reflow Y inside each column.
|
|
1327
|
-
async function fitTextNodesToContentAndReflowY(selectedIds) {
|
|
1328
|
-
if (!_module) return;
|
|
1329
|
-
|
|
1330
|
-
const textNodeIds = selectedIds.filter(nodeId => {
|
|
1331
|
-
const entry = _module.nodeObjectsById.get(nodeId);
|
|
1332
|
-
return entry && TEXT_TYPES.includes(entry.model?.contentType);
|
|
1333
|
-
});
|
|
1334
|
-
|
|
1335
|
-
if (textNodeIds.length === 0) {
|
|
1336
|
-
console.log('[MultiSelect] No text nodes to fit');
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
console.log(`[MultiSelect] Fitting ${textNodeIds.length} text nodes to content...`);
|
|
1341
|
-
const startTime = performance.now();
|
|
1342
|
-
|
|
1343
|
-
const measureStart = performance.now();
|
|
1344
|
-
const measurePromises = textNodeIds.map(async (nodeId) => {
|
|
1345
|
-
const frame = getNodeFrame(nodeId);
|
|
1346
|
-
if (!frame) return null;
|
|
1347
|
-
|
|
1348
|
-
try {
|
|
1349
|
-
const newHeight = await measureTextFitHeight(frame.entry);
|
|
1350
|
-
if (!Number.isFinite(newHeight) || newHeight <= 0) return null;
|
|
1351
|
-
return { ...frame, newHeight };
|
|
1352
|
-
} catch (err) {
|
|
1353
|
-
console.error(`[MultiSelect] Failed to measure node ${nodeId}:`, err);
|
|
1354
|
-
return null;
|
|
1355
|
-
}
|
|
1356
|
-
});
|
|
1357
|
-
|
|
1358
|
-
const measureResults = await Promise.all(measurePromises);
|
|
1359
|
-
const fitItems = measureResults.filter(r => r !== null);
|
|
1360
|
-
|
|
1361
|
-
const measureTime = (performance.now() - measureStart).toFixed(0);
|
|
1362
|
-
console.log(`[MultiSelect] Measured ${textNodeIds.length} nodes in ${measureTime}ms`);
|
|
1363
|
-
|
|
1364
|
-
if (fitItems.length > 0) {
|
|
1365
|
-
let layoutUpdates = buildTextFitLayoutUpdates(fitItems);
|
|
1366
|
-
await applyTextFitLayoutUpdates(layoutUpdates);
|
|
1367
|
-
|
|
1368
|
-
const refinedCount = refineTextFitItemsFromRenderedDom(fitItems);
|
|
1369
|
-
if (refinedCount > 0) {
|
|
1370
|
-
layoutUpdates = buildTextFitLayoutUpdates(fitItems);
|
|
1371
|
-
await applyTextFitLayoutUpdates(layoutUpdates);
|
|
1372
|
-
console.log(`[MultiSelect] Refined ${refinedCount} text node heights from rendered DOM`);
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
if (_module.dotNetHelper && layoutUpdates.length > 0) {
|
|
1376
|
-
await _module.dotNetHelper.invokeMethodAsync('UpdateMultipleNodesPositionFromJs', layoutUpdates);
|
|
1377
|
-
_module.dotNetHelper.invokeMethodAsync('TriggerBoardSave');
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
const elapsed = (performance.now() - startTime).toFixed(0);
|
|
1381
|
-
console.log(`[MultiSelect] ✅ Fit ${layoutUpdates.length} text nodes in ${elapsed}ms`);
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
// Update bounds after fitting/reflow.
|
|
1385
|
-
showMultiSelectBounds(getSelectedNodeIds());
|
|
1386
|
-
}
|
|
1059
|
+
function getTextFitMinHeight(model) {
|
|
1060
|
+
const contentType = String(model?.contentType || '').toLowerCase();
|
|
1061
|
+
if (contentType === 'note') return 96;
|
|
1062
|
+
if (contentType === 'code') return 120;
|
|
1063
|
+
return 88;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
async function measureTextFitHeight(nodeEntry) {
|
|
1067
|
+
const model = nodeEntry?.model;
|
|
1068
|
+
if (!model) return null;
|
|
1069
|
+
|
|
1070
|
+
const FIT_BUFFER = 18;
|
|
1071
|
+
const minHeight = getTextFitMinHeight(model);
|
|
1072
|
+
|
|
1073
|
+
const domHeight = measureExpandedHeightFromCssDom(nodeEntry, FIT_BUFFER);
|
|
1074
|
+
let factoryHeight = null;
|
|
1075
|
+
|
|
1076
|
+
if (window.MindMapTextureFactory?.measureHtmlHeightAsync) {
|
|
1077
|
+
const zoomLevel = 2;
|
|
1078
|
+
const measureModel = {
|
|
1079
|
+
...model,
|
|
1080
|
+
response: model.response ?? model.Response ?? model.content ?? ''
|
|
1081
|
+
};
|
|
1082
|
+
const { height: measuredHeight } = await window.MindMapTextureFactory.measureHtmlHeightAsync(
|
|
1083
|
+
measureModel,
|
|
1084
|
+
zoomLevel,
|
|
1085
|
+
(model.width || 400) * zoomLevel,
|
|
1086
|
+
null,
|
|
1087
|
+
TEXT_FIT_MAX_HEIGHT
|
|
1088
|
+
);
|
|
1089
|
+
factoryHeight = measuredHeight + FIT_BUFFER;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const requiredHeight = Math.max(
|
|
1093
|
+
Number.isFinite(domHeight) ? domHeight : 0,
|
|
1094
|
+
Number.isFinite(factoryHeight) ? factoryHeight : 0
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
if (!Number.isFinite(requiredHeight) || requiredHeight <= 0) {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
return Math.min(TEXT_FIT_MAX_HEIGHT, Math.max(minHeight, Math.ceil(requiredHeight)));
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function readRenderedTextOverflowAdjustment(item) {
|
|
1105
|
+
const entry = item?.entry;
|
|
1106
|
+
const root = entry?.cssObject?.element?.firstElementChild || entry?.cssObject?.element;
|
|
1107
|
+
if (!root) return 0;
|
|
1108
|
+
|
|
1109
|
+
const contentEl = root.querySelector?.('[id^="node-response-"]')
|
|
1110
|
+
|| root.querySelector?.('.node-response')
|
|
1111
|
+
|| root.querySelector?.('.note-textarea')
|
|
1112
|
+
|| root.querySelector?.('textarea');
|
|
1113
|
+
if (!contentEl) return 0;
|
|
1114
|
+
|
|
1115
|
+
const scrollHeight = Math.ceil(Number(contentEl.scrollHeight || 0));
|
|
1116
|
+
const clientHeight = Math.ceil(Number(contentEl.clientHeight || 0));
|
|
1117
|
+
const overflow = scrollHeight - clientHeight;
|
|
1118
|
+
if (!Number.isFinite(overflow) || overflow <= 1) return 0;
|
|
1119
|
+
|
|
1120
|
+
return overflow + 12;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function refineTextFitItemsFromRenderedDom(items) {
|
|
1124
|
+
if (!Array.isArray(items) || items.length === 0) return 0;
|
|
1125
|
+
|
|
1126
|
+
let adjusted = 0;
|
|
1127
|
+
for (const item of items) {
|
|
1128
|
+
const correction = readRenderedTextOverflowAdjustment(item);
|
|
1129
|
+
if (correction <= 0) continue;
|
|
1130
|
+
|
|
1131
|
+
item.newHeight = Math.min(TEXT_FIT_MAX_HEIGHT, Math.ceil(item.newHeight + correction));
|
|
1132
|
+
adjusted++;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
return adjusted;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function getNodeFrame(nodeId) {
|
|
1139
|
+
const entry = _module?.nodeObjectsById?.get?.(nodeId);
|
|
1140
|
+
const model = entry?.model;
|
|
1141
|
+
if (!entry || !model) return null;
|
|
1142
|
+
|
|
1143
|
+
const x = Number(model.positionX ?? model.x ?? entry.glObject?.position?.x ?? entry.cssObject?.position?.x ?? 0);
|
|
1144
|
+
const y = Number(model.positionY ?? model.y ?? entry.glObject?.position?.y ?? entry.cssObject?.position?.y ?? 0);
|
|
1145
|
+
const width = Math.max(1, Math.round(Number(model.width || 400)));
|
|
1146
|
+
const height = Math.max(1, Math.round(Number(model.height || 200)));
|
|
1147
|
+
|
|
1148
|
+
return {
|
|
1149
|
+
nodeId,
|
|
1150
|
+
entry,
|
|
1151
|
+
model,
|
|
1152
|
+
x,
|
|
1153
|
+
y,
|
|
1154
|
+
width,
|
|
1155
|
+
height,
|
|
1156
|
+
left: x,
|
|
1157
|
+
right: x + width,
|
|
1158
|
+
centerX: x + width / 2
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function horizontalOverlapRatio(a, b) {
|
|
1163
|
+
const overlap = Math.min(a.right, b.right) - Math.max(a.left, b.left);
|
|
1164
|
+
if (overlap <= 0) return 0;
|
|
1165
|
+
return overlap / Math.max(1, Math.min(a.width, b.width));
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function groupTextFramesByColumn(items) {
|
|
1169
|
+
const groups = [];
|
|
1170
|
+
const ordered = [...items].sort((a, b) => a.left - b.left || b.y - a.y);
|
|
1171
|
+
|
|
1172
|
+
for (const item of ordered) {
|
|
1173
|
+
let target = null;
|
|
1174
|
+
let bestScore = 0;
|
|
1175
|
+
|
|
1176
|
+
for (const group of groups) {
|
|
1177
|
+
const groupFrame = {
|
|
1178
|
+
left: group.left,
|
|
1179
|
+
right: group.right,
|
|
1180
|
+
width: Math.max(1, group.right - group.left)
|
|
1181
|
+
};
|
|
1182
|
+
const overlapScore = horizontalOverlapRatio(item, groupFrame);
|
|
1183
|
+
const centerDistance = Math.abs(item.centerX - group.centerX);
|
|
1184
|
+
const centerThreshold = Math.max(80, Math.min(item.width, groupFrame.width) * 0.35);
|
|
1185
|
+
const score = overlapScore > 0 ? overlapScore : (centerDistance <= centerThreshold ? 0.2 : 0);
|
|
1186
|
+
|
|
1187
|
+
if (score > bestScore) {
|
|
1188
|
+
bestScore = score;
|
|
1189
|
+
target = group;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (!target || bestScore < 0.15) {
|
|
1194
|
+
target = {
|
|
1195
|
+
items: [],
|
|
1196
|
+
left: item.left,
|
|
1197
|
+
right: item.right,
|
|
1198
|
+
centerX: item.centerX
|
|
1199
|
+
};
|
|
1200
|
+
groups.push(target);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
target.items.push(item);
|
|
1204
|
+
target.left = Math.min(target.left, item.left);
|
|
1205
|
+
target.right = Math.max(target.right, item.right);
|
|
1206
|
+
target.centerX = target.items.reduce((sum, n) => sum + n.centerX, 0) / target.items.length;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
return groups;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function buildTextFitLayoutUpdates(items) {
|
|
1213
|
+
const DEFAULT_GAP = 24;
|
|
1214
|
+
const MIN_GAP = 12;
|
|
1215
|
+
const groups = groupTextFramesByColumn(items);
|
|
1216
|
+
const updates = [];
|
|
1217
|
+
|
|
1218
|
+
for (const group of groups) {
|
|
1219
|
+
const ordered = [...group.items].sort((a, b) => b.y - a.y || a.x - b.x);
|
|
1220
|
+
|
|
1221
|
+
let previous = null;
|
|
1222
|
+
for (const item of ordered) {
|
|
1223
|
+
let y = item.y;
|
|
1224
|
+
|
|
1225
|
+
if (previous) {
|
|
1226
|
+
const originalGap = previous.y - previous.height - item.y;
|
|
1227
|
+
const gap = Number.isFinite(originalGap) && originalGap >= 0
|
|
1228
|
+
? Math.max(MIN_GAP, originalGap)
|
|
1229
|
+
: DEFAULT_GAP;
|
|
1230
|
+
y = previous.newY - previous.newHeight - gap;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
item.newY = y;
|
|
1234
|
+
previous = item;
|
|
1235
|
+
|
|
1236
|
+
updates.push({
|
|
1237
|
+
nodeId: item.nodeId,
|
|
1238
|
+
x: item.x,
|
|
1239
|
+
y,
|
|
1240
|
+
width: item.width,
|
|
1241
|
+
height: item.newHeight
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
return updates;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
async function applyTextFitLayoutUpdates(updates) {
|
|
1250
|
+
if (!_module || !Array.isArray(updates) || updates.length === 0) return;
|
|
1251
|
+
|
|
1252
|
+
const renderPromises = [];
|
|
1253
|
+
|
|
1254
|
+
for (const update of updates) {
|
|
1255
|
+
const entry = _module.nodeObjectsById.get(update.nodeId);
|
|
1256
|
+
if (!entry || !entry.model) continue;
|
|
1257
|
+
|
|
1258
|
+
const width = Math.max(1, Math.round(Number(update.width || entry.model.width || 400)));
|
|
1259
|
+
const height = Math.max(1, Math.round(Number(update.height || entry.model.height || 200)));
|
|
1260
|
+
const x = Number(update.x ?? entry.model.positionX ?? entry.model.x ?? 0);
|
|
1261
|
+
const y = Number(update.y ?? entry.model.positionY ?? entry.model.y ?? 0);
|
|
1262
|
+
|
|
1263
|
+
entry.model.positionX = x;
|
|
1264
|
+
entry.model.positionY = y;
|
|
1265
|
+
entry.model.x = x;
|
|
1266
|
+
entry.model.y = y;
|
|
1267
|
+
entry.model.width = width;
|
|
1268
|
+
entry.model.height = height;
|
|
1269
|
+
entry.model.originalHeight = height;
|
|
1270
|
+
entry.model.isExpanded = false;
|
|
1271
|
+
entry.model.scrollOffset = 0;
|
|
1272
|
+
entry.model.maxScroll = 0;
|
|
1273
|
+
|
|
1274
|
+
if (entry.glObject) {
|
|
1275
|
+
entry.glObject.position.x = x;
|
|
1276
|
+
entry.glObject.position.y = y;
|
|
1277
|
+
if (entry.glObject.userData) {
|
|
1278
|
+
entry.glObject.userData.worldWidth = width;
|
|
1279
|
+
entry.glObject.userData.worldHeight = height;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
if (entry.cssObject) {
|
|
1283
|
+
entry.cssObject.position.x = x;
|
|
1284
|
+
entry.cssObject.position.y = y;
|
|
1285
|
+
if (entry.cssObject.userData) {
|
|
1286
|
+
entry.cssObject.userData.worldWidth = width;
|
|
1287
|
+
entry.cssObject.userData.worldHeight = height;
|
|
1288
|
+
}
|
|
1289
|
+
window.MindMapCss3DManager?.syncCss3dContent?.(entry.model, entry.cssObject);
|
|
1290
|
+
entry.cssObject.updateMatrixWorld?.(true);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
window.MindMapNodes?.updateNodeSpatialGrid?.(_module, update.nodeId);
|
|
1294
|
+
window.MindMapTextLOD?.invalidateNodeCache?.(update.nodeId, false);
|
|
1295
|
+
window.MindMapTextOverlayV2?.invalidateNode?.(_module, update.nodeId, 'layout');
|
|
1296
|
+
|
|
1297
|
+
if (window.MindMapNodes?.pendingNodeUpdates && window.MindMapPipeline?.processRenderQueue) {
|
|
1298
|
+
window.MindMapNodes.pendingNodeUpdates.set(update.nodeId, {
|
|
1299
|
+
content: entry.model.response ?? entry.model.Response ?? entry.model.content ?? '',
|
|
1300
|
+
width,
|
|
1301
|
+
height,
|
|
1302
|
+
skipMeasure: true,
|
|
1303
|
+
skipPersistDimensions: true
|
|
1304
|
+
});
|
|
1305
|
+
renderPromises.push(window.MindMapPipeline.processRenderQueue(_module, update.nodeId));
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const RENDER_CHUNK_SIZE = 10;
|
|
1310
|
+
for (let i = 0; i < renderPromises.length; i += RENDER_CHUNK_SIZE) {
|
|
1311
|
+
await Promise.all(renderPromises.slice(i, i + RENDER_CHUNK_SIZE));
|
|
1312
|
+
if (i + RENDER_CHUNK_SIZE < renderPromises.length) {
|
|
1313
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
for (const update of updates) {
|
|
1318
|
+
window.MindMapTextOverlayV2?.updateNodePlacement?.(_module, update.nodeId);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
_module.markLODPositionsDirty?.();
|
|
1322
|
+
_module.markLODDirty?.();
|
|
1323
|
+
_module.updateVisibility?.(_module.camera);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Fit selected text nodes to their CSS3D content and reflow Y inside each column.
|
|
1327
|
+
async function fitTextNodesToContentAndReflowY(selectedIds) {
|
|
1328
|
+
if (!_module) return;
|
|
1329
|
+
|
|
1330
|
+
const textNodeIds = selectedIds.filter(nodeId => {
|
|
1331
|
+
const entry = _module.nodeObjectsById.get(nodeId);
|
|
1332
|
+
return entry && TEXT_TYPES.includes(entry.model?.contentType);
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
if (textNodeIds.length === 0) {
|
|
1336
|
+
console.log('[MultiSelect] No text nodes to fit');
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
console.log(`[MultiSelect] Fitting ${textNodeIds.length} text nodes to content...`);
|
|
1341
|
+
const startTime = performance.now();
|
|
1342
|
+
|
|
1343
|
+
const measureStart = performance.now();
|
|
1344
|
+
const measurePromises = textNodeIds.map(async (nodeId) => {
|
|
1345
|
+
const frame = getNodeFrame(nodeId);
|
|
1346
|
+
if (!frame) return null;
|
|
1347
|
+
|
|
1348
|
+
try {
|
|
1349
|
+
const newHeight = await measureTextFitHeight(frame.entry);
|
|
1350
|
+
if (!Number.isFinite(newHeight) || newHeight <= 0) return null;
|
|
1351
|
+
return { ...frame, newHeight };
|
|
1352
|
+
} catch (err) {
|
|
1353
|
+
console.error(`[MultiSelect] Failed to measure node ${nodeId}:`, err);
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
const measureResults = await Promise.all(measurePromises);
|
|
1359
|
+
const fitItems = measureResults.filter(r => r !== null);
|
|
1360
|
+
|
|
1361
|
+
const measureTime = (performance.now() - measureStart).toFixed(0);
|
|
1362
|
+
console.log(`[MultiSelect] Measured ${textNodeIds.length} nodes in ${measureTime}ms`);
|
|
1363
|
+
|
|
1364
|
+
if (fitItems.length > 0) {
|
|
1365
|
+
let layoutUpdates = buildTextFitLayoutUpdates(fitItems);
|
|
1366
|
+
await applyTextFitLayoutUpdates(layoutUpdates);
|
|
1367
|
+
|
|
1368
|
+
const refinedCount = refineTextFitItemsFromRenderedDom(fitItems);
|
|
1369
|
+
if (refinedCount > 0) {
|
|
1370
|
+
layoutUpdates = buildTextFitLayoutUpdates(fitItems);
|
|
1371
|
+
await applyTextFitLayoutUpdates(layoutUpdates);
|
|
1372
|
+
console.log(`[MultiSelect] Refined ${refinedCount} text node heights from rendered DOM`);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
if (_module.dotNetHelper && layoutUpdates.length > 0) {
|
|
1376
|
+
await _module.dotNetHelper.invokeMethodAsync('UpdateMultipleNodesPositionFromJs', layoutUpdates);
|
|
1377
|
+
_module.dotNetHelper.invokeMethodAsync('TriggerBoardSave');
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
const elapsed = (performance.now() - startTime).toFixed(0);
|
|
1381
|
+
console.log(`[MultiSelect] ✅ Fit ${layoutUpdates.length} text nodes in ${elapsed}ms`);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Update bounds after fitting/reflow.
|
|
1385
|
+
showMultiSelectBounds(getSelectedNodeIds());
|
|
1386
|
+
}
|
|
1387
1387
|
|
|
1388
1388
|
function applyMasonryLayout(selectedIds) {
|
|
1389
1389
|
if (!currentBounds || selectedIds.length === 0) return;
|
|
@@ -1511,27 +1511,27 @@
|
|
|
1511
1511
|
if (multiSelectOverlay) {
|
|
1512
1512
|
multiSelectOverlay.style.display = 'none';
|
|
1513
1513
|
multiSelectOverlay.style.visibility = 'hidden';
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
// Delete via C# (batch delete - much faster than individual calls)
|
|
1517
|
-
const snapshots = selectedIds
|
|
1518
|
-
.map(nodeId => window.MindMapNodes?.captureDeleteSnapshot?.(_module, nodeId))
|
|
1519
|
-
.filter(Boolean);
|
|
1520
|
-
if (snapshots.length > 0) {
|
|
1521
|
-
window.MindMapNodes?.removeNodes?.(_module, snapshots.map(snapshot => snapshot.nodeId));
|
|
1522
|
-
}
|
|
1523
|
-
_module.dotNetHelper.invokeMethodAsync('DeleteMultipleNodeSnapshotsFromJs', snapshots);
|
|
1524
|
-
|
|
1525
|
-
console.log(`[MultiSelect] Deleted ${selectedIds.length} nodes`);
|
|
1526
|
-
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Delete via C# (batch delete - much faster than individual calls)
|
|
1517
|
+
const snapshots = selectedIds
|
|
1518
|
+
.map(nodeId => window.MindMapNodes?.captureDeleteSnapshot?.(_module, nodeId))
|
|
1519
|
+
.filter(Boolean);
|
|
1520
|
+
if (snapshots.length > 0) {
|
|
1521
|
+
window.MindMapNodes?.removeNodes?.(_module, snapshots.map(snapshot => snapshot.nodeId));
|
|
1522
|
+
}
|
|
1523
|
+
_module.dotNetHelper.invokeMethodAsync('DeleteMultipleNodeSnapshotsFromJs', snapshots);
|
|
1524
|
+
|
|
1525
|
+
console.log(`[MultiSelect] Deleted ${selectedIds.length} nodes`);
|
|
1526
|
+
}
|
|
1527
1527
|
|
|
1528
1528
|
// Update overlay position on camera move or node drag (called from animate loop)
|
|
1529
1529
|
// ▼▼▼ [Perf] Throttled and optimized for drag performance ▼▼▼
|
|
1530
|
-
function updateMultiSelectOverlay() {
|
|
1531
|
-
if (!_module || !multiSelectOverlay || multiSelectOverlay.style.display === 'none') return;
|
|
1532
|
-
|
|
1533
|
-
// Centralized drag-state sync from interactions module
|
|
1534
|
-
const draggingNow = !!_module.isDraggingMultipleNodes;
|
|
1530
|
+
function updateMultiSelectOverlay() {
|
|
1531
|
+
if (!_module || !multiSelectOverlay || multiSelectOverlay.style.display === 'none') return;
|
|
1532
|
+
|
|
1533
|
+
// Centralized drag-state sync from interactions module
|
|
1534
|
+
const draggingNow = !!_module.isDraggingMultipleNodes;
|
|
1535
1535
|
if (draggingNow !== isDragging) {
|
|
1536
1536
|
isDragging = draggingNow;
|
|
1537
1537
|
lastUpdateTime = 0;
|
|
@@ -1581,45 +1581,45 @@
|
|
|
1581
1581
|
}
|
|
1582
1582
|
|
|
1583
1583
|
// Add padding
|
|
1584
|
-
const padding = 10;
|
|
1584
|
+
const padding = 10;
|
|
1585
1585
|
currentBounds = {
|
|
1586
1586
|
minX: minX - padding,
|
|
1587
1587
|
minY: minY - padding,
|
|
1588
1588
|
maxX: maxX + padding,
|
|
1589
1589
|
maxY: maxY + padding
|
|
1590
1590
|
};
|
|
1591
|
-
|
|
1592
|
-
updateOverlayPosition(selectedIds);
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
function update() {
|
|
1596
|
-
if (!_module) return;
|
|
1597
|
-
updateMultiSelectOverlay();
|
|
1598
|
-
updateSingleImageOverlay();
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
// Camera-only fast path: currentBounds and node world frames are already
|
|
1602
|
-
// canonical, so reproject only the fixed overlays. This deliberately avoids
|
|
1603
|
-
// selected-node scans, DOM layout reads, menu work, and renderer rebuilds.
|
|
1604
|
-
function updateForCameraMotion() {
|
|
1605
|
-
if (!_module || !_module.camera || !_module.container) return false;
|
|
1606
|
-
|
|
1607
|
-
let updated = false;
|
|
1608
|
-
if (multiSelectOverlay &&
|
|
1609
|
-
multiSelectOverlay.style.display !== 'none' &&
|
|
1610
|
-
currentBounds) {
|
|
1611
|
-
updateOverlayPosition(null, true);
|
|
1612
|
-
updated = true;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
if (getSingleSelectedImageNodeId()) {
|
|
1616
|
-
updateSingleImageOverlay(true);
|
|
1617
|
-
updated = true;
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
return updated;
|
|
1621
|
-
}
|
|
1622
|
-
// ▲▲▲ [Perf] ▲▲▲
|
|
1591
|
+
|
|
1592
|
+
updateOverlayPosition(selectedIds);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
function update() {
|
|
1596
|
+
if (!_module) return;
|
|
1597
|
+
updateMultiSelectOverlay();
|
|
1598
|
+
updateSingleImageOverlay();
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// Camera-only fast path: currentBounds and node world frames are already
|
|
1602
|
+
// canonical, so reproject only the fixed overlays. This deliberately avoids
|
|
1603
|
+
// selected-node scans, DOM layout reads, menu work, and renderer rebuilds.
|
|
1604
|
+
function updateForCameraMotion() {
|
|
1605
|
+
if (!_module || !_module.camera || !_module.container) return false;
|
|
1606
|
+
|
|
1607
|
+
let updated = false;
|
|
1608
|
+
if (multiSelectOverlay &&
|
|
1609
|
+
multiSelectOverlay.style.display !== 'none' &&
|
|
1610
|
+
currentBounds) {
|
|
1611
|
+
updateOverlayPosition(null, true);
|
|
1612
|
+
updated = true;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
if (getSingleSelectedImageNodeId()) {
|
|
1616
|
+
updateSingleImageOverlay(true);
|
|
1617
|
+
updated = true;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
return updated;
|
|
1621
|
+
}
|
|
1622
|
+
// ▲▲▲ [Perf] ▲▲▲
|
|
1623
1623
|
|
|
1624
1624
|
// ▼▼▼ [New] Dynamic menu button creation based on selection type ▼▼▼
|
|
1625
1625
|
function updateMenuButtons() {
|
|
@@ -1639,24 +1639,24 @@
|
|
|
1639
1639
|
// Copy is always available
|
|
1640
1640
|
buttons.push({ icon: 'fa-regular fa-copy', title: 'Copy', action: 'copy', class: 'js-btn-multi-copy' });
|
|
1641
1641
|
|
|
1642
|
-
const isMixedImageTextSelection = hasImages && hasText;
|
|
1643
|
-
|
|
1644
|
-
if (hasImages && !isMixedImageTextSelection) {
|
|
1645
|
-
// Image layout organization actions
|
|
1646
|
-
buttons.push({ icon: 'fa-solid fa-table-cells-large', title: 'Tidy masonry', action: 'masonry', class: 'js-btn-multi-masonry' });
|
|
1647
|
-
buttons.push({ icon: 'fa-solid fa-border-all', title: 'Tidy grid', action: 'grid', class: 'js-btn-multi-grid' });
|
|
1648
|
-
buttons.push({ icon: 'fa-solid fa-grip-lines', title: 'Tidy strip', action: 'stripHorizontal', class: 'js-btn-multi-strip' });
|
|
1642
|
+
const isMixedImageTextSelection = hasImages && hasText;
|
|
1643
|
+
|
|
1644
|
+
if (hasImages && !isMixedImageTextSelection) {
|
|
1645
|
+
// Image layout organization actions
|
|
1646
|
+
buttons.push({ icon: 'fa-solid fa-table-cells-large', title: 'Tidy masonry', action: 'masonry', class: 'js-btn-multi-masonry' });
|
|
1647
|
+
buttons.push({ icon: 'fa-solid fa-border-all', title: 'Tidy grid', action: 'grid', class: 'js-btn-multi-grid' });
|
|
1648
|
+
buttons.push({ icon: 'fa-solid fa-grip-lines', title: 'Tidy strip', action: 'stripHorizontal', class: 'js-btn-multi-strip' });
|
|
1649
1649
|
|
|
1650
1650
|
// Stack is only for image-only selection
|
|
1651
1651
|
if (!hasText) {
|
|
1652
1652
|
buttons.push({ icon: 'fa-solid fa-layer-group', title: 'Stack into pile', action: 'stack', class: 'js-btn-multi-stack' });
|
|
1653
1653
|
}
|
|
1654
1654
|
}
|
|
1655
|
-
|
|
1656
|
-
// Text-specific actions
|
|
1657
|
-
if (hasText && !isMixedImageTextSelection) {
|
|
1658
|
-
buttons.push({ icon: 'fa-solid fa-arrows-up-down', title: 'Fit text height', action: 'expand', class: 'js-btn-multi-expand' });
|
|
1659
|
-
}
|
|
1655
|
+
|
|
1656
|
+
// Text-specific actions
|
|
1657
|
+
if (hasText && !isMixedImageTextSelection) {
|
|
1658
|
+
buttons.push({ icon: 'fa-solid fa-arrows-up-down', title: 'Fit text height', action: 'expand', class: 'js-btn-multi-expand' });
|
|
1659
|
+
}
|
|
1660
1660
|
|
|
1661
1661
|
buttons.push({ separator: true });
|
|
1662
1662
|
buttons.push({ icon: 'fa-solid fa-trash', title: 'Delete all', action: 'delete', class: 'js-btn-multi-delete delete-btn' });
|
|
@@ -1703,37 +1703,37 @@
|
|
|
1703
1703
|
function checkAndUpdateDisplay() {
|
|
1704
1704
|
if (!_module) return;
|
|
1705
1705
|
|
|
1706
|
-
const nodeIds = getSelectedNodeIds();
|
|
1707
|
-
|
|
1708
|
-
if (nodeIds.length >= 2) {
|
|
1709
|
-
updateMenuButtons(); // Update menu based on selection type
|
|
1710
|
-
showMultiSelectBounds(nodeIds);
|
|
1711
|
-
hideSingleImageOverlay();
|
|
1712
|
-
// Hide single-node menu
|
|
1713
|
-
if (window.MindMapMenuManager) {
|
|
1714
|
-
window.MindMapMenuManager.hideMenu();
|
|
1715
|
-
}
|
|
1716
|
-
} else {
|
|
1717
|
-
if (multiSelectOverlay) {
|
|
1718
|
-
multiSelectOverlay.style.display = 'none';
|
|
1719
|
-
multiSelectOverlay.style.visibility = 'hidden';
|
|
1720
|
-
}
|
|
1721
|
-
currentBounds = null;
|
|
1722
|
-
initialNodeSizes.clear();
|
|
1723
|
-
updateSingleImageOverlay();
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1706
|
+
const nodeIds = getSelectedNodeIds();
|
|
1707
|
+
|
|
1708
|
+
if (nodeIds.length >= 2) {
|
|
1709
|
+
updateMenuButtons(); // Update menu based on selection type
|
|
1710
|
+
showMultiSelectBounds(nodeIds);
|
|
1711
|
+
hideSingleImageOverlay();
|
|
1712
|
+
// Hide single-node menu
|
|
1713
|
+
if (window.MindMapMenuManager) {
|
|
1714
|
+
window.MindMapMenuManager.hideMenu();
|
|
1715
|
+
}
|
|
1716
|
+
} else {
|
|
1717
|
+
if (multiSelectOverlay) {
|
|
1718
|
+
multiSelectOverlay.style.display = 'none';
|
|
1719
|
+
multiSelectOverlay.style.visibility = 'hidden';
|
|
1720
|
+
}
|
|
1721
|
+
currentBounds = null;
|
|
1722
|
+
initialNodeSizes.clear();
|
|
1723
|
+
updateSingleImageOverlay();
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
1726
|
|
|
1727
1727
|
window.MindMapMultiSelect = {
|
|
1728
1728
|
init,
|
|
1729
|
-
showMultiSelectBounds,
|
|
1730
|
-
hide,
|
|
1731
|
-
update,
|
|
1732
|
-
updateForCameraMotion,
|
|
1733
|
-
checkAndUpdateDisplay,
|
|
1734
|
-
getSelectedImageNodeIds,
|
|
1735
|
-
fitTextNodesToContentAndReflowY,
|
|
1736
|
-
isResizing: () => isResizing,
|
|
1729
|
+
showMultiSelectBounds,
|
|
1730
|
+
hide,
|
|
1731
|
+
update,
|
|
1732
|
+
updateForCameraMotion,
|
|
1733
|
+
checkAndUpdateDisplay,
|
|
1734
|
+
getSelectedImageNodeIds,
|
|
1735
|
+
fitTextNodesToContentAndReflowY,
|
|
1736
|
+
isResizing: () => isResizing,
|
|
1737
1737
|
handleResizeMove: onResizeMove,
|
|
1738
1738
|
handleResizeEnd: onResizeEnd
|
|
1739
1739
|
};
|