@ifc-lite/viewer 1.27.0 → 1.28.1
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/.turbo/turbo-build.log +35 -42
- package/CHANGELOG.md +74 -0
- package/dist/assets/{basketViewActivator-B3CdrLsb.js → basketViewActivator-Ce38DhXd.js} +8 -8
- package/dist/assets/{bcf-QeHK_Aud.js → bcf-Cv_O3JfD.js} +56 -56
- package/dist/assets/{decode-worker-CgM1iNSK.js → decode-worker-Cjign7Zh.js} +1 -1
- package/dist/assets/{deflate-B-d0SYQM.js → deflate-HbyMq59o.js} +1 -1
- package/dist/assets/drawing-2d-DW98umlt.js +257 -0
- package/dist/assets/e57-source-2wI9jkCA.js +1 -0
- package/dist/assets/{exporters-B4LbZFeT.js → exporters-BuD3XRzB.js} +1309 -1153
- package/dist/assets/geometry.worker-TH3fCCoY.js +1 -0
- package/dist/assets/{geotiff-CrVtDRFq.js → geotiff-B2HA8Bwm.js} +10 -10
- package/dist/assets/{ids-DjsGFN10.js → ids-DYUFMd5f.js} +952 -945
- package/dist/assets/{ifc-lite_bg-DsYUIHm3.wasm → ifc-lite_bg-BEA5DLmg.wasm} +0 -0
- package/dist/assets/index-E9wB0zWt.css +1 -0
- package/dist/assets/{index-COYokSKc.js → index-n5O1QJMM.js} +37877 -38126
- package/dist/assets/{index.es-CY202jA3.js → index.es-BKVIpZgL.js} +9 -9
- package/dist/assets/{jpeg-D4wOkf5h.js → jpeg-C7hjKjPX.js} +1 -1
- package/dist/assets/{jspdf.es.min-DIGb9BHN.js → jspdf.es.min-oWlFc42Y.js} +4 -4
- package/dist/assets/lens-C4p1kQ0p.js +1 -0
- package/dist/assets/{lerc-DmW0_tgf.js → lerc-BfIOGhQz.js} +1 -1
- package/dist/assets/{lzw-oWetY-d6.js → lzw-B0jRuuW5.js} +1 -1
- package/dist/assets/{native-bridge-BX8_tHXE.js → native-bridge-DpB-dtEn.js} +6 -3
- package/dist/assets/{packbits-F8Nkp4NY.js → packbits-DVvBTC39.js} +1 -1
- package/dist/assets/parser.worker-BDsWQ6rc.js +182 -0
- package/dist/assets/{pdf-Dsh3HPZB.js → pdf-dVIqI5ac.js} +10 -10
- package/dist/assets/raw-C0ZJYGmN.js +1 -0
- package/dist/assets/{sandbox-BAC3a-eN.js → sandbox-qpJlrNN0.js} +2962 -2554
- package/dist/assets/server-client-DVZ2huNS.js +719 -0
- package/dist/assets/{webimage-BLV1dgmd.js → webimage-B394g0Tw.js} +1 -1
- package/dist/assets/{xlsx-Bc2HTrjC.js → xlsx-D-oHO76J.js} +8 -8
- package/dist/assets/{zstd-C_1HxVrA.js → zstd-Bf38MwV2.js} +1 -1
- package/dist/index.html +9 -9
- package/package.json +24 -23
- package/src/App.tsx +1 -3
- package/src/components/mcp/playground-dispatcher.ts +3 -0
- package/src/components/mcp/playground-files.ts +33 -1
- package/src/components/viewer/BCFPanel.tsx +1 -16
- package/src/components/viewer/ChatPanel.tsx +11 -46
- package/src/components/viewer/CommandPalette.tsx +6 -1
- package/src/components/viewer/ComparePanel.tsx +420 -0
- package/src/components/viewer/HierarchyPanel.tsx +48 -183
- package/src/components/viewer/IDSPanel.tsx +1 -26
- package/src/components/viewer/MainToolbar.tsx +94 -187
- package/src/components/viewer/MobileToolbar.tsx +1 -9
- package/src/components/viewer/PropertiesPanel.tsx +98 -127
- package/src/components/viewer/ScriptPanel.tsx +8 -34
- package/src/components/viewer/Section2DPanel.tsx +32 -1
- package/src/components/viewer/ViewerLayout.tsx +5 -2
- package/src/components/viewer/Viewport.tsx +3 -0
- package/src/components/viewer/ViewportContainer.tsx +24 -42
- package/src/components/viewer/ViewportOverlays.tsx +1 -4
- package/src/components/viewer/hierarchy/HierarchyNode.tsx +3 -3
- package/src/components/viewer/hierarchy/ifc-icons.ts +9 -0
- package/src/components/viewer/hierarchy/treeDataBuilder.ts +87 -0
- package/src/components/viewer/hierarchy/types.ts +1 -0
- package/src/components/viewer/hierarchy/useHierarchyTree.ts +6 -2
- package/src/components/viewer/properties/MaterialTotalsPanel.tsx +283 -0
- package/src/components/viewer/useGeometryStreaming.ts +0 -2
- package/src/hooks/federationLoadGate.test.ts +12 -2
- package/src/hooks/federationLoadGate.ts +9 -2
- package/src/hooks/ingest/federationAlign.ts +488 -0
- package/src/hooks/ingest/viewerModelIngest.ts +3 -212
- package/src/hooks/useCompare.ts +0 -0
- package/src/hooks/useCompareOverlay.ts +119 -0
- package/src/hooks/useDrawingGeneration.ts +234 -14
- package/src/hooks/useIfc.ts +1 -1
- package/src/hooks/useIfcCache.ts +100 -24
- package/src/hooks/useIfcFederation.ts +42 -811
- package/src/hooks/useIfcLoader.ts +349 -1517
- package/src/hooks/useIfcServer.ts +3 -0
- package/src/hooks/useLens.ts +5 -1
- package/src/hooks/useSymbolicAnnotations.ts +70 -38
- package/src/lib/compare/buildFingerprints.ts +173 -0
- package/src/lib/compare/describeChange.ts +0 -0
- package/src/lib/compare/geometricData.test.ts +54 -0
- package/src/lib/compare/geometricData.ts +37 -0
- package/src/lib/compare/overlay.test.ts +99 -0
- package/src/lib/compare/overlay.ts +91 -0
- package/src/lib/geo/cesium-placement.ts +1 -1
- package/src/lib/geo/reproject.ts +4 -1
- package/src/lib/llm/script-edit-ops.ts +23 -0
- package/src/lib/llm/stream-client.ts +8 -1
- package/src/lib/search/result-export.ts +7 -1
- package/src/sdk/adapters/export-adapter.ts +6 -1
- package/src/services/cacheService.ts +9 -25
- package/src/services/desktop-export.ts +2 -59
- package/src/services/file-dialog.ts +8 -142
- package/src/store/constants.ts +23 -0
- package/src/store/globalId.ts +15 -13
- package/src/store/index.ts +19 -6
- package/src/store/slices/cesiumSlice.ts +8 -1
- package/src/store/slices/compareSlice.ts +96 -0
- package/src/store/slices/drawing2DSlice.ts +8 -0
- package/src/store/slices/lensSlice.ts +8 -0
- package/src/store/slices/visibilitySlice.ts +22 -1
- package/src/store/types.ts +1 -71
- package/src/utils/acquireFileBuffer.test.ts +12 -4
- package/src/utils/ifcConfig.ts +0 -12
- package/src/utils/loadingUtils.ts +32 -0
- package/src/utils/spatialHierarchy.test.ts +53 -1
- package/src/utils/spatialHierarchy.ts +42 -2
- package/src/vite-env.d.ts +2 -0
- package/vite.config.ts +6 -3
- package/DESKTOP_CONTRACT_VERSION +0 -1
- package/dist/assets/drawing-2d-C71b8Ugx.js +0 -257
- package/dist/assets/e57-source-CQHxE8n3.js +0 -1
- package/dist/assets/event-B0kAzHa-.js +0 -1
- package/dist/assets/geometry.worker-BdH-E6NB.js +0 -1
- package/dist/assets/index-ajK6D32J.css +0 -1
- package/dist/assets/lens-PYsLu_MA.js +0 -1
- package/dist/assets/parser.worker-D591Zu_-.js +0 -182
- package/dist/assets/raw-D9iw0tmc.js +0 -1
- package/dist/assets/server-client-Cjwnm7il.js +0 -706
- package/dist/assets/tauri-core-stub-D8Fa-u43.js +0 -1
- package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +0 -1
- package/dist/assets/tauri-fs-stub-BdeRC7aK.js +0 -1
- package/src/components/viewer/DesktopEntitlementBanner.tsx +0 -74
- package/src/components/viewer/SettingsPage.tsx +0 -581
- package/src/hooks/ingest/resolveDataStoreOrAbort.test.ts +0 -61
- package/src/hooks/ingest/resolveDataStoreOrAbort.ts +0 -28
- package/src/hooks/ingest/watchedGeometryStream.test.ts +0 -78
- package/src/hooks/ingest/watchedGeometryStream.ts +0 -76
- package/src/lib/desktop/desktopEntitlementEvents.ts +0 -39
- package/src/lib/desktop-entitlement.ts +0 -43
- package/src/lib/desktop-product.ts +0 -130
- package/src/lib/platform.ts +0 -23
- package/src/services/desktop-cache.ts +0 -186
- package/src/services/desktop-harness.ts +0 -196
- package/src/services/desktop-logger.ts +0 -20
- package/src/services/desktop-native-metadata.ts +0 -230
- package/src/services/desktop-panel-actions.ts +0 -43
- package/src/services/desktop-preferences.ts +0 -44
- package/src/services/fs-cache.ts +0 -212
- package/src/services/tauri-core-stub.ts +0 -7
- package/src/services/tauri-dialog-stub.ts +0 -7
- package/src/services/tauri-fs-stub.ts +0 -7
- package/src/services/tauri-modules.d.ts +0 -50
- package/src/store/slices/desktopEntitlementSlice.ts +0 -86
- package/src/utils/desktopModelSnapshot.ts +0 -358
- package/src/utils/nativeSpatialDataStore.ts +0 -277
- package/src-tauri/Cargo.toml +0 -29
- package/src-tauri/build.rs +0 -7
- package/src-tauri/capabilities/default.json +0 -18
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/lib.rs +0 -21
- package/src-tauri/src/main.rs +0 -10
- package/src-tauri/tauri.conf.json +0 -39
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
4
|
|
|
5
|
-
import { useState, useCallback, useRef, useEffect, useMemo
|
|
5
|
+
import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
|
6
6
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
7
7
|
import {
|
|
8
8
|
Search,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
LayoutTemplate,
|
|
12
12
|
FileBox,
|
|
13
13
|
GripHorizontal,
|
|
14
|
+
Palette,
|
|
14
15
|
} from 'lucide-react';
|
|
15
16
|
import { Input } from '@/components/ui/input';
|
|
16
17
|
import { Button } from '@/components/ui/button';
|
|
@@ -18,7 +19,6 @@ import { cn } from '@/lib/utils';
|
|
|
18
19
|
import { useViewerStore, resolveEntityRef } from '@/store';
|
|
19
20
|
import { toGlobalIdFromModels } from '@/store/globalId';
|
|
20
21
|
import { useIfc } from '@/hooks/useIfc';
|
|
21
|
-
import { getNativeMetadataChildren, searchNativeMetadataEntities } from '@/services/desktop-native-metadata';
|
|
22
22
|
|
|
23
23
|
import type { TreeNode } from './hierarchy/types';
|
|
24
24
|
import { isSpatialContainer } from './hierarchy/types';
|
|
@@ -37,7 +37,6 @@ export function HierarchyPanel() {
|
|
|
37
37
|
removeModel,
|
|
38
38
|
} = useIfc();
|
|
39
39
|
const selectedEntityId = useViewerStore((s) => s.selectedEntityId);
|
|
40
|
-
const selectedEntity = useViewerStore((s) => s.selectedEntity);
|
|
41
40
|
const setSelectedEntityId = useViewerStore((s) => s.setSelectedEntityId);
|
|
42
41
|
const setSelectedEntityIds = useViewerStore((s) => s.setSelectedEntityIds);
|
|
43
42
|
const setSelectedEntity = useViewerStore((s) => s.setSelectedEntity);
|
|
@@ -91,33 +90,6 @@ export function HierarchyPanel() {
|
|
|
91
90
|
|
|
92
91
|
// Check if we have multiple models loaded
|
|
93
92
|
const isMultiModel = models.size > 1;
|
|
94
|
-
const nativeLazyModel = useMemo(() => {
|
|
95
|
-
if (models.size !== 1) return null;
|
|
96
|
-
const [, model] = Array.from(models.entries())[0];
|
|
97
|
-
if (!model.nativeMetadata) return null;
|
|
98
|
-
return model.ifcDataStore?.spatialHierarchy ? null : model;
|
|
99
|
-
}, [models]);
|
|
100
|
-
const [nativeChildren, setNativeChildren] = useState<Record<number, Array<{
|
|
101
|
-
expressId: number;
|
|
102
|
-
type: string;
|
|
103
|
-
name: string;
|
|
104
|
-
globalId?: string | null;
|
|
105
|
-
kind: 'spatial' | 'element';
|
|
106
|
-
hasChildren: boolean;
|
|
107
|
-
elementCount?: number;
|
|
108
|
-
elevation?: number | null;
|
|
109
|
-
}>>>({});
|
|
110
|
-
const [nativeExpanded, setNativeExpanded] = useState<Set<number>>(new Set());
|
|
111
|
-
const [nativeSearchResults, setNativeSearchResults] = useState<Array<{
|
|
112
|
-
expressId: number;
|
|
113
|
-
type: string;
|
|
114
|
-
name: string;
|
|
115
|
-
globalId?: string | null;
|
|
116
|
-
kind: 'spatial' | 'element';
|
|
117
|
-
hasChildren: boolean;
|
|
118
|
-
elementCount?: number;
|
|
119
|
-
elevation?: number | null;
|
|
120
|
-
}>>([]);
|
|
121
93
|
|
|
122
94
|
// Use extracted hook for tree data management
|
|
123
95
|
const {
|
|
@@ -221,29 +193,6 @@ export function HierarchyPanel() {
|
|
|
221
193
|
};
|
|
222
194
|
}, [isDragging]);
|
|
223
195
|
|
|
224
|
-
useEffect(() => {
|
|
225
|
-
if (!nativeLazyModel?.nativeMetadata) {
|
|
226
|
-
setNativeSearchResults([]);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
const query = searchQuery.trim();
|
|
230
|
-
if (!query) {
|
|
231
|
-
setNativeSearchResults([]);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
let cancelled = false;
|
|
235
|
-
void searchNativeMetadataEntities(nativeLazyModel.nativeMetadata.cacheKey, query, 200)
|
|
236
|
-
.then((results) => {
|
|
237
|
-
if (!cancelled) setNativeSearchResults(results);
|
|
238
|
-
})
|
|
239
|
-
.catch(() => {
|
|
240
|
-
if (!cancelled) setNativeSearchResults([]);
|
|
241
|
-
});
|
|
242
|
-
return () => {
|
|
243
|
-
cancelled = true;
|
|
244
|
-
};
|
|
245
|
-
}, [nativeLazyModel, searchQuery]);
|
|
246
|
-
|
|
247
196
|
// Toggle visibility for a node
|
|
248
197
|
const handleVisibilityToggle = useCallback((node: TreeNode) => {
|
|
249
198
|
const elements = getNodeElements(node);
|
|
@@ -322,6 +271,34 @@ export function HierarchyPanel() {
|
|
|
322
271
|
return;
|
|
323
272
|
}
|
|
324
273
|
|
|
274
|
+
// Material group nodes (Materials tab) - select the material entity for the
|
|
275
|
+
// totals panel + isolate the elements that use it.
|
|
276
|
+
if (node.type === 'material-group') {
|
|
277
|
+
const modelId = node.modelIds[0];
|
|
278
|
+
const materialExpressId = node.entityExpressId;
|
|
279
|
+
|
|
280
|
+
// Clear multi-selection first (setSelectedEntityIds([]) resets selectedEntityId)
|
|
281
|
+
setSelectedEntityIds([]);
|
|
282
|
+
|
|
283
|
+
if (materialExpressId !== undefined) {
|
|
284
|
+
if (modelId && modelId !== 'legacy') {
|
|
285
|
+
setSelectedEntityId(toGlobalId(modelId, materialExpressId));
|
|
286
|
+
setSelectedEntity({ modelId, expressId: materialExpressId });
|
|
287
|
+
setActiveModel(modelId);
|
|
288
|
+
} else {
|
|
289
|
+
setSelectedEntityId(materialExpressId);
|
|
290
|
+
setSelectedEntity({ modelId: 'legacy', expressId: materialExpressId });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Isolate the elements using this material
|
|
295
|
+
const elements = getNodeElements(node);
|
|
296
|
+
if (elements.length > 0) {
|
|
297
|
+
isolateEntities(elements);
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
325
302
|
// IFC type entity nodes (e.g. IfcWallType/W01) - select type entity for property panel + isolate instances
|
|
326
303
|
if (node.type === 'ifc-type') {
|
|
327
304
|
const modelId = node.modelIds[0];
|
|
@@ -478,14 +455,14 @@ export function HierarchyPanel() {
|
|
|
478
455
|
? selectedStoreys.has(node.expressIds[0])
|
|
479
456
|
: node.type === 'IfcSpace' || node.type === 'element'
|
|
480
457
|
? selectedEntityId === (node.globalIds[0] ?? node.expressIds[0])
|
|
481
|
-
: node.type === 'ifc-type'
|
|
458
|
+
: node.type === 'ifc-type' || node.type === 'material-group'
|
|
482
459
|
? (() => {
|
|
483
|
-
const
|
|
484
|
-
if (!
|
|
460
|
+
const entityExpressId = node.entityExpressId;
|
|
461
|
+
if (!entityExpressId) return false;
|
|
485
462
|
const mId = node.modelIds[0];
|
|
486
463
|
const gId = mId && mId !== 'legacy'
|
|
487
|
-
? toGlobalId(mId,
|
|
488
|
-
:
|
|
464
|
+
? toGlobalId(mId, entityExpressId)
|
|
465
|
+
: entityExpressId;
|
|
489
466
|
return selectedEntityId === gId;
|
|
490
467
|
})()
|
|
491
468
|
: false;
|
|
@@ -495,7 +472,7 @@ export function HierarchyPanel() {
|
|
|
495
472
|
if (node.type === 'element') {
|
|
496
473
|
nodeHidden = hiddenEntities.has(node.globalIds[0] ?? node.expressIds[0]);
|
|
497
474
|
} else if (node.type === 'IfcBuildingStorey' || node.type === 'IfcSpace' || node.type === 'unified-storey' ||
|
|
498
|
-
node.type === 'type-group' || node.type === 'ifc-type' ||
|
|
475
|
+
node.type === 'type-group' || node.type === 'ifc-type' || node.type === 'material-group' ||
|
|
499
476
|
(node.type === 'model-header' && node.id.startsWith('contrib-'))) {
|
|
500
477
|
const elements = getNodeElements(node);
|
|
501
478
|
nodeHidden = elements.length > 0 && elements.every(id => hiddenEntities.has(id));
|
|
@@ -531,7 +508,7 @@ export function HierarchyPanel() {
|
|
|
531
508
|
}
|
|
532
509
|
|
|
533
510
|
const singleModel = models.size === 1 ? Array.from(models.values())[0] : null;
|
|
534
|
-
if (!ifcDataStore && singleModel
|
|
511
|
+
if (!ifcDataStore && singleModel) {
|
|
535
512
|
const metadataState = singleModel.metadataLoadState;
|
|
536
513
|
const message = metadataState === 'error'
|
|
537
514
|
? (singleModel.loadError || 'Native metadata failed to load.')
|
|
@@ -552,128 +529,6 @@ export function HierarchyPanel() {
|
|
|
552
529
|
);
|
|
553
530
|
}
|
|
554
531
|
|
|
555
|
-
if (nativeLazyModel?.nativeMetadata) {
|
|
556
|
-
const nativeMetadata = nativeLazyModel.nativeMetadata;
|
|
557
|
-
const nativeSelectedGlobalId =
|
|
558
|
-
selectedEntity?.modelId === nativeLazyModel.id
|
|
559
|
-
? toGlobalId(nativeLazyModel.id, selectedEntity.expressId)
|
|
560
|
-
: null;
|
|
561
|
-
|
|
562
|
-
const selectNativeEntity = (expressId: number) => {
|
|
563
|
-
const globalId = toGlobalId(nativeLazyModel.id, expressId);
|
|
564
|
-
setSelectedEntityIds([]);
|
|
565
|
-
setSelectedEntityId(globalId);
|
|
566
|
-
setSelectedEntity({
|
|
567
|
-
modelId: nativeLazyModel.id,
|
|
568
|
-
expressId,
|
|
569
|
-
});
|
|
570
|
-
setActiveModel(nativeLazyModel.id);
|
|
571
|
-
};
|
|
572
|
-
|
|
573
|
-
const toggleNativeNode = async (expressId: number) => {
|
|
574
|
-
setNativeExpanded((prev) => {
|
|
575
|
-
const next = new Set(prev);
|
|
576
|
-
if (next.has(expressId)) {
|
|
577
|
-
next.delete(expressId);
|
|
578
|
-
} else {
|
|
579
|
-
next.add(expressId);
|
|
580
|
-
}
|
|
581
|
-
return next;
|
|
582
|
-
});
|
|
583
|
-
if (nativeChildren[expressId]) return;
|
|
584
|
-
try {
|
|
585
|
-
const children = await getNativeMetadataChildren(nativeMetadata.cacheKey, expressId);
|
|
586
|
-
setNativeChildren((prev) => ({ ...prev, [expressId]: children }));
|
|
587
|
-
} catch {
|
|
588
|
-
setNativeChildren((prev) => ({ ...prev, [expressId]: [] }));
|
|
589
|
-
}
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
const renderNativeSummary = (
|
|
593
|
-
summary: {
|
|
594
|
-
expressId: number;
|
|
595
|
-
type: string;
|
|
596
|
-
name: string;
|
|
597
|
-
kind: 'spatial' | 'element';
|
|
598
|
-
hasChildren: boolean;
|
|
599
|
-
elementCount?: number;
|
|
600
|
-
},
|
|
601
|
-
depth: number,
|
|
602
|
-
): ReactElement => {
|
|
603
|
-
const expanded = nativeExpanded.has(summary.expressId);
|
|
604
|
-
return (
|
|
605
|
-
<div key={`${summary.kind}-${summary.expressId}`}>
|
|
606
|
-
<button
|
|
607
|
-
type="button"
|
|
608
|
-
className={cn(
|
|
609
|
-
'w-full flex items-center gap-2 px-3 py-2 text-left border-b border-zinc-100 dark:border-zinc-900 hover:bg-zinc-50 dark:hover:bg-zinc-950',
|
|
610
|
-
nativeSelectedGlobalId === toGlobalId(nativeLazyModel.id, summary.expressId) && 'bg-primary/10 text-primary'
|
|
611
|
-
)}
|
|
612
|
-
style={{ paddingLeft: `${12 + depth * 16}px` }}
|
|
613
|
-
onClick={() => selectNativeEntity(summary.expressId)}
|
|
614
|
-
>
|
|
615
|
-
{summary.hasChildren ? (
|
|
616
|
-
<span
|
|
617
|
-
className="w-4 text-center text-xs text-zinc-500"
|
|
618
|
-
onClick={(event) => {
|
|
619
|
-
event.stopPropagation();
|
|
620
|
-
void toggleNativeNode(summary.expressId);
|
|
621
|
-
}}
|
|
622
|
-
>
|
|
623
|
-
{expanded ? 'v' : '>'}
|
|
624
|
-
</span>
|
|
625
|
-
) : (
|
|
626
|
-
<span className="w-4" />
|
|
627
|
-
)}
|
|
628
|
-
<span className="truncate flex-1 text-sm">{summary.name || `${summary.type} #${summary.expressId}`}</span>
|
|
629
|
-
<span className="text-[10px] uppercase tracking-wide text-zinc-500">{summary.type}</span>
|
|
630
|
-
{typeof summary.elementCount === 'number' && summary.elementCount > 0 && (
|
|
631
|
-
<span className="text-[10px] text-zinc-400">{summary.elementCount}</span>
|
|
632
|
-
)}
|
|
633
|
-
</button>
|
|
634
|
-
{expanded && (nativeChildren[summary.expressId] ?? []).map((child) => renderNativeSummary(child, depth + 1))}
|
|
635
|
-
</div>
|
|
636
|
-
);
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
return (
|
|
640
|
-
<div className="h-full flex flex-col border-r-2 border-zinc-200 dark:border-zinc-800 bg-white dark:bg-black">
|
|
641
|
-
<div className="p-3 border-b-2 border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-black">
|
|
642
|
-
<Input
|
|
643
|
-
placeholder="Search..."
|
|
644
|
-
value={searchQuery}
|
|
645
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
646
|
-
leftIcon={<Search className="h-4 w-4" />}
|
|
647
|
-
className="h-9 text-sm rounded-none border-2 border-zinc-200 dark:border-zinc-800 focus:border-primary focus:ring-0 bg-white dark:bg-zinc-950 text-zinc-900 dark:text-zinc-100 placeholder:text-zinc-400 dark:placeholder:text-zinc-600"
|
|
648
|
-
/>
|
|
649
|
-
</div>
|
|
650
|
-
<SectionHeader
|
|
651
|
-
icon={Building2}
|
|
652
|
-
title={searchQuery.trim() ? 'Search Results' : 'Hierarchy'}
|
|
653
|
-
count={searchQuery.trim() ? nativeSearchResults.length : 1}
|
|
654
|
-
/>
|
|
655
|
-
<div className="flex-1 overflow-auto scrollbar-thin bg-white dark:bg-black">
|
|
656
|
-
{searchQuery.trim()
|
|
657
|
-
? nativeSearchResults.map((result) => renderNativeSummary(result, 0))
|
|
658
|
-
: nativeMetadata.spatialTree
|
|
659
|
-
? renderNativeSummary(nativeMetadata.spatialTree, 0)
|
|
660
|
-
: (
|
|
661
|
-
<div className="p-4 text-xs text-zinc-500">
|
|
662
|
-
{nativeLazyModel.metadataLoadState === 'error'
|
|
663
|
-
? (nativeLazyModel.loadError || 'Native spatial metadata is unavailable for this model.')
|
|
664
|
-
: nativeLazyModel.metadataLoadState === 'bootstrapping'
|
|
665
|
-
? 'Native spatial metadata is still loading.'
|
|
666
|
-
: 'Native spatial metadata tree is unavailable for this model.'}
|
|
667
|
-
</div>
|
|
668
|
-
)}
|
|
669
|
-
</div>
|
|
670
|
-
<div className="p-2 border-t-2 border-zinc-200 dark:border-zinc-800 text-[10px] uppercase tracking-wide text-zinc-500 dark:text-zinc-500 text-center bg-zinc-50 dark:bg-black font-mono">
|
|
671
|
-
On-demand desktop metadata
|
|
672
|
-
</div>
|
|
673
|
-
</div>
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
532
|
// Helper to render a node via the extracted HierarchyNode component
|
|
678
533
|
const renderNode = (node: TreeNode, virtualRow: { index: number; size: number; start: number }) => {
|
|
679
534
|
const { isSelected, nodeHidden, modelVisible } = computeNodeState(node);
|
|
@@ -732,6 +587,16 @@ export function HierarchyPanel() {
|
|
|
732
587
|
<FileBox className="h-3 w-3 shrink-0 panel-compact-icon" />
|
|
733
588
|
<span className="panel-compact-text">Type</span>
|
|
734
589
|
</Button>
|
|
590
|
+
<Button
|
|
591
|
+
variant={groupingMode === 'material' ? 'default' : 'outline'}
|
|
592
|
+
size="sm"
|
|
593
|
+
className="h-6 text-[10px] flex-1 min-w-0 rounded-none uppercase tracking-wider"
|
|
594
|
+
onClick={() => setGroupingMode('material')}
|
|
595
|
+
title="Materials"
|
|
596
|
+
>
|
|
597
|
+
<Palette className="h-3 w-3 shrink-0 panel-compact-icon" />
|
|
598
|
+
<span className="panel-compact-text">Material</span>
|
|
599
|
+
</Button>
|
|
735
600
|
</div>
|
|
736
601
|
);
|
|
737
602
|
|
|
@@ -871,7 +736,7 @@ export function HierarchyPanel() {
|
|
|
871
736
|
</div>
|
|
872
737
|
|
|
873
738
|
{/* Section Header */}
|
|
874
|
-
<SectionHeader icon={groupingMode === 'spatial' ? Building2 : groupingMode === 'type' ? Layers : FileBox} title={groupingMode === 'spatial' ? 'Hierarchy' : groupingMode === 'type' ? 'By Class' : 'By Type'} count={filteredNodes.length} />
|
|
739
|
+
<SectionHeader icon={groupingMode === 'spatial' ? Building2 : groupingMode === 'type' ? Layers : groupingMode === 'material' ? Palette : FileBox} title={groupingMode === 'spatial' ? 'Hierarchy' : groupingMode === 'type' ? 'By Class' : groupingMode === 'material' ? 'By Material' : 'By Type'} count={filteredNodes.length} />
|
|
875
740
|
|
|
876
741
|
{/* Tree */}
|
|
877
742
|
<div ref={parentRef} className="flex-1 overflow-auto scrollbar-thin bg-white dark:bg-black">
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - Multi-language support (EN/DE/FR)
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import React, { useCallback,
|
|
18
|
+
import React, { useCallback, useState, useMemo, useRef } from 'react';
|
|
19
19
|
import {
|
|
20
20
|
X,
|
|
21
21
|
Upload,
|
|
@@ -75,7 +75,6 @@ import { cn } from '@/lib/utils';
|
|
|
75
75
|
import { IDSAuditSummary } from './IDSAuditSummary';
|
|
76
76
|
import { IDSExportDialog } from './IDSExportDialog';
|
|
77
77
|
import type { IDSBCFExportSettings, IDSExportProgress } from './IDSExportDialog';
|
|
78
|
-
import { claimNextDesktopPanelAction, subscribeDesktopPanelActions } from '@/services/desktop-panel-actions';
|
|
79
78
|
|
|
80
79
|
// ============================================================================
|
|
81
80
|
// Types
|
|
@@ -513,30 +512,6 @@ export function IDSPanel({ onClose }: IDSPanelProps) {
|
|
|
513
512
|
fileInputRef.current?.click();
|
|
514
513
|
}, [loadIdsFromDialog]);
|
|
515
514
|
|
|
516
|
-
const handleDesktopRunValidation = useCallback(async () => {
|
|
517
|
-
if (!document) {
|
|
518
|
-
const loaded = await loadIdsFromDialog();
|
|
519
|
-
if (!loaded) {
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
await runValidation();
|
|
524
|
-
}, [document, loadIdsFromDialog, runValidation]);
|
|
525
|
-
|
|
526
|
-
useEffect(() => {
|
|
527
|
-
const drainDesktopActions = () => {
|
|
528
|
-
if (claimNextDesktopPanelAction('ids-open')) {
|
|
529
|
-
void loadIdsFromDialog();
|
|
530
|
-
}
|
|
531
|
-
if (claimNextDesktopPanelAction('ids-run-validation')) {
|
|
532
|
-
void handleDesktopRunValidation();
|
|
533
|
-
}
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
drainDesktopActions();
|
|
537
|
-
return subscribeDesktopPanelActions(drainDesktopActions);
|
|
538
|
-
}, [handleDesktopRunValidation, loadIdsFromDialog]);
|
|
539
|
-
|
|
540
515
|
// Handle entity click
|
|
541
516
|
const handleEntityClick = useCallback((modelId: string, expressId: number) => {
|
|
542
517
|
selectEntity(modelId, expressId);
|