@ifc-lite/viewer 1.28.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 +34 -41
- package/CHANGELOG.md +10 -0
- package/dist/assets/{basketViewActivator-BNRDNuUJ.js → basketViewActivator-Ce38DhXd.js} +7 -7
- package/dist/assets/{bcf-DCwCuP7n.js → bcf-Cv_O3JfD.js} +1 -1
- package/dist/assets/{deflate-DNGgs8Ur.js → deflate-HbyMq59o.js} +1 -1
- package/dist/assets/drawing-2d-DW98umlt.js +257 -0
- package/dist/assets/{exporters-B9v81gi9.js → exporters-BuD3XRzB.js} +463 -416
- package/dist/assets/geometry.worker-TH3fCCoY.js +1 -0
- package/dist/assets/{geotiff-D-YCLS4g.js → geotiff-B2HA8Bwm.js} +10 -10
- package/dist/assets/{ids-CCpq-5d3.js → ids-DYUFMd5f.js} +4 -4
- package/dist/assets/{ifc-lite_bg-DbgS5EUA.wasm → ifc-lite_bg-BEA5DLmg.wasm} +0 -0
- package/dist/assets/index-E9wB0zWt.css +1 -0
- package/dist/assets/{index-Bgb3_Pu_.js → index-n5O1QJMM.js} +36808 -39415
- package/dist/assets/{index.es-CWfqZyyr.js → index.es-BKVIpZgL.js} +8 -8
- package/dist/assets/{jpeg-DGOAeUqU.js → jpeg-C7hjKjPX.js} +1 -1
- package/dist/assets/{jspdf.es.min-XPLU2Wkq.js → jspdf.es.min-oWlFc42Y.js} +4 -4
- package/dist/assets/{lerc-1PMSCHwX.js → lerc-BfIOGhQz.js} +1 -1
- package/dist/assets/{lzw-C65U9lNM.js → lzw-B0jRuuW5.js} +1 -1
- package/dist/assets/{native-bridge-XxXos6yI.js → native-bridge-DpB-dtEn.js} +5 -2
- package/dist/assets/{packbits-BdMWXC3m.js → packbits-DVvBTC39.js} +1 -1
- package/dist/assets/{parser.worker-Ddwo3_06.js → parser.worker-BDsWQ6rc.js} +1 -1
- package/dist/assets/{pdf-CRwaZf3s.js → pdf-dVIqI5ac.js} +9 -9
- package/dist/assets/raw-C0ZJYGmN.js +1 -0
- package/dist/assets/{sandbox-0sDo3g3m.js → sandbox-qpJlrNN0.js} +8 -8
- package/dist/assets/{server-client-cTCJ-853.js → server-client-DVZ2huNS.js} +1 -1
- package/dist/assets/{webimage-BtakWX7W.js → webimage-B394g0Tw.js} +1 -1
- package/dist/assets/{xlsx-B1YOg2QB.js → xlsx-D-oHO76J.js} +7 -7
- package/dist/assets/{zstd-CmwsbxmM.js → zstd-Bf38MwV2.js} +1 -1
- package/dist/index.html +8 -8
- package/package.json +5 -5
- package/src/App.tsx +1 -3
- package/src/components/viewer/BCFPanel.tsx +1 -16
- package/src/components/viewer/ChatPanel.tsx +11 -46
- package/src/components/viewer/HierarchyPanel.tsx +2 -176
- package/src/components/viewer/IDSPanel.tsx +1 -26
- package/src/components/viewer/MainToolbar.tsx +75 -185
- package/src/components/viewer/MobileToolbar.tsx +1 -9
- package/src/components/viewer/PropertiesPanel.tsx +28 -126
- package/src/components/viewer/ScriptPanel.tsx +8 -34
- package/src/components/viewer/Section2DPanel.tsx +32 -1
- package/src/components/viewer/ViewerLayout.tsx +0 -2
- package/src/components/viewer/ViewportContainer.tsx +24 -42
- package/src/components/viewer/ViewportOverlays.tsx +1 -4
- package/src/components/viewer/useGeometryStreaming.ts +0 -2
- package/src/hooks/ingest/federationAlign.ts +7 -0
- package/src/hooks/useDrawingGeneration.ts +211 -13
- package/src/hooks/useIfcCache.ts +94 -41
- package/src/hooks/useIfcFederation.ts +2 -3
- package/src/hooks/useIfcLoader.ts +10 -1051
- 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/index.ts +3 -5
- package/src/store/slices/drawing2DSlice.ts +8 -0
- package/src/store/slices/visibilitySlice.ts +22 -1
- package/src/store/types.ts +1 -71
- package/src/utils/ifcConfig.ts +0 -12
- package/vite.config.ts +6 -3
- package/DESKTOP_CONTRACT_VERSION +0 -1
- package/dist/assets/drawing-2d-D0dDf6Lh.js +0 -257
- package/dist/assets/event-B0kAzHa-.js +0 -1
- package/dist/assets/geometry.worker-Bpa3115V.js +0 -1
- package/dist/assets/index-BtbXFKsX.css +0 -1
- package/dist/assets/raw-CJgQdyuZ.js +0 -1
- 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/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 -359
- 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
|
@@ -44,13 +44,14 @@ import {
|
|
|
44
44
|
CalendarClock,
|
|
45
45
|
Globe2,
|
|
46
46
|
Move,
|
|
47
|
-
Settings,
|
|
48
47
|
PenLine,
|
|
49
48
|
Layers3,
|
|
50
49
|
SquareStack,
|
|
51
50
|
ChevronsUpDown,
|
|
52
51
|
Undo2,
|
|
53
52
|
Redo2,
|
|
53
|
+
Boxes,
|
|
54
|
+
Shapes,
|
|
54
55
|
} from 'lucide-react';
|
|
55
56
|
import { Button } from '@/components/ui/button';
|
|
56
57
|
import { Switch } from '@/components/ui/switch';
|
|
@@ -83,16 +84,10 @@ import { DataConnector } from './DataConnector';
|
|
|
83
84
|
import { ExportChangesButton } from './ExportChangesButton';
|
|
84
85
|
import { SearchInline } from './SearchInline';
|
|
85
86
|
import { useFloorplanView } from '@/hooks/useFloorplanView';
|
|
86
|
-
import { buildDesktopUpgradeUrl, hasDesktopFeatureAccess, type DesktopFeature } from '@/lib/desktop-product';
|
|
87
87
|
import { recordRecentFiles, cacheFileBlobs } from '@/lib/recent-files';
|
|
88
88
|
import { ThemeSwitch } from './ThemeSwitch';
|
|
89
89
|
import { ExtensionToolbarSlot } from '@/components/extensions/ExtensionToolbarSlot';
|
|
90
90
|
import { toast } from '@/components/ui/toast';
|
|
91
|
-
import { navigateToPath } from '@/services/app-navigation';
|
|
92
|
-
import { getStartupHarnessRequest, setActiveHarnessRequest, tryClaimStartupHarnessRequest } from '@/services/desktop-harness';
|
|
93
|
-
import { logToDesktopTerminal } from '@/services/desktop-logger';
|
|
94
|
-
import { openIfcFileDialog, type NativeFileHandle } from '@/services/file-dialog';
|
|
95
|
-
import { isTauri } from '@/lib/platform';
|
|
96
91
|
import {
|
|
97
92
|
closeActiveAnalysisExtension,
|
|
98
93
|
getAnalysisExtensionsSnapshot,
|
|
@@ -103,10 +98,6 @@ import {
|
|
|
103
98
|
type Tool = 'select' | 'walk' | 'measure' | 'section' | 'annotate' | 'addElement' | 'split';
|
|
104
99
|
type WorkspacePanel = 'script' | 'list' | 'bcf' | 'ids' | 'lens' | 'addElement' | string;
|
|
105
100
|
|
|
106
|
-
function isNativeFileHandle(file: File | NativeFileHandle): file is NativeFileHandle {
|
|
107
|
-
return typeof (file as NativeFileHandle).path === 'string';
|
|
108
|
-
}
|
|
109
|
-
|
|
110
101
|
// #region FIX: Move ToolButton OUTSIDE MainToolbar to prevent recreation on every render
|
|
111
102
|
// This fixes Radix UI Tooltip's asChild prop becoming stale during re-renders
|
|
112
103
|
interface ToolButtonProps {
|
|
@@ -438,11 +429,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
438
429
|
// Listen for programmatic file-load requests (from command palette recent files)
|
|
439
430
|
useEffect(() => {
|
|
440
431
|
const handler = (e: Event) => {
|
|
441
|
-
const file = (e as CustomEvent<File
|
|
432
|
+
const file = (e as CustomEvent<File>).detail;
|
|
442
433
|
if (file) {
|
|
443
|
-
recordRecentFiles([
|
|
444
|
-
? { name: file.name, size: file.size, path: file.path, modifiedMs: file.modifiedMs ?? null }
|
|
445
|
-
: { name: file.name, size: file.size }]);
|
|
434
|
+
recordRecentFiles([{ name: file.name, size: file.size }]);
|
|
446
435
|
void loadFile(file);
|
|
447
436
|
}
|
|
448
437
|
};
|
|
@@ -450,78 +439,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
450
439
|
return () => window.removeEventListener('ifc-lite:load-file', handler);
|
|
451
440
|
}, [loadFile]);
|
|
452
441
|
|
|
453
|
-
useEffect(() => {
|
|
454
|
-
let cancelled = false;
|
|
455
|
-
const sleep = (ms: number) => new Promise((resolve) => globalThis.setTimeout(resolve, ms));
|
|
456
|
-
|
|
457
|
-
const waitForViewerToSettle = async (label: string) => {
|
|
458
|
-
const timeoutMs = 120_000;
|
|
459
|
-
const pollMs = 100;
|
|
460
|
-
const start = performance.now();
|
|
461
|
-
while (!cancelled) {
|
|
462
|
-
const state = useViewerStore.getState();
|
|
463
|
-
const meshCount = state.geometryResult?.meshes.length ?? 0;
|
|
464
|
-
if (!state.loading && meshCount > 0) {
|
|
465
|
-
void logToDesktopTerminal(
|
|
466
|
-
'info',
|
|
467
|
-
`[DesktopHarness] ${label} settled: loading=${state.loading} meshes=${meshCount} progress=${state.progress?.phase ?? 'none'}`
|
|
468
|
-
);
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
if (performance.now() - start >= timeoutMs) {
|
|
472
|
-
throw new Error(`[DesktopHarness] Timed out waiting for ${label} to settle`);
|
|
473
|
-
}
|
|
474
|
-
await sleep(pollMs);
|
|
475
|
-
}
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
void (async () => {
|
|
479
|
-
void logToDesktopTerminal('info', '[DesktopHarness] MainToolbar startup harness effect running');
|
|
480
|
-
const request = await getStartupHarnessRequest();
|
|
481
|
-
if (!request || cancelled) {
|
|
482
|
-
void logToDesktopTerminal(
|
|
483
|
-
'info',
|
|
484
|
-
`[DesktopHarness] No startup harness request available (cancelled=${cancelled})`
|
|
485
|
-
);
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
if (!tryClaimStartupHarnessRequest(request)) {
|
|
489
|
-
void logToDesktopTerminal('info', `[DesktopHarness] Startup harness request already claimed for ${request.file.path}`);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
void logToDesktopTerminal('info', `[DesktopHarness] Claimed startup harness request for ${request.file.path}`);
|
|
493
|
-
console.log(`[DesktopHarness] Auto-loading startup file: ${request.file.path}`);
|
|
494
|
-
if (!request.replaceFile) {
|
|
495
|
-
void logToDesktopTerminal('info', `[DesktopHarness] Calling loadFile for ${request.file.path}`);
|
|
496
|
-
await loadFile(request.file);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
void logToDesktopTerminal(
|
|
501
|
-
'info',
|
|
502
|
-
`[DesktopHarness] Running replacement sequence first=${request.file.path} second=${request.replaceFile.path}`
|
|
503
|
-
);
|
|
504
|
-
setActiveHarnessRequest(null);
|
|
505
|
-
await loadFile(request.file);
|
|
506
|
-
await waitForViewerToSettle(`first load ${request.file.name}`);
|
|
507
|
-
if (cancelled) {
|
|
508
|
-
return;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
setActiveHarnessRequest({
|
|
512
|
-
...request,
|
|
513
|
-
file: request.replaceFile,
|
|
514
|
-
replaceFile: undefined,
|
|
515
|
-
});
|
|
516
|
-
void logToDesktopTerminal('info', `[DesktopHarness] Calling replacement loadFile for ${request.replaceFile.path}`);
|
|
517
|
-
await loadFile(request.replaceFile);
|
|
518
|
-
})();
|
|
519
|
-
|
|
520
|
-
return () => {
|
|
521
|
-
cancelled = true;
|
|
522
|
-
};
|
|
523
|
-
}, [loadFile]);
|
|
524
|
-
|
|
525
442
|
// Floorplan view
|
|
526
443
|
const { availableStoreys, activateFloorplan } = useFloorplanView();
|
|
527
444
|
|
|
@@ -541,6 +458,10 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
541
458
|
const typeVisibility = useViewerStore((state) => state.typeVisibility);
|
|
542
459
|
const toggleTypeVisibility = useViewerStore((state) => state.toggleTypeVisibility);
|
|
543
460
|
const resetTypeVisibility = useViewerStore((state) => state.resetTypeVisibility);
|
|
461
|
+
// #957 follow-up: Model/Types 3D view switch — 'model' shows placed
|
|
462
|
+
// occurrences (default), 'types' shows the type-library shapes.
|
|
463
|
+
const typeViewMode = useViewerStore((state) => state.typeViewMode);
|
|
464
|
+
const setTypeViewMode = useViewerStore((state) => state.setTypeViewMode);
|
|
544
465
|
// How many of the five class toggles are on — surfaced in the menu
|
|
545
466
|
// header so the user sees scene state at a glance.
|
|
546
467
|
const visibleClassCount = [
|
|
@@ -591,7 +512,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
591
512
|
const cesiumPlacementEditMode = useViewerStore((state) => state.cesiumPlacementEditMode);
|
|
592
513
|
const setCesiumPlacementEditMode = useViewerStore((state) => state.setCesiumPlacementEditMode);
|
|
593
514
|
const storeModels = useViewerStore((state) => state.models);
|
|
594
|
-
const desktopEntitlement = useViewerStore((state) => state.desktopEntitlement);
|
|
595
515
|
const analysisExtensionState = useSyncExternalStore(
|
|
596
516
|
subscribeAnalysisExtensions,
|
|
597
517
|
getAnalysisExtensionsSnapshot,
|
|
@@ -609,7 +529,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
609
529
|
() => analysisExtensionState.extensions.filter((extension) => (extension.placement ?? 'right') === 'bottom'),
|
|
610
530
|
[analysisExtensionState.extensions],
|
|
611
531
|
);
|
|
612
|
-
const desktopShell = isTauri();
|
|
613
532
|
|
|
614
533
|
// NOTE: The Class Visibility dropdown used to gate each toggle on whether
|
|
615
534
|
// the loaded model actually contained that class (scanning meshes for
|
|
@@ -725,19 +644,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
725
644
|
goHomeFromStore();
|
|
726
645
|
}, []);
|
|
727
646
|
|
|
728
|
-
const promptDesktopUpgrade = useCallback((featureLabel: string) => {
|
|
729
|
-
toast.info(`${featureLabel} is available with Desktop Pro`);
|
|
730
|
-
navigateToPath(buildDesktopUpgradeUrl());
|
|
731
|
-
}, []);
|
|
732
|
-
|
|
733
|
-
const requireDesktopFeature = useCallback((feature: DesktopFeature, label: string) => {
|
|
734
|
-
if (hasDesktopFeatureAccess(desktopEntitlement, feature)) {
|
|
735
|
-
return true;
|
|
736
|
-
}
|
|
737
|
-
promptDesktopUpgrade(label);
|
|
738
|
-
return false;
|
|
739
|
-
}, [desktopEntitlement, promptDesktopUpgrade]);
|
|
740
|
-
|
|
741
647
|
const handleToggleBottomPanel = useCallback((panel: 'script' | 'list' | 'gantt') => {
|
|
742
648
|
if (activeAnalysisExtension?.placement === 'bottom') {
|
|
743
649
|
closeActiveAnalysisExtension();
|
|
@@ -768,15 +674,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
768
674
|
if (activeAnalysisExtension?.placement !== 'bottom') {
|
|
769
675
|
closeActiveAnalysisExtension();
|
|
770
676
|
}
|
|
771
|
-
if (panel === 'bcf' && !requireDesktopFeature('bcf_issue_management', 'BCF issue management')) {
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
if (panel === 'ids' && !requireDesktopFeature('ids_validation', 'IDS validation')) {
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
if (panel === 'extensions' && !requireDesktopFeature('extensions', 'Extensions')) {
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
677
|
|
|
781
678
|
const nextBcfVisible = panel === 'bcf' ? !bcfPanelVisible : false;
|
|
782
679
|
const nextIdsVisible = panel === 'ids' ? !idsPanelVisible : false;
|
|
@@ -812,7 +709,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
812
709
|
extensionsPanelVisible,
|
|
813
710
|
idsPanelVisible,
|
|
814
711
|
lensPanelVisible,
|
|
815
|
-
requireDesktopFeature,
|
|
816
712
|
setActiveTool,
|
|
817
713
|
setBcfPanelVisible,
|
|
818
714
|
setClashPanelVisible,
|
|
@@ -920,7 +816,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
920
816
|
}, [activeAnalysisExtension?.label, activeWorkspacePanels]);
|
|
921
817
|
|
|
922
818
|
const handleScreenshot = useCallback(() => {
|
|
923
|
-
if (!requireDesktopFeature('exports', 'Exports')) return;
|
|
924
819
|
const canvas = document.querySelector('canvas');
|
|
925
820
|
if (!canvas) return;
|
|
926
821
|
try {
|
|
@@ -934,10 +829,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
934
829
|
console.error('Screenshot failed:', err);
|
|
935
830
|
toast.error('Screenshot failed');
|
|
936
831
|
}
|
|
937
|
-
}, [
|
|
832
|
+
}, []);
|
|
938
833
|
|
|
939
834
|
const handleExportCSV = useCallback((type: 'entities' | 'properties' | 'quantities' | 'spatial') => {
|
|
940
|
-
if (!requireDesktopFeature('exports', 'Exports')) return;
|
|
941
835
|
if (!ifcDataStore) return;
|
|
942
836
|
try {
|
|
943
837
|
const exporter = new CSVExporter(ifcDataStore);
|
|
@@ -975,10 +869,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
975
869
|
console.error('CSV export failed:', err);
|
|
976
870
|
toast.error(`CSV export failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
977
871
|
}
|
|
978
|
-
}, [ifcDataStore
|
|
872
|
+
}, [ifcDataStore]);
|
|
979
873
|
|
|
980
874
|
const handleExportJSON = useCallback(() => {
|
|
981
|
-
if (!requireDesktopFeature('exports', 'Exports')) return;
|
|
982
875
|
if (!ifcDataStore) return;
|
|
983
876
|
try {
|
|
984
877
|
const entities: Record<string, unknown>[] = [];
|
|
@@ -1006,7 +899,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1006
899
|
console.error('JSON export failed:', err);
|
|
1007
900
|
toast.error(`JSON export failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
1008
901
|
}
|
|
1009
|
-
}, [ifcDataStore
|
|
902
|
+
}, [ifcDataStore]);
|
|
1010
903
|
|
|
1011
904
|
return (
|
|
1012
905
|
<div className="flex items-center gap-1 px-2 h-12 border-b bg-white dark:bg-black border-zinc-200 dark:border-zinc-800 relative z-50">
|
|
@@ -1034,25 +927,9 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1034
927
|
<Button
|
|
1035
928
|
variant="ghost"
|
|
1036
929
|
size="icon-sm"
|
|
1037
|
-
onClick={
|
|
930
|
+
onClick={(e) => {
|
|
1038
931
|
// Blur button to close tooltip before opening file dialog
|
|
1039
932
|
(e.currentTarget as HTMLButtonElement).blur();
|
|
1040
|
-
|
|
1041
|
-
void logToDesktopTerminal('info', '[MainToolbar] Open file button clicked');
|
|
1042
|
-
const file = await openIfcFileDialog();
|
|
1043
|
-
if (file) {
|
|
1044
|
-
void logToDesktopTerminal('info', `[MainToolbar] Native dialog selected ${file.path}`);
|
|
1045
|
-
recordRecentFiles([{
|
|
1046
|
-
name: file.name,
|
|
1047
|
-
size: file.size,
|
|
1048
|
-
path: file.path,
|
|
1049
|
-
modifiedMs: file.modifiedMs ?? null,
|
|
1050
|
-
}]);
|
|
1051
|
-
void loadFile(file);
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
void logToDesktopTerminal('info', '[MainToolbar] Falling back to browser file input');
|
|
1056
933
|
fileInputRef.current?.click();
|
|
1057
934
|
}}
|
|
1058
935
|
disabled={loading}
|
|
@@ -1095,37 +972,23 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1095
972
|
</Button>
|
|
1096
973
|
</DropdownMenuTrigger>
|
|
1097
974
|
<DropdownMenuContent>
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
<
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
/>
|
|
1107
|
-
) : (
|
|
1108
|
-
<DropdownMenuItem onClick={() => promptDesktopUpgrade('Exports')}>
|
|
1109
|
-
<FileText className="h-4 w-4 mr-2" />
|
|
1110
|
-
Export IFC (with changes)
|
|
1111
|
-
</DropdownMenuItem>
|
|
1112
|
-
)}
|
|
975
|
+
<ExportDialog
|
|
976
|
+
trigger={
|
|
977
|
+
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
|
978
|
+
<FileText className="h-4 w-4 mr-2" />
|
|
979
|
+
Export IFC (with changes)
|
|
980
|
+
</DropdownMenuItem>
|
|
981
|
+
}
|
|
982
|
+
/>
|
|
1113
983
|
<DropdownMenuSeparator />
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
<
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
/>
|
|
1123
|
-
) : (
|
|
1124
|
-
<DropdownMenuItem onClick={() => promptDesktopUpgrade('Exports')}>
|
|
1125
|
-
<Download className="h-4 w-4 mr-2" />
|
|
1126
|
-
Export GLB (3D Model)
|
|
1127
|
-
</DropdownMenuItem>
|
|
1128
|
-
)}
|
|
984
|
+
<GLBExportDialog
|
|
985
|
+
trigger={
|
|
986
|
+
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
|
987
|
+
<Download className="h-4 w-4 mr-2" />
|
|
988
|
+
Export GLB (3D Model)
|
|
989
|
+
</DropdownMenuItem>
|
|
990
|
+
}
|
|
991
|
+
/>
|
|
1129
992
|
<DropdownMenuSeparator />
|
|
1130
993
|
<DropdownMenuSub>
|
|
1131
994
|
<DropdownMenuSubTrigger disabled={!ifcDataStore}>
|
|
@@ -1197,9 +1060,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1197
1060
|
</DropdownMenu>
|
|
1198
1061
|
|
|
1199
1062
|
{/* Export Changes Button - shows when there are pending mutations */}
|
|
1200
|
-
|
|
1201
|
-
<ExportChangesButton />
|
|
1202
|
-
) : null}
|
|
1063
|
+
<ExportChangesButton />
|
|
1203
1064
|
|
|
1204
1065
|
{/* ── Panels ── */}
|
|
1205
1066
|
<DropdownMenu>
|
|
@@ -1549,6 +1410,51 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1549
1410
|
model lacks is a no-op.
|
|
1550
1411
|
*/}
|
|
1551
1412
|
<DropdownMenuContent align="start" className="w-[300px] p-1.5">
|
|
1413
|
+
{/* Model / Types 3D view switch (#957 follow-up). A type carries a
|
|
1414
|
+
RepresentationMap whose shape is drawn at its MappingOrigin; "Types"
|
|
1415
|
+
shows that type library, "Model" shows the placed occurrences. The
|
|
1416
|
+
two are mutually exclusive — toggling re-filters the cached mesh set
|
|
1417
|
+
instantly (no reload). */}
|
|
1418
|
+
<div className="px-1.5 pb-1 pt-0.5">
|
|
1419
|
+
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
1420
|
+
3D View
|
|
1421
|
+
</span>
|
|
1422
|
+
</div>
|
|
1423
|
+
<div className="flex gap-1 px-1.5 pb-1.5" role="radiogroup" aria-label="3D view mode">
|
|
1424
|
+
<button
|
|
1425
|
+
type="button"
|
|
1426
|
+
role="radio"
|
|
1427
|
+
aria-checked={typeViewMode === 'model'}
|
|
1428
|
+
onClick={() => setTypeViewMode('model')}
|
|
1429
|
+
className={cn(
|
|
1430
|
+
'flex flex-1 items-center justify-center gap-1.5 rounded-md border px-2 py-1.5 text-xs font-medium transition-colors',
|
|
1431
|
+
typeViewMode === 'model'
|
|
1432
|
+
? 'border-primary/40 bg-primary/10 text-foreground'
|
|
1433
|
+
: 'border-transparent text-muted-foreground hover:bg-muted/50',
|
|
1434
|
+
)}
|
|
1435
|
+
>
|
|
1436
|
+
<Boxes className="h-3.5 w-3.5 shrink-0" />
|
|
1437
|
+
Model
|
|
1438
|
+
</button>
|
|
1439
|
+
<button
|
|
1440
|
+
type="button"
|
|
1441
|
+
role="radio"
|
|
1442
|
+
aria-checked={typeViewMode === 'types'}
|
|
1443
|
+
onClick={() => setTypeViewMode('types')}
|
|
1444
|
+
className={cn(
|
|
1445
|
+
'flex flex-1 items-center justify-center gap-1.5 rounded-md border px-2 py-1.5 text-xs font-medium transition-colors',
|
|
1446
|
+
typeViewMode === 'types'
|
|
1447
|
+
? 'border-primary/40 bg-primary/10 text-foreground'
|
|
1448
|
+
: 'border-transparent text-muted-foreground hover:bg-muted/50',
|
|
1449
|
+
)}
|
|
1450
|
+
>
|
|
1451
|
+
<Shapes className="h-3.5 w-3.5 shrink-0" />
|
|
1452
|
+
Types
|
|
1453
|
+
</button>
|
|
1454
|
+
</div>
|
|
1455
|
+
|
|
1456
|
+
<DropdownMenuSeparator className="my-1" />
|
|
1457
|
+
|
|
1552
1458
|
<div className="flex items-center justify-between gap-2 px-1.5 pb-1 pt-0.5">
|
|
1553
1459
|
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
1554
1460
|
Visibility
|
|
@@ -1638,7 +1544,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1638
1544
|
appears beside it (its amber tint signals a modal pose whose
|
|
1639
1545
|
exit affordance must stay visible).
|
|
1640
1546
|
*/}
|
|
1641
|
-
{cesiumAvailable &&
|
|
1547
|
+
{cesiumAvailable && (
|
|
1642
1548
|
<>
|
|
1643
1549
|
<Tooltip>
|
|
1644
1550
|
<TooltipTrigger asChild>
|
|
@@ -1800,22 +1706,6 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1800
1706
|
the toolbar's meta cluster stays focused on shell chrome
|
|
1801
1707
|
(Settings · Theme · Help). */}
|
|
1802
1708
|
<div className="flex items-center gap-2 ml-2 pl-2 border-l border-zinc-200 dark:border-zinc-700/60">
|
|
1803
|
-
{desktopShell ? (
|
|
1804
|
-
<Tooltip>
|
|
1805
|
-
<TooltipTrigger asChild>
|
|
1806
|
-
<Button
|
|
1807
|
-
variant="ghost"
|
|
1808
|
-
size="icon"
|
|
1809
|
-
className="rounded-full"
|
|
1810
|
-
onClick={() => navigateToPath('/settings')}
|
|
1811
|
-
>
|
|
1812
|
-
<Settings className="!h-[20px] !w-[20px]" />
|
|
1813
|
-
</Button>
|
|
1814
|
-
</TooltipTrigger>
|
|
1815
|
-
<TooltipContent>Settings</TooltipContent>
|
|
1816
|
-
</Tooltip>
|
|
1817
|
-
) : null}
|
|
1818
|
-
|
|
1819
1709
|
<Tooltip>
|
|
1820
1710
|
<TooltipTrigger asChild>
|
|
1821
1711
|
<div>
|
|
@@ -44,8 +44,6 @@ import { executeBasketIsolate } from '@/store/basket/basketCommands';
|
|
|
44
44
|
import { useIfc } from '@/hooks/useIfc';
|
|
45
45
|
import { cn } from '@/lib/utils';
|
|
46
46
|
import { GLTFExporter } from '@ifc-lite/export';
|
|
47
|
-
import { openIfcFileDialog } from '@/services/file-dialog';
|
|
48
|
-
import { logToDesktopTerminal } from '@/services/desktop-logger';
|
|
49
47
|
import { recordRecentFiles, cacheFileBlobs } from '@/lib/recent-files';
|
|
50
48
|
import { toast } from '@/components/ui/toast';
|
|
51
49
|
|
|
@@ -181,13 +179,7 @@ export function MobileToolbar() {
|
|
|
181
179
|
variant="ghost"
|
|
182
180
|
size="icon-sm"
|
|
183
181
|
className="h-9 w-9 flex-shrink-0"
|
|
184
|
-
onClick={
|
|
185
|
-
const file = await openIfcFileDialog();
|
|
186
|
-
if (file) {
|
|
187
|
-
recordRecentFiles([{ name: file.name, size: file.size, path: file.path, modifiedMs: file.modifiedMs ?? null }]);
|
|
188
|
-
void loadFile(file);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
182
|
+
onClick={() => {
|
|
191
183
|
fileInputRef.current?.click();
|
|
192
184
|
}}
|
|
193
185
|
disabled={loading}
|