@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
package/src/App.tsx
CHANGED
|
@@ -9,12 +9,10 @@
|
|
|
9
9
|
* at boot, react to popstate, and switch by prefix. The handful of routes:
|
|
10
10
|
*
|
|
11
11
|
* / → main WebGL viewer (default)
|
|
12
|
-
* /settings → desktop-shell account / API-key management (Tauri)
|
|
13
12
|
* /mcp[/...] → @ifc-lite/mcp marketing surface
|
|
14
13
|
*/
|
|
15
14
|
|
|
16
15
|
import { ViewerLayout } from './components/viewer/ViewerLayout';
|
|
17
|
-
import { SettingsPage } from './components/viewer/SettingsPage';
|
|
18
16
|
import { McpLanding } from './components/mcp/McpLanding';
|
|
19
17
|
import { McpPlayground } from './components/mcp/McpPlayground';
|
|
20
18
|
import { BimProvider } from './sdk/BimProvider';
|
|
@@ -66,7 +64,7 @@ export function App() {
|
|
|
66
64
|
return (
|
|
67
65
|
<BimProvider>
|
|
68
66
|
<ExtensionHostProvider>
|
|
69
|
-
|
|
67
|
+
<ViewerLayout />
|
|
70
68
|
<Toaster />
|
|
71
69
|
<Analytics />
|
|
72
70
|
</ExtensionHostProvider>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - Import/export BCF files
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import React, { useCallback,
|
|
16
|
+
import React, { useCallback, useState, useMemo, useRef } from 'react';
|
|
17
17
|
import {
|
|
18
18
|
X,
|
|
19
19
|
MessageSquare,
|
|
@@ -39,7 +39,6 @@ import { BCFTopicList } from './bcf/BCFTopicList';
|
|
|
39
39
|
import { BCFTopicDetail } from './bcf/BCFTopicDetail';
|
|
40
40
|
import { BCFCreateTopicForm } from './bcf/BCFCreateTopicForm';
|
|
41
41
|
import { openGenericFileDialog } from '@/services/file-dialog';
|
|
42
|
-
import { claimNextDesktopPanelAction, subscribeDesktopPanelActions } from '@/services/desktop-panel-actions';
|
|
43
42
|
|
|
44
43
|
// ============================================================================
|
|
45
44
|
// Main BCF Panel Component
|
|
@@ -298,20 +297,6 @@ export function BCFPanel({ onClose }: BCFPanelProps) {
|
|
|
298
297
|
setShowAuthorDialog(false);
|
|
299
298
|
}, [tempAuthor, setBcfAuthor]);
|
|
300
299
|
|
|
301
|
-
useEffect(() => {
|
|
302
|
-
const drainDesktopActions = () => {
|
|
303
|
-
if (claimNextDesktopPanelAction('bcf-import')) {
|
|
304
|
-
void importFromDialog();
|
|
305
|
-
}
|
|
306
|
-
if (claimNextDesktopPanelAction('bcf-export')) {
|
|
307
|
-
void handleExport();
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
drainDesktopActions();
|
|
312
|
-
return subscribeDesktopPanelActions(drainDesktopActions);
|
|
313
|
-
}, [handleExport, importFromDialog]);
|
|
314
|
-
|
|
315
300
|
return (
|
|
316
301
|
<div className="flex flex-col h-full bg-background">
|
|
317
302
|
{/* Header */}
|
|
@@ -52,7 +52,6 @@ import { buildRepairSessionKey, getEscalatedRepairScope, pruneMessagesForRepair
|
|
|
52
52
|
import type { ChatMessage, ChatRepairRequest, FileAttachment } from '@/lib/llm/types';
|
|
53
53
|
import { canUsePlainCodeBlockFallback, type ScriptMutationIntent } from '@/lib/llm/script-preservation';
|
|
54
54
|
import { Check, Image as ImageIcon, KeyRound } from 'lucide-react';
|
|
55
|
-
import { hasDesktopFeatureAccess } from '@/lib/desktop-product';
|
|
56
55
|
import { getModelById } from '@/lib/llm/models';
|
|
57
56
|
import { resolveStreamRoute } from '@/lib/llm/byok-guard';
|
|
58
57
|
import { getApiKeys, hasAnthropicKey, hasOpenaiKey, subscribeApiKeys } from '@/services/api-keys';
|
|
@@ -300,9 +299,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
300
299
|
const setChatHasByokKey = useViewerStore((s) => s.setChatHasByokKey);
|
|
301
300
|
const usage = useViewerStore((s) => s.chatUsage);
|
|
302
301
|
const setChatUsage = useViewerStore((s) => s.setChatUsage);
|
|
303
|
-
const desktopEntitlement = useViewerStore((s) => s.desktopEntitlement);
|
|
304
302
|
const { execute } = useSandbox();
|
|
305
|
-
const canUseAiAssistant = hasDesktopFeatureAccess(desktopEntitlement, 'ai_assistant');
|
|
306
303
|
|
|
307
304
|
// Sync BYOK key availability into the store and track per-provider state
|
|
308
305
|
const [keyStateAnthropic, setKeyStateAnthropic] = useState(hasAnthropicKey);
|
|
@@ -349,10 +346,6 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
349
346
|
const [showScrollBtn, setShowScrollBtn] = useState(false);
|
|
350
347
|
const [userScrolledUp, setUserScrolledUp] = useState(false);
|
|
351
348
|
const [lastFinishReason, setLastFinishReason] = useState<string | null>(null);
|
|
352
|
-
const promptAiUpgrade = useCallback(() => {
|
|
353
|
-
setChatError('AI assistant is available with Desktop Pro.');
|
|
354
|
-
toast.info('AI assistant is available with Desktop Pro');
|
|
355
|
-
}, [setChatError]);
|
|
356
349
|
|
|
357
350
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
358
351
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
@@ -505,10 +498,6 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
505
498
|
// ── Core send logic ──
|
|
506
499
|
const doSend = useCallback(async (text: string, options?: ChatSendOptions) => {
|
|
507
500
|
if (!text.trim() || status === 'streaming' || status === 'sending') return;
|
|
508
|
-
if (!canUseAiAssistant) {
|
|
509
|
-
setChatError('AI assistant is available with Desktop Pro.');
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
501
|
// Clear any stale post-authoring CTA — this turn re-establishes it
|
|
513
502
|
// on completion if it's another authoring turn.
|
|
514
503
|
setChatToolReady(null);
|
|
@@ -1056,7 +1045,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1056
1045
|
}
|
|
1057
1046
|
}
|
|
1058
1047
|
}, [
|
|
1059
|
-
|
|
1048
|
+
status, activeModel, attachments,
|
|
1060
1049
|
addMessage, setChatStatus, updateStreaming, finalizeAssistant,
|
|
1061
1050
|
setChatError, setChatAbortController, clearAttachments, setChatUsage, resizeInput,
|
|
1062
1051
|
buildRepairPromptFromLiveState, triggerAutoRepair, execute, extensionHost,
|
|
@@ -1064,12 +1053,8 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1064
1053
|
]);
|
|
1065
1054
|
|
|
1066
1055
|
const handleSend = useCallback(() => {
|
|
1067
|
-
if (!canUseAiAssistant) {
|
|
1068
|
-
promptAiUpgrade();
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
1056
|
doSend(inputText);
|
|
1072
|
-
}, [
|
|
1057
|
+
}, [doSend, inputText]);
|
|
1073
1058
|
|
|
1074
1059
|
// Allow other panels (e.g. ScriptPanel errors) to trigger a chat repair turn.
|
|
1075
1060
|
useEffect(() => {
|
|
@@ -1098,10 +1083,6 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1098
1083
|
}, [pendingRepairRequest, status, consumePendingRepairRequest, buildRepairPromptFromLiveState, doSend]);
|
|
1099
1084
|
|
|
1100
1085
|
const handleContinue = useCallback(() => {
|
|
1101
|
-
if (!canUseAiAssistant) {
|
|
1102
|
-
promptAiUpgrade();
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
1086
|
const state = useViewerStore.getState();
|
|
1106
1087
|
const partial = state.chatStreamingContent.trim();
|
|
1107
1088
|
const lastAssistant = [...state.chatMessages].reverse().find((m) => m.role === 'assistant');
|
|
@@ -1114,7 +1095,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1114
1095
|
}
|
|
1115
1096
|
setChatError(null);
|
|
1116
1097
|
doSend(CONTINUE_PROMPT, { continuationBase });
|
|
1117
|
-
}, [
|
|
1098
|
+
}, [doSend, finalizeAssistant, setChatError]);
|
|
1118
1099
|
|
|
1119
1100
|
const handleStop = useCallback(() => {
|
|
1120
1101
|
const controller = useViewerStore.getState().chatAbortController;
|
|
@@ -1139,10 +1120,6 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1139
1120
|
|
|
1140
1121
|
// ── Error feedback (Fix this) ──
|
|
1141
1122
|
const handleFixError = useCallback((code: string, errorMsg: string) => {
|
|
1142
|
-
if (!canUseAiAssistant) {
|
|
1143
|
-
promptAiUpgrade();
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
1123
|
const diagnostics = useViewerStore.getState().scriptLastDiagnostics;
|
|
1147
1124
|
const liveCode = useViewerStore.getState().scriptEditorContent;
|
|
1148
1125
|
const staleCode = code.trim() !== liveCode.trim() ? code : undefined;
|
|
@@ -1157,7 +1134,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1157
1134
|
requestedRepairScope: getPrimaryRootCause(diagnostics)?.repairScope,
|
|
1158
1135
|
rootCauseKey: getPrimaryRootCause(diagnostics)?.rootCauseKey,
|
|
1159
1136
|
});
|
|
1160
|
-
}, [buildRepairPromptFromLiveState,
|
|
1137
|
+
}, [buildRepairPromptFromLiveState, doSend]);
|
|
1161
1138
|
|
|
1162
1139
|
// ── Clickable example prompts ──
|
|
1163
1140
|
const handleExampleClick = useCallback((prompt: string) => {
|
|
@@ -1191,10 +1168,6 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1191
1168
|
|
|
1192
1169
|
// ── File upload (button + drag-drop + paste) ──
|
|
1193
1170
|
const processFiles = useCallback(async (files: FileList | File[]) => {
|
|
1194
|
-
if (!canUseAiAssistant) {
|
|
1195
|
-
promptAiUpgrade();
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
1171
|
const model = getModelById(activeModel);
|
|
1199
1172
|
const supportsImages = model?.supportsImages ?? false;
|
|
1200
1173
|
const supportsFileAttachments = model?.supportsFileAttachments ?? true;
|
|
@@ -1309,7 +1282,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1309
1282
|
setChatError(`Could not read ${file.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1310
1283
|
}
|
|
1311
1284
|
}
|
|
1312
|
-
}, [activeModel, addAttachment, attachments.length,
|
|
1285
|
+
}, [activeModel, addAttachment, attachments.length, setChatError]);
|
|
1313
1286
|
|
|
1314
1287
|
const handleFileUpload = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
1315
1288
|
const files = e.target.files;
|
|
@@ -1490,15 +1463,9 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1490
1463
|
)}
|
|
1491
1464
|
</div>
|
|
1492
1465
|
|
|
1493
|
-
{!canUseAiAssistant && (
|
|
1494
|
-
<div className="border-b bg-muted/40 px-3 py-2 text-xs text-muted-foreground">
|
|
1495
|
-
AI assistant requires Desktop Pro. Core viewing and scripting stay available without it.
|
|
1496
|
-
</div>
|
|
1497
|
-
)}
|
|
1498
|
-
|
|
1499
1466
|
{/* Slim CTA banner — appears when the modal has been dismissed but the
|
|
1500
1467
|
selected model still needs a key. Re-opens the modal on click. */}
|
|
1501
|
-
{needsByokKey &&
|
|
1468
|
+
{needsByokKey && !byokModal.open && (
|
|
1502
1469
|
<button
|
|
1503
1470
|
type="button"
|
|
1504
1471
|
onClick={() => openByokModal(needsAnthropicKey ? 'anthropic' : 'openai')}
|
|
@@ -1734,16 +1701,14 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1734
1701
|
variant="ghost"
|
|
1735
1702
|
size="icon-xs"
|
|
1736
1703
|
onClick={() => fileInputRef.current?.click()}
|
|
1737
|
-
disabled={!canAttachInput
|
|
1704
|
+
disabled={!canAttachInput}
|
|
1738
1705
|
className="shrink-0 mb-0.5"
|
|
1739
1706
|
>
|
|
1740
1707
|
<Paperclip className="h-3.5 w-3.5" />
|
|
1741
1708
|
</Button>
|
|
1742
1709
|
</TooltipTrigger>
|
|
1743
1710
|
<TooltipContent>
|
|
1744
|
-
{
|
|
1745
|
-
? 'AI assistant not available'
|
|
1746
|
-
: canAttachInput
|
|
1711
|
+
{canAttachInput
|
|
1747
1712
|
? 'Attach file or image (paste, drag & drop)'
|
|
1748
1713
|
: 'Selected model does not support attachments'}
|
|
1749
1714
|
</TooltipContent>
|
|
@@ -1758,11 +1723,11 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1758
1723
|
}}
|
|
1759
1724
|
onKeyDown={handleKeyDown}
|
|
1760
1725
|
onPaste={handlePaste}
|
|
1761
|
-
placeholder={
|
|
1726
|
+
placeholder={needsByokKey ? `Add your ${needsAnthropicKey ? 'Anthropic' : 'OpenAI'} key to chat with this model` : 'Ask anything...'}
|
|
1762
1727
|
rows={1}
|
|
1763
1728
|
className="flex-1 resize-none rounded-md border border-input bg-background text-foreground placeholder:text-muted-foreground px-3 py-1.5 text-sm min-h-[32px] max-h-[120px] focus:outline-none focus:ring-1 focus:ring-ring"
|
|
1764
1729
|
style={{ height: 'auto', overflow: 'hidden' }}
|
|
1765
|
-
disabled={
|
|
1730
|
+
disabled={needsByokKey}
|
|
1766
1731
|
/>
|
|
1767
1732
|
|
|
1768
1733
|
{isActive ? (
|
|
@@ -1786,7 +1751,7 @@ export function ChatPanel({ onClose }: ChatPanelProps) {
|
|
|
1786
1751
|
variant="default"
|
|
1787
1752
|
size="icon-xs"
|
|
1788
1753
|
onClick={handleSend}
|
|
1789
|
-
disabled={!inputText.trim() ||
|
|
1754
|
+
disabled={!inputText.trim() || needsByokKey}
|
|
1790
1755
|
className="shrink-0 mb-0.5"
|
|
1791
1756
|
>
|
|
1792
1757
|
<Send className="h-3.5 w-3.5" />
|
|
@@ -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,
|
|
@@ -19,7 +19,6 @@ import { cn } from '@/lib/utils';
|
|
|
19
19
|
import { useViewerStore, resolveEntityRef } from '@/store';
|
|
20
20
|
import { toGlobalIdFromModels } from '@/store/globalId';
|
|
21
21
|
import { useIfc } from '@/hooks/useIfc';
|
|
22
|
-
import { getNativeMetadataChildren, searchNativeMetadataEntities } from '@/services/desktop-native-metadata';
|
|
23
22
|
|
|
24
23
|
import type { TreeNode } from './hierarchy/types';
|
|
25
24
|
import { isSpatialContainer } from './hierarchy/types';
|
|
@@ -38,7 +37,6 @@ export function HierarchyPanel() {
|
|
|
38
37
|
removeModel,
|
|
39
38
|
} = useIfc();
|
|
40
39
|
const selectedEntityId = useViewerStore((s) => s.selectedEntityId);
|
|
41
|
-
const selectedEntity = useViewerStore((s) => s.selectedEntity);
|
|
42
40
|
const setSelectedEntityId = useViewerStore((s) => s.setSelectedEntityId);
|
|
43
41
|
const setSelectedEntityIds = useViewerStore((s) => s.setSelectedEntityIds);
|
|
44
42
|
const setSelectedEntity = useViewerStore((s) => s.setSelectedEntity);
|
|
@@ -92,33 +90,6 @@ export function HierarchyPanel() {
|
|
|
92
90
|
|
|
93
91
|
// Check if we have multiple models loaded
|
|
94
92
|
const isMultiModel = models.size > 1;
|
|
95
|
-
const nativeLazyModel = useMemo(() => {
|
|
96
|
-
if (models.size !== 1) return null;
|
|
97
|
-
const [, model] = Array.from(models.entries())[0];
|
|
98
|
-
if (!model.nativeMetadata) return null;
|
|
99
|
-
return model.ifcDataStore?.spatialHierarchy ? null : model;
|
|
100
|
-
}, [models]);
|
|
101
|
-
const [nativeChildren, setNativeChildren] = useState<Record<number, Array<{
|
|
102
|
-
expressId: number;
|
|
103
|
-
type: string;
|
|
104
|
-
name: string;
|
|
105
|
-
globalId?: string | null;
|
|
106
|
-
kind: 'spatial' | 'element';
|
|
107
|
-
hasChildren: boolean;
|
|
108
|
-
elementCount?: number;
|
|
109
|
-
elevation?: number | null;
|
|
110
|
-
}>>>({});
|
|
111
|
-
const [nativeExpanded, setNativeExpanded] = useState<Set<number>>(new Set());
|
|
112
|
-
const [nativeSearchResults, setNativeSearchResults] = useState<Array<{
|
|
113
|
-
expressId: number;
|
|
114
|
-
type: string;
|
|
115
|
-
name: string;
|
|
116
|
-
globalId?: string | null;
|
|
117
|
-
kind: 'spatial' | 'element';
|
|
118
|
-
hasChildren: boolean;
|
|
119
|
-
elementCount?: number;
|
|
120
|
-
elevation?: number | null;
|
|
121
|
-
}>>([]);
|
|
122
93
|
|
|
123
94
|
// Use extracted hook for tree data management
|
|
124
95
|
const {
|
|
@@ -222,29 +193,6 @@ export function HierarchyPanel() {
|
|
|
222
193
|
};
|
|
223
194
|
}, [isDragging]);
|
|
224
195
|
|
|
225
|
-
useEffect(() => {
|
|
226
|
-
if (!nativeLazyModel?.nativeMetadata) {
|
|
227
|
-
setNativeSearchResults([]);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
const query = searchQuery.trim();
|
|
231
|
-
if (!query) {
|
|
232
|
-
setNativeSearchResults([]);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
let cancelled = false;
|
|
236
|
-
void searchNativeMetadataEntities(nativeLazyModel.nativeMetadata.cacheKey, query, 200)
|
|
237
|
-
.then((results) => {
|
|
238
|
-
if (!cancelled) setNativeSearchResults(results);
|
|
239
|
-
})
|
|
240
|
-
.catch(() => {
|
|
241
|
-
if (!cancelled) setNativeSearchResults([]);
|
|
242
|
-
});
|
|
243
|
-
return () => {
|
|
244
|
-
cancelled = true;
|
|
245
|
-
};
|
|
246
|
-
}, [nativeLazyModel, searchQuery]);
|
|
247
|
-
|
|
248
196
|
// Toggle visibility for a node
|
|
249
197
|
const handleVisibilityToggle = useCallback((node: TreeNode) => {
|
|
250
198
|
const elements = getNodeElements(node);
|
|
@@ -560,7 +508,7 @@ export function HierarchyPanel() {
|
|
|
560
508
|
}
|
|
561
509
|
|
|
562
510
|
const singleModel = models.size === 1 ? Array.from(models.values())[0] : null;
|
|
563
|
-
if (!ifcDataStore && singleModel
|
|
511
|
+
if (!ifcDataStore && singleModel) {
|
|
564
512
|
const metadataState = singleModel.metadataLoadState;
|
|
565
513
|
const message = metadataState === 'error'
|
|
566
514
|
? (singleModel.loadError || 'Native metadata failed to load.')
|
|
@@ -581,128 +529,6 @@ export function HierarchyPanel() {
|
|
|
581
529
|
);
|
|
582
530
|
}
|
|
583
531
|
|
|
584
|
-
if (nativeLazyModel?.nativeMetadata) {
|
|
585
|
-
const nativeMetadata = nativeLazyModel.nativeMetadata;
|
|
586
|
-
const nativeSelectedGlobalId =
|
|
587
|
-
selectedEntity?.modelId === nativeLazyModel.id
|
|
588
|
-
? toGlobalId(nativeLazyModel.id, selectedEntity.expressId)
|
|
589
|
-
: null;
|
|
590
|
-
|
|
591
|
-
const selectNativeEntity = (expressId: number) => {
|
|
592
|
-
const globalId = toGlobalId(nativeLazyModel.id, expressId);
|
|
593
|
-
setSelectedEntityIds([]);
|
|
594
|
-
setSelectedEntityId(globalId);
|
|
595
|
-
setSelectedEntity({
|
|
596
|
-
modelId: nativeLazyModel.id,
|
|
597
|
-
expressId,
|
|
598
|
-
});
|
|
599
|
-
setActiveModel(nativeLazyModel.id);
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
const toggleNativeNode = async (expressId: number) => {
|
|
603
|
-
setNativeExpanded((prev) => {
|
|
604
|
-
const next = new Set(prev);
|
|
605
|
-
if (next.has(expressId)) {
|
|
606
|
-
next.delete(expressId);
|
|
607
|
-
} else {
|
|
608
|
-
next.add(expressId);
|
|
609
|
-
}
|
|
610
|
-
return next;
|
|
611
|
-
});
|
|
612
|
-
if (nativeChildren[expressId]) return;
|
|
613
|
-
try {
|
|
614
|
-
const children = await getNativeMetadataChildren(nativeMetadata.cacheKey, expressId);
|
|
615
|
-
setNativeChildren((prev) => ({ ...prev, [expressId]: children }));
|
|
616
|
-
} catch {
|
|
617
|
-
setNativeChildren((prev) => ({ ...prev, [expressId]: [] }));
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
const renderNativeSummary = (
|
|
622
|
-
summary: {
|
|
623
|
-
expressId: number;
|
|
624
|
-
type: string;
|
|
625
|
-
name: string;
|
|
626
|
-
kind: 'spatial' | 'element';
|
|
627
|
-
hasChildren: boolean;
|
|
628
|
-
elementCount?: number;
|
|
629
|
-
},
|
|
630
|
-
depth: number,
|
|
631
|
-
): ReactElement => {
|
|
632
|
-
const expanded = nativeExpanded.has(summary.expressId);
|
|
633
|
-
return (
|
|
634
|
-
<div key={`${summary.kind}-${summary.expressId}`}>
|
|
635
|
-
<button
|
|
636
|
-
type="button"
|
|
637
|
-
className={cn(
|
|
638
|
-
'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',
|
|
639
|
-
nativeSelectedGlobalId === toGlobalId(nativeLazyModel.id, summary.expressId) && 'bg-primary/10 text-primary'
|
|
640
|
-
)}
|
|
641
|
-
style={{ paddingLeft: `${12 + depth * 16}px` }}
|
|
642
|
-
onClick={() => selectNativeEntity(summary.expressId)}
|
|
643
|
-
>
|
|
644
|
-
{summary.hasChildren ? (
|
|
645
|
-
<span
|
|
646
|
-
className="w-4 text-center text-xs text-zinc-500"
|
|
647
|
-
onClick={(event) => {
|
|
648
|
-
event.stopPropagation();
|
|
649
|
-
void toggleNativeNode(summary.expressId);
|
|
650
|
-
}}
|
|
651
|
-
>
|
|
652
|
-
{expanded ? 'v' : '>'}
|
|
653
|
-
</span>
|
|
654
|
-
) : (
|
|
655
|
-
<span className="w-4" />
|
|
656
|
-
)}
|
|
657
|
-
<span className="truncate flex-1 text-sm">{summary.name || `${summary.type} #${summary.expressId}`}</span>
|
|
658
|
-
<span className="text-[10px] uppercase tracking-wide text-zinc-500">{summary.type}</span>
|
|
659
|
-
{typeof summary.elementCount === 'number' && summary.elementCount > 0 && (
|
|
660
|
-
<span className="text-[10px] text-zinc-400">{summary.elementCount}</span>
|
|
661
|
-
)}
|
|
662
|
-
</button>
|
|
663
|
-
{expanded && (nativeChildren[summary.expressId] ?? []).map((child) => renderNativeSummary(child, depth + 1))}
|
|
664
|
-
</div>
|
|
665
|
-
);
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
return (
|
|
669
|
-
<div className="h-full flex flex-col border-r-2 border-zinc-200 dark:border-zinc-800 bg-white dark:bg-black">
|
|
670
|
-
<div className="p-3 border-b-2 border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-black">
|
|
671
|
-
<Input
|
|
672
|
-
placeholder="Search..."
|
|
673
|
-
value={searchQuery}
|
|
674
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
675
|
-
leftIcon={<Search className="h-4 w-4" />}
|
|
676
|
-
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"
|
|
677
|
-
/>
|
|
678
|
-
</div>
|
|
679
|
-
<SectionHeader
|
|
680
|
-
icon={Building2}
|
|
681
|
-
title={searchQuery.trim() ? 'Search Results' : 'Hierarchy'}
|
|
682
|
-
count={searchQuery.trim() ? nativeSearchResults.length : 1}
|
|
683
|
-
/>
|
|
684
|
-
<div className="flex-1 overflow-auto scrollbar-thin bg-white dark:bg-black">
|
|
685
|
-
{searchQuery.trim()
|
|
686
|
-
? nativeSearchResults.map((result) => renderNativeSummary(result, 0))
|
|
687
|
-
: nativeMetadata.spatialTree
|
|
688
|
-
? renderNativeSummary(nativeMetadata.spatialTree, 0)
|
|
689
|
-
: (
|
|
690
|
-
<div className="p-4 text-xs text-zinc-500">
|
|
691
|
-
{nativeLazyModel.metadataLoadState === 'error'
|
|
692
|
-
? (nativeLazyModel.loadError || 'Native spatial metadata is unavailable for this model.')
|
|
693
|
-
: nativeLazyModel.metadataLoadState === 'bootstrapping'
|
|
694
|
-
? 'Native spatial metadata is still loading.'
|
|
695
|
-
: 'Native spatial metadata tree is unavailable for this model.'}
|
|
696
|
-
</div>
|
|
697
|
-
)}
|
|
698
|
-
</div>
|
|
699
|
-
<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">
|
|
700
|
-
On-demand desktop metadata
|
|
701
|
-
</div>
|
|
702
|
-
</div>
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
532
|
// Helper to render a node via the extracted HierarchyNode component
|
|
707
533
|
const renderNode = (node: TreeNode, virtualRow: { index: number; size: number; start: number }) => {
|
|
708
534
|
const { isSelected, nodeHidden, modelVisible } = computeNodeState(node);
|
|
@@ -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);
|