@ifc-lite/viewer 1.17.6 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +17 -14
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +513 -0
- package/dist/assets/{basketViewActivator-86rgogji.js → basketViewActivator-Cm1QEk_R.js} +1 -1
- package/dist/assets/{exporters-CcPS9MK5.js → exporters-B_OBqIyD.js} +3235 -2648
- package/dist/assets/{geometry.worker-BFUYA08u.js → geometry.worker-xHHy-9DV.js} +1 -1
- package/dist/assets/{ifc-lite_bg-BINvzoCP.wasm → ifc-lite_bg-ADjKXSms.wasm} +0 -0
- package/dist/assets/{index-Bfms9I4A.js → index-BKq-M3Mk.js} +44124 -30920
- package/dist/assets/index-COnQRuqY.css +1 -0
- package/dist/assets/{native-bridge-DUyLCMZS.js → native-bridge-SHXiQwFW.js} +1 -1
- package/dist/assets/sandbox-jez21HtV.js +9627 -0
- package/dist/assets/{server-client-BuZK7OST.js → server-client-ncOQVNso.js} +1 -1
- package/dist/assets/{wasm-bridge-JsqEGDV8.js → wasm-bridge-DyfBSB8z.js} +1 -1
- package/dist/index.html +6 -6
- package/package.json +10 -10
- package/src/apache-arrow.d.ts +30 -0
- package/src/components/viewer/AddElementPanel.tsx +758 -0
- package/src/components/viewer/BulkPropertyEditor.tsx +7 -0
- package/src/components/viewer/ChatPanel.tsx +64 -2
- package/src/components/viewer/CommandPalette.tsx +56 -7
- package/src/components/viewer/EntityContextMenu.tsx +168 -4
- package/src/components/viewer/ExportChangesButton.tsx +25 -5
- package/src/components/viewer/ExportDialog.tsx +19 -1
- package/src/components/viewer/MainToolbar.tsx +69 -10
- package/src/components/viewer/PropertiesPanel.tsx +222 -22
- package/src/components/viewer/SearchInline.tsx +669 -0
- package/src/components/viewer/SearchModal.filter.builder.tsx +766 -0
- package/src/components/viewer/SearchModal.filter.tsx +514 -0
- package/src/components/viewer/SearchModal.text.tsx +388 -0
- package/src/components/viewer/SearchModal.tsx +235 -0
- package/src/components/viewer/ToolOverlays.tsx +5 -0
- package/src/components/viewer/ViewerLayout.tsx +24 -4
- package/src/components/viewer/Viewport.tsx +11 -1
- package/src/components/viewer/ViewportContainer.tsx +2 -0
- package/src/components/viewer/annotations/AnnotationDropInput.tsx +203 -0
- package/src/components/viewer/annotations/AnnotationLayer.tsx +287 -0
- package/src/components/viewer/annotations/AnnotationPin.tsx +90 -0
- package/src/components/viewer/annotations/AnnotationPopover.tsx +296 -0
- package/src/components/viewer/bcf/BCFTopicDetail.tsx +1 -1
- package/src/components/viewer/lists/ListPanel.tsx +14 -21
- package/src/components/viewer/properties/RawStepCard.tsx +332 -0
- package/src/components/viewer/properties/RawStepRow.tsx +261 -0
- package/src/components/viewer/properties/ScheduleCard.tsx +224 -0
- package/src/components/viewer/properties/TaskEditCard.tsx +510 -0
- package/src/components/viewer/properties/raw-step-format.ts +193 -0
- package/src/components/viewer/schedule/AnimationSettingsPopover.tsx +542 -0
- package/src/components/viewer/schedule/GanttDependencyArrows.tsx +89 -0
- package/src/components/viewer/schedule/GanttDragTooltip.tsx +48 -0
- package/src/components/viewer/schedule/GanttEmptyState.tsx +97 -0
- package/src/components/viewer/schedule/GanttPanel.tsx +295 -0
- package/src/components/viewer/schedule/GanttTaskBar.tsx +199 -0
- package/src/components/viewer/schedule/GanttTaskTree.tsx +250 -0
- package/src/components/viewer/schedule/GanttTimeline.tsx +305 -0
- package/src/components/viewer/schedule/GanttToolbar.tsx +406 -0
- package/src/components/viewer/schedule/GenerateAdvancedPanel.tsx +147 -0
- package/src/components/viewer/schedule/GenerateScheduleDialog.tsx +392 -0
- package/src/components/viewer/schedule/HeightStrategyPanel.tsx +120 -0
- package/src/components/viewer/schedule/generate-schedule.test.ts +439 -0
- package/src/components/viewer/schedule/generate-schedule.ts +648 -0
- package/src/components/viewer/schedule/schedule-animator.test.ts +452 -0
- package/src/components/viewer/schedule/schedule-animator.ts +488 -0
- package/src/components/viewer/schedule/schedule-selection.test.ts +148 -0
- package/src/components/viewer/schedule/schedule-selection.ts +163 -0
- package/src/components/viewer/schedule/schedule-utils.ts +223 -0
- package/src/components/viewer/schedule/useConstructionSequence.ts +156 -0
- package/src/components/viewer/schedule/useGanttBarDrag.test.ts +90 -0
- package/src/components/viewer/schedule/useGanttBarDrag.ts +305 -0
- package/src/components/viewer/schedule/useGanttSelection3DHighlight.ts +152 -0
- package/src/components/viewer/schedule/useOverlayCompositor.ts +108 -0
- package/src/components/viewer/selectionHandlers.ts +446 -0
- package/src/components/viewer/tools/AddElementOverlay.tsx +540 -0
- package/src/components/viewer/useDuplicateShortcut.ts +77 -0
- package/src/components/viewer/useMouseControls.ts +9 -1
- package/src/hooks/useIfcLoader.ts +22 -10
- package/src/hooks/useKeyboardShortcuts.ts +25 -0
- package/src/hooks/useSandbox.ts +1 -1
- package/src/hooks/useSearchIndex.ts +125 -0
- package/src/index.css +66 -0
- package/src/lib/llm/system-prompt.test.ts +14 -0
- package/src/lib/llm/system-prompt.ts +102 -1
- package/src/lib/llm/types.ts +6 -0
- package/src/lib/recent-files.ts +38 -4
- package/src/lib/scripts/templates/bim-globals.d.ts +136 -114
- package/src/lib/scripts/templates/construction-schedule.ts +223 -0
- package/src/lib/scripts/templates.ts +7 -0
- package/src/lib/search/common-ifc-types.ts +36 -0
- package/src/lib/search/filter-evaluate.test.ts +537 -0
- package/src/lib/search/filter-evaluate.ts +610 -0
- package/src/lib/search/filter-rules.test.ts +119 -0
- package/src/lib/search/filter-rules.ts +198 -0
- package/src/lib/search/filter-schema.test.ts +233 -0
- package/src/lib/search/filter-schema.ts +146 -0
- package/src/lib/search/recent-searches.test.ts +116 -0
- package/src/lib/search/recent-searches.ts +93 -0
- package/src/lib/search/result-export.test.ts +101 -0
- package/src/lib/search/result-export.ts +104 -0
- package/src/lib/search/saved-filters.test.ts +118 -0
- package/src/lib/search/saved-filters.ts +154 -0
- package/src/lib/search/tier0-scan.test.ts +196 -0
- package/src/lib/search/tier0-scan.ts +237 -0
- package/src/lib/search/tier1-index.test.ts +242 -0
- package/src/lib/search/tier1-index.ts +448 -0
- package/src/sdk/adapters/export-adapter.test.ts +434 -1
- package/src/sdk/adapters/export-adapter.ts +404 -1
- package/src/sdk/adapters/export-schedule-splice.test.ts +127 -0
- package/src/sdk/adapters/export-schedule-splice.ts +87 -0
- package/src/sdk/adapters/model-compat.ts +8 -2
- package/src/sdk/adapters/schedule-adapter.ts +73 -0
- package/src/sdk/adapters/store-adapter.ts +201 -0
- package/src/sdk/adapters/visibility-adapter.ts +3 -0
- package/src/sdk/local-backend.ts +16 -8
- package/src/services/desktop-export.ts +3 -1
- package/src/services/desktop-native-metadata.ts +41 -18
- package/src/services/file-dialog.ts +4 -1
- package/src/services/tauri-modules.d.ts +25 -0
- package/src/store/basketVisibleSet.ts +3 -0
- package/src/store/globalId.ts +4 -1
- package/src/store/index.ts +70 -1
- package/src/store/slices/addElementMeshes.ts +365 -0
- package/src/store/slices/addElementSlice.ts +275 -0
- package/src/store/slices/annotationsSlice.test.ts +133 -0
- package/src/store/slices/annotationsSlice.ts +251 -0
- package/src/store/slices/dataSlice.test.ts +23 -4
- package/src/store/slices/dataSlice.ts +1 -1
- package/src/store/slices/modelSlice.test.ts +67 -9
- package/src/store/slices/modelSlice.ts +39 -7
- package/src/store/slices/mutationSlice.ts +964 -3
- package/src/store/slices/overlayCompositor.test.ts +164 -0
- package/src/store/slices/overlaySlice.test.ts +93 -0
- package/src/store/slices/overlaySlice.ts +151 -0
- package/src/store/slices/pinboardSlice.test.ts +6 -1
- package/src/store/slices/playbackSlice.ts +128 -0
- package/src/store/slices/schedule-edit-helpers.test.ts +97 -0
- package/src/store/slices/schedule-edit-helpers.ts +179 -0
- package/src/store/slices/scheduleSlice.test.ts +694 -0
- package/src/store/slices/scheduleSlice.ts +1330 -0
- package/src/store/slices/searchSlice.test.ts +342 -0
- package/src/store/slices/searchSlice.ts +341 -0
- package/src/store/slices/selectionSlice.test.ts +46 -0
- package/src/store/slices/selectionSlice.ts +20 -0
- package/src/store.ts +14 -0
- package/dist/assets/index-_bfZsDCC.css +0 -1
- package/dist/assets/sandbox-C8575tul.js +0 -5951
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
PersonStanding,
|
|
11
11
|
Ruler,
|
|
12
12
|
Scissors,
|
|
13
|
+
MapPin,
|
|
13
14
|
Eye,
|
|
14
15
|
EyeOff,
|
|
15
16
|
Equal,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
Layout,
|
|
38
39
|
LayoutTemplate,
|
|
39
40
|
FileCode2,
|
|
41
|
+
CalendarClock,
|
|
40
42
|
Globe2,
|
|
41
43
|
Settings,
|
|
42
44
|
} from 'lucide-react';
|
|
@@ -66,6 +68,7 @@ import { ExportDialog } from './ExportDialog';
|
|
|
66
68
|
import { BulkPropertyEditor } from './BulkPropertyEditor';
|
|
67
69
|
import { DataConnector } from './DataConnector';
|
|
68
70
|
import { ExportChangesButton } from './ExportChangesButton';
|
|
71
|
+
import { SearchInline } from './SearchInline';
|
|
69
72
|
// CesiumSettingsDialog removed — settings now shown as overlay on Cesium viewer
|
|
70
73
|
import { useFloorplanView } from '@/hooks/useFloorplanView';
|
|
71
74
|
import { buildDesktopUpgradeUrl, hasDesktopFeatureAccess, type DesktopFeature } from '@/lib/desktop-product';
|
|
@@ -84,7 +87,7 @@ import {
|
|
|
84
87
|
subscribeAnalysisExtensions,
|
|
85
88
|
} from '@/services/analysis-extensions';
|
|
86
89
|
|
|
87
|
-
type Tool = 'select' | 'walk' | 'measure' | 'section';
|
|
90
|
+
type Tool = 'select' | 'walk' | 'measure' | 'section' | 'annotate';
|
|
88
91
|
type WorkspacePanel = 'script' | 'list' | 'bcf' | 'ids' | 'lens' | string;
|
|
89
92
|
|
|
90
93
|
function isNativeFileHandle(file: File | NativeFileHandle): file is NativeFileHandle {
|
|
@@ -100,21 +103,39 @@ interface ToolButtonProps {
|
|
|
100
103
|
shortcut?: string;
|
|
101
104
|
activeTool: string;
|
|
102
105
|
onToolChange: (tool: Tool) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Tailwind classes applied when this tool is active. Defaults to the
|
|
108
|
+
* shared `bg-primary text-primary-foreground` shape; pass a per-tool
|
|
109
|
+
* accent (e.g. amber for Annotate) to set tools apart visually
|
|
110
|
+
* without breaking the toolbar's tool-button rhythm.
|
|
111
|
+
*/
|
|
112
|
+
activeAccentClass?: string;
|
|
103
113
|
}
|
|
104
114
|
|
|
105
|
-
function ToolButton({
|
|
115
|
+
function ToolButton({
|
|
116
|
+
tool,
|
|
117
|
+
icon: Icon,
|
|
118
|
+
label,
|
|
119
|
+
shortcut,
|
|
120
|
+
activeTool,
|
|
121
|
+
onToolChange,
|
|
122
|
+
activeAccentClass,
|
|
123
|
+
}: ToolButtonProps) {
|
|
124
|
+
const isActive = activeTool === tool;
|
|
106
125
|
return (
|
|
107
126
|
<Tooltip>
|
|
108
127
|
<TooltipTrigger asChild>
|
|
109
128
|
<Button
|
|
110
|
-
variant={
|
|
129
|
+
variant={isActive ? 'default' : 'ghost'}
|
|
111
130
|
size="icon-sm"
|
|
112
131
|
onClick={(e) => {
|
|
113
132
|
// Blur button to close tooltip after click
|
|
114
133
|
(e.currentTarget as HTMLButtonElement).blur();
|
|
115
134
|
onToolChange(tool);
|
|
116
135
|
}}
|
|
117
|
-
className={cn(
|
|
136
|
+
className={cn(
|
|
137
|
+
isActive && (activeAccentClass ?? 'bg-primary text-primary-foreground'),
|
|
138
|
+
)}
|
|
118
139
|
>
|
|
119
140
|
<Icon className="h-4 w-4" />
|
|
120
141
|
</Button>
|
|
@@ -305,6 +326,8 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
305
326
|
const setLensPanelVisible = useViewerStore((state) => state.setLensPanelVisible);
|
|
306
327
|
const scriptPanelVisible = useViewerStore((state) => state.scriptPanelVisible);
|
|
307
328
|
const setScriptPanelVisible = useViewerStore((state) => state.setScriptPanelVisible);
|
|
329
|
+
const ganttPanelVisible = useViewerStore((state) => state.ganttPanelVisible);
|
|
330
|
+
const setGanttPanelVisible = useViewerStore((state) => state.setGanttPanelVisible);
|
|
308
331
|
// Cesium 3D overlay state
|
|
309
332
|
const cesiumAvailable = useViewerStore((state) => state.cesiumAvailable);
|
|
310
333
|
const cesiumEnabled = useViewerStore((state) => state.cesiumEnabled);
|
|
@@ -508,21 +531,31 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
508
531
|
return false;
|
|
509
532
|
}, [desktopEntitlement, promptDesktopUpgrade]);
|
|
510
533
|
|
|
511
|
-
const handleToggleBottomPanel = useCallback((panel: 'script' | 'list') => {
|
|
534
|
+
const handleToggleBottomPanel = useCallback((panel: 'script' | 'list' | 'gantt') => {
|
|
512
535
|
if (activeAnalysisExtension?.placement === 'bottom') {
|
|
513
536
|
closeActiveAnalysisExtension();
|
|
514
537
|
}
|
|
515
|
-
const
|
|
516
|
-
const
|
|
517
|
-
const
|
|
538
|
+
const nextScriptVisible = panel === 'script' ? !scriptPanelVisible : false;
|
|
539
|
+
const nextListVisible = panel === 'list' ? !listPanelVisible : false;
|
|
540
|
+
const nextGanttVisible = panel === 'gantt' ? !ganttPanelVisible : false;
|
|
518
541
|
|
|
519
542
|
setScriptPanelVisible(nextScriptVisible);
|
|
520
543
|
setListPanelVisible(nextListVisible);
|
|
544
|
+
setGanttPanelVisible(nextGanttVisible);
|
|
521
545
|
|
|
522
|
-
if (nextScriptVisible || nextListVisible) {
|
|
546
|
+
if (nextScriptVisible || nextListVisible || nextGanttVisible) {
|
|
523
547
|
setRightPanelCollapsed(false);
|
|
524
548
|
}
|
|
525
|
-
}, [
|
|
549
|
+
}, [
|
|
550
|
+
activeAnalysisExtension?.placement,
|
|
551
|
+
ganttPanelVisible,
|
|
552
|
+
listPanelVisible,
|
|
553
|
+
scriptPanelVisible,
|
|
554
|
+
setGanttPanelVisible,
|
|
555
|
+
setListPanelVisible,
|
|
556
|
+
setRightPanelCollapsed,
|
|
557
|
+
setScriptPanelVisible,
|
|
558
|
+
]);
|
|
526
559
|
|
|
527
560
|
const handleToggleRightPanel = useCallback((panel: 'bcf' | 'ids' | 'lens') => {
|
|
528
561
|
if (activeAnalysisExtension?.placement !== 'bottom') {
|
|
@@ -577,6 +610,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
577
610
|
if ((extension.placement ?? 'right') === 'bottom') {
|
|
578
611
|
setScriptPanelVisible(false);
|
|
579
612
|
setListPanelVisible(false);
|
|
613
|
+
setGanttPanelVisible(false);
|
|
580
614
|
setRightPanelCollapsed(false);
|
|
581
615
|
return;
|
|
582
616
|
}
|
|
@@ -589,6 +623,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
589
623
|
analysisExtensionState.activeId,
|
|
590
624
|
analysisExtensionState.extensions,
|
|
591
625
|
setBcfPanelVisible,
|
|
626
|
+
setGanttPanelVisible,
|
|
592
627
|
setIdsPanelVisible,
|
|
593
628
|
setLensPanelVisible,
|
|
594
629
|
setListPanelVisible,
|
|
@@ -600,6 +635,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
600
635
|
const panels = new Set<WorkspacePanel>();
|
|
601
636
|
if (scriptPanelVisible) panels.add('script');
|
|
602
637
|
if (listPanelVisible) panels.add('list');
|
|
638
|
+
if (ganttPanelVisible) panels.add('gantt');
|
|
603
639
|
if (bcfPanelVisible) panels.add('bcf');
|
|
604
640
|
if (idsPanelVisible) panels.add('ids');
|
|
605
641
|
if (lensPanelVisible) panels.add('lens');
|
|
@@ -608,6 +644,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
608
644
|
}, [
|
|
609
645
|
analysisExtensionState.activeId,
|
|
610
646
|
bcfPanelVisible,
|
|
647
|
+
ganttPanelVisible,
|
|
611
648
|
idsPanelVisible,
|
|
612
649
|
lensPanelVisible,
|
|
613
650
|
listPanelVisible,
|
|
@@ -619,6 +656,7 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
619
656
|
if (activeWorkspacePanels.size > 1) return 'Multiple Panels';
|
|
620
657
|
if (activeWorkspacePanels.has('script')) return 'Script Editor';
|
|
621
658
|
if (activeWorkspacePanels.has('list')) return 'Lists';
|
|
659
|
+
if (activeWorkspacePanels.has('gantt')) return 'Schedule';
|
|
622
660
|
if (activeWorkspacePanels.has('bcf')) return 'BCF Issues';
|
|
623
661
|
if (activeWorkspacePanels.has('ids')) return 'IDS Validation';
|
|
624
662
|
if (activeWorkspacePanels.has('lens')) return 'Lens Rules';
|
|
@@ -948,6 +986,13 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
948
986
|
<FileSpreadsheet className="h-4 w-4 mr-2" />
|
|
949
987
|
Lists
|
|
950
988
|
</DropdownMenuCheckboxItem>
|
|
989
|
+
<DropdownMenuCheckboxItem
|
|
990
|
+
checked={activeWorkspacePanels.has('gantt')}
|
|
991
|
+
onCheckedChange={() => handleToggleBottomPanel('gantt')}
|
|
992
|
+
>
|
|
993
|
+
<CalendarClock className="h-4 w-4 mr-2" />
|
|
994
|
+
Schedule (Gantt)
|
|
995
|
+
</DropdownMenuCheckboxItem>
|
|
951
996
|
<DropdownMenuSeparator />
|
|
952
997
|
<DropdownMenuCheckboxItem
|
|
953
998
|
checked={activeWorkspacePanels.has('bcf')}
|
|
@@ -1011,6 +1056,11 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1011
1056
|
|
|
1012
1057
|
<Separator orientation="vertical" className="h-6 mx-1" />
|
|
1013
1058
|
|
|
1059
|
+
{/* ── Search (Tier-0 inline; ⌘F or / to focus) ── */}
|
|
1060
|
+
<SearchInline />
|
|
1061
|
+
|
|
1062
|
+
<Separator orientation="vertical" className="h-6 mx-1" />
|
|
1063
|
+
|
|
1014
1064
|
{/* ── Navigation Tools ── */}
|
|
1015
1065
|
<ToolButton tool="select" icon={MousePointer2} label="Select" shortcut="V" activeTool={activeTool} onToolChange={setActiveTool} />
|
|
1016
1066
|
<ToolButton tool="walk" icon={PersonStanding} label="Walk Mode" shortcut="C" activeTool={activeTool} onToolChange={setActiveTool} />
|
|
@@ -1020,6 +1070,15 @@ export function MainToolbar({ onShowShortcuts }: MainToolbarProps = {} as MainTo
|
|
|
1020
1070
|
{/* ── Measurement & Section ── */}
|
|
1021
1071
|
<ToolButton tool="measure" icon={Ruler} label="Measure" shortcut="M" activeTool={activeTool} onToolChange={setActiveTool} />
|
|
1022
1072
|
<ToolButton tool="section" icon={Scissors} label="Section" shortcut="X" activeTool={activeTool} onToolChange={setActiveTool} />
|
|
1073
|
+
<ToolButton
|
|
1074
|
+
tool="annotate"
|
|
1075
|
+
icon={MapPin}
|
|
1076
|
+
label="Annotate"
|
|
1077
|
+
shortcut="P"
|
|
1078
|
+
activeTool={activeTool}
|
|
1079
|
+
onToolChange={setActiveTool}
|
|
1080
|
+
activeAccentClass="bg-amber-500 text-white hover:bg-amber-500/90"
|
|
1081
|
+
/>
|
|
1023
1082
|
|
|
1024
1083
|
{/* Floorplan dropdown */}
|
|
1025
1084
|
{availableStoreys.length > 0 && (
|
|
@@ -33,7 +33,8 @@ import { getNativeEntityDetails } from '@/services/desktop-native-metadata';
|
|
|
33
33
|
import { configureMutationView } from '@/utils/configureMutationView';
|
|
34
34
|
import { IfcQuery } from '@ifc-lite/query';
|
|
35
35
|
import { MutablePropertyView } from '@ifc-lite/mutations';
|
|
36
|
-
import { extractClassificationsOnDemand, extractMaterialsOnDemand, extractTypePropertiesOnDemand, extractTypeEntityOwnProperties, extractDocumentsOnDemand, extractRelationshipsOnDemand, extractGeoreferencingOnDemand, extractLengthUnitScale, type IfcDataStore } from '@ifc-lite/parser';
|
|
36
|
+
import { extractClassificationsOnDemand, extractMaterialsOnDemand, extractTypePropertiesOnDemand, extractTypeEntityOwnProperties, extractDocumentsOnDemand, extractRelationshipsOnDemand, extractGeoreferencingOnDemand, extractLengthUnitScale, getAttributeNames, type IfcDataStore } from '@ifc-lite/parser';
|
|
37
|
+
import type { NewEntity } from '@ifc-lite/mutations';
|
|
37
38
|
import { EntityFlags, RelationshipType, isSpatialStructureTypeName, isStoreyLikeSpatialTypeName } from '@ifc-lite/data';
|
|
38
39
|
import type { EntityRef, FederatedModel } from '@/store/types';
|
|
39
40
|
|
|
@@ -43,11 +44,14 @@ import { QuantitySetCard } from './properties/QuantitySetCard';
|
|
|
43
44
|
import { ModelMetadataPanel } from './properties/ModelMetadataPanel';
|
|
44
45
|
import { ClassificationCard } from './properties/ClassificationCard';
|
|
45
46
|
import { MaterialCard } from './properties/MaterialCard';
|
|
47
|
+
import { ScheduleCard } from './properties/ScheduleCard';
|
|
48
|
+
import { TaskEditCard } from './properties/TaskEditCard';
|
|
46
49
|
import { DocumentCard } from './properties/DocumentCard';
|
|
47
50
|
import { RelationshipsCard } from './properties/RelationshipsCard';
|
|
48
51
|
import type { PropertySet, QuantitySet } from './properties/encodingUtils';
|
|
49
52
|
import { BsddCard } from './properties/BsddCard';
|
|
50
53
|
import { GeoreferencingPanel } from './properties/GeoreferencingPanel';
|
|
54
|
+
import { RawStepCard } from './properties/RawStepCard';
|
|
51
55
|
|
|
52
56
|
type DisplayProperty = { name: string; value: unknown; isMutated: boolean };
|
|
53
57
|
type DisplayPropertySet = {
|
|
@@ -57,6 +61,41 @@ type DisplayPropertySet = {
|
|
|
57
61
|
source?: PropertySet['source'];
|
|
58
62
|
};
|
|
59
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Synthesize an attribute list from a NewEntity record so the panel's
|
|
66
|
+
* attributes section renders for overlay-only duplicates / scripted
|
|
67
|
+
* adds. Positional indices are mapped to schema names; everything past
|
|
68
|
+
* the schema's defined slots is dropped (no "Arg 9" rows in the bSDD
|
|
69
|
+
* panel).
|
|
70
|
+
*/
|
|
71
|
+
function attributesFromOverlayEntity(entity: NewEntity): Array<{ name: string; value: string }> {
|
|
72
|
+
const names = getAttributeNames(entity.type) ?? [];
|
|
73
|
+
if (names.length === 0) return [];
|
|
74
|
+
const out: Array<{ name: string; value: string }> = [];
|
|
75
|
+
// Stop at the smaller of the schema and the actual attributes — IFC
|
|
76
|
+
// entities can be partially populated (trailing optionals omitted).
|
|
77
|
+
const len = Math.min(names.length, entity.attributes.length);
|
|
78
|
+
for (let i = 0; i < len; i++) {
|
|
79
|
+
const value = entity.attributes[i];
|
|
80
|
+
let display: string;
|
|
81
|
+
if (value === null || value === undefined) continue;
|
|
82
|
+
if (typeof value === 'string') {
|
|
83
|
+
if (value === '$' || value.length === 0) continue;
|
|
84
|
+
display = value;
|
|
85
|
+
} else if (typeof value === 'number') {
|
|
86
|
+
display = String(value);
|
|
87
|
+
} else if (typeof value === 'boolean') {
|
|
88
|
+
display = value ? 'true' : 'false';
|
|
89
|
+
} else {
|
|
90
|
+
// Lists / typed values — skip the bSDD attributes panel; users
|
|
91
|
+
// can still see them on the Raw STEP tab.
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
out.push({ name: names[i], value: display });
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
60
99
|
function mergePropertySetLists(base: DisplayPropertySet[], incoming: DisplayPropertySet[]): DisplayPropertySet[] {
|
|
61
100
|
const merged = base.map(pset => ({
|
|
62
101
|
...pset,
|
|
@@ -364,6 +403,32 @@ export function PropertiesPanel() {
|
|
|
364
403
|
return modelQuery.entity(originalExpressId);
|
|
365
404
|
}, [selectedEntity, modelQuery]);
|
|
366
405
|
|
|
406
|
+
// Overlay-only entity record (duplicates, scripted adds). Carries
|
|
407
|
+
// the type + positional attributes the StoreEditor recorded — used
|
|
408
|
+
// as a fallback when the parsed entityNode comes up empty so the
|
|
409
|
+
// panel doesn't render `UNKNOWN / Unknown` for fresh entities.
|
|
410
|
+
const overlayEntity = useMemo(() => {
|
|
411
|
+
let modelId = selectedEntity?.modelId;
|
|
412
|
+
if (modelId === 'legacy') modelId = '__legacy__';
|
|
413
|
+
const expressId = selectedEntity?.expressId;
|
|
414
|
+
if (!modelId || !expressId) return null;
|
|
415
|
+
const view = mutationViews.get(modelId);
|
|
416
|
+
return view?.getNewEntity(expressId) ?? null;
|
|
417
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
418
|
+
}, [selectedEntity, mutationViews, mutationVersion]);
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Read a positional attribute from the overlay entity record as a
|
|
422
|
+
* display string. Returns null when the entity isn't overlay-only
|
|
423
|
+
* or the slot is empty / not stringy.
|
|
424
|
+
*/
|
|
425
|
+
const overlayAttr = useCallback((index: number): string | null => {
|
|
426
|
+
if (!overlayEntity) return null;
|
|
427
|
+
const value = overlayEntity.attributes[index];
|
|
428
|
+
if (typeof value === 'string' && value.length > 0 && value !== '$') return value;
|
|
429
|
+
return null;
|
|
430
|
+
}, [overlayEntity]);
|
|
431
|
+
|
|
367
432
|
// Check if the selected entity is a type entity (IfcWallType, etc.)
|
|
368
433
|
// Uses the entity type name to detect — type entity names end with "Type"
|
|
369
434
|
const isTypeEntity = useMemo(() => {
|
|
@@ -483,7 +548,14 @@ export function PropertiesPanel() {
|
|
|
483
548
|
// Merges mutated attributes (from bSDD) into the base attribute list.
|
|
484
549
|
// Note: GlobalId is intentionally excluded since it's shown in the dedicated GUID field above
|
|
485
550
|
const attributes = useMemo(() => {
|
|
486
|
-
const base = entityNode
|
|
551
|
+
const base = entityNode
|
|
552
|
+
? entityNode.allAttributes()
|
|
553
|
+
// Overlay-only entity (duplicate / scripted add) — synthesize the
|
|
554
|
+
// attribute list from the NewEntity record using the schema's
|
|
555
|
+
// positional names so the panel still shows Name/Description/etc.
|
|
556
|
+
: overlayEntity
|
|
557
|
+
? attributesFromOverlayEntity(overlayEntity)
|
|
558
|
+
: [];
|
|
487
559
|
|
|
488
560
|
// Merge mutated attributes from bSDD
|
|
489
561
|
let modelId = selectedEntity?.modelId;
|
|
@@ -510,41 +582,100 @@ export function PropertiesPanel() {
|
|
|
510
582
|
}
|
|
511
583
|
|
|
512
584
|
return base;
|
|
513
|
-
}, [entityNode, selectedEntity, mutationViews, mutationVersion]);
|
|
585
|
+
}, [entityNode, overlayEntity, selectedEntity, mutationViews, mutationVersion]);
|
|
586
|
+
|
|
587
|
+
// Resolve the entity id used for parsed-store lookups. For overlay
|
|
588
|
+
// duplicates this is the source entity (via the view's alias) — so
|
|
589
|
+
// materials / classifications / documents / structural rels appear
|
|
590
|
+
// on the duplicate exactly as they do on the source. Without the
|
|
591
|
+
// alias resolution the parsed maps would return empty for the
|
|
592
|
+
// overlay-only id.
|
|
593
|
+
const lookupExpressId = useMemo(() => {
|
|
594
|
+
const expressId = selectedEntity?.expressId;
|
|
595
|
+
if (!expressId) return null;
|
|
596
|
+
let modelId = selectedEntity?.modelId;
|
|
597
|
+
if (modelId === 'legacy') modelId = '__legacy__';
|
|
598
|
+
const view = modelId ? mutationViews.get(modelId) : null;
|
|
599
|
+
return view?.resolveBaseEntityId(expressId) ?? expressId;
|
|
600
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
601
|
+
}, [selectedEntity, mutationViews, mutationVersion]);
|
|
514
602
|
|
|
515
603
|
// Extract classifications for the selected entity from the IFC data store
|
|
516
604
|
const classifications = useMemo(() => {
|
|
517
|
-
if (!selectedEntity) return [];
|
|
605
|
+
if (!selectedEntity || lookupExpressId === null) return [];
|
|
518
606
|
const dataStore = model?.ifcDataStore ?? ifcDataStore;
|
|
519
607
|
if (!dataStore) return [];
|
|
520
|
-
return extractClassificationsOnDemand(dataStore as IfcDataStore,
|
|
521
|
-
}, [selectedEntity, model, ifcDataStore]);
|
|
608
|
+
return extractClassificationsOnDemand(dataStore as IfcDataStore, lookupExpressId);
|
|
609
|
+
}, [selectedEntity, lookupExpressId, model, ifcDataStore]);
|
|
522
610
|
|
|
523
611
|
// Extract materials for the selected entity from the IFC data store
|
|
524
612
|
const materialInfo = useMemo(() => {
|
|
525
|
-
if (!selectedEntity) return null;
|
|
613
|
+
if (!selectedEntity || lookupExpressId === null) return null;
|
|
526
614
|
const dataStore = model?.ifcDataStore ?? ifcDataStore;
|
|
527
615
|
if (!dataStore) return null;
|
|
528
|
-
return extractMaterialsOnDemand(dataStore as IfcDataStore,
|
|
529
|
-
}, [selectedEntity, model, ifcDataStore]);
|
|
616
|
+
return extractMaterialsOnDemand(dataStore as IfcDataStore, lookupExpressId);
|
|
617
|
+
}, [selectedEntity, lookupExpressId, model, ifcDataStore]);
|
|
530
618
|
|
|
531
619
|
// Extract documents for the selected entity from the IFC data store
|
|
532
620
|
const documents = useMemo(() => {
|
|
533
|
-
if (!selectedEntity) return [];
|
|
621
|
+
if (!selectedEntity || lookupExpressId === null) return [];
|
|
534
622
|
const dataStore = model?.ifcDataStore ?? ifcDataStore;
|
|
535
623
|
if (!dataStore) return [];
|
|
536
|
-
return extractDocumentsOnDemand(dataStore as IfcDataStore,
|
|
537
|
-
}, [selectedEntity, model, ifcDataStore]);
|
|
624
|
+
return extractDocumentsOnDemand(dataStore as IfcDataStore, lookupExpressId);
|
|
625
|
+
}, [selectedEntity, lookupExpressId, model, ifcDataStore]);
|
|
538
626
|
|
|
539
627
|
// Extract structural relationships (openings, fills, groups, connections)
|
|
540
628
|
const entityRelationships = useMemo(() => {
|
|
541
|
-
if (!selectedEntity) return null;
|
|
629
|
+
if (!selectedEntity || lookupExpressId === null) return null;
|
|
542
630
|
const dataStore = model?.ifcDataStore ?? ifcDataStore;
|
|
543
631
|
if (!dataStore) return null;
|
|
544
|
-
const rels = extractRelationshipsOnDemand(dataStore as IfcDataStore,
|
|
632
|
+
const rels = extractRelationshipsOnDemand(dataStore as IfcDataStore, lookupExpressId);
|
|
545
633
|
const totalCount = rels.voids.length + rels.fills.length + rels.groups.length + rels.connections.length;
|
|
546
634
|
return totalCount > 0 ? rels : null;
|
|
635
|
+
}, [selectedEntity, lookupExpressId, model, ifcDataStore]);
|
|
636
|
+
|
|
637
|
+
// 4D schedule — both parsed-from-IFC and locally-generated schedules live in
|
|
638
|
+
// the schedule slice. ScheduleCard renders nothing when no task in the
|
|
639
|
+
// schedule lists this entity as a controlled product, so it's safe to call
|
|
640
|
+
// unconditionally.
|
|
641
|
+
const scheduleData = useViewerStore((s) => s.scheduleData);
|
|
642
|
+
// Single-task selection from the Gantt triggers the Task edit card —
|
|
643
|
+
// pull the set and its size so the Inspector can react to any change.
|
|
644
|
+
const selectedTaskGlobalIds = useViewerStore((s) => s.selectedTaskGlobalIds);
|
|
645
|
+
const singleSelectedTaskGlobalId = useMemo(() => {
|
|
646
|
+
if (selectedTaskGlobalIds.size !== 1) return null;
|
|
647
|
+
return selectedTaskGlobalIds.values().next().value ?? null;
|
|
648
|
+
}, [selectedTaskGlobalIds]);
|
|
649
|
+
// True when the schedule contains at least one task the user generated
|
|
650
|
+
// locally (no expressId in the host STEP). Mixed schedules — parsed tail +
|
|
651
|
+
// user-appended task — still surface the pending banner so the user sees
|
|
652
|
+
// that something will be spliced on export.
|
|
653
|
+
const scheduleIsGenerated = useMemo(() => {
|
|
654
|
+
if (!scheduleData || scheduleData.tasks.length === 0) return false;
|
|
655
|
+
return scheduleData.tasks.some(t => !t.expressId || t.expressId <= 0);
|
|
656
|
+
}, [scheduleData]);
|
|
657
|
+
const selectedEntityGlobalId = useMemo(() => {
|
|
658
|
+
if (!selectedEntity) return null;
|
|
659
|
+
const dataStore = model?.ifcDataStore ?? ifcDataStore;
|
|
660
|
+
return (dataStore as IfcDataStore | null)?.entities?.getGlobalId?.(selectedEntity.expressId) ?? null;
|
|
547
661
|
}, [selectedEntity, model, ifcDataStore]);
|
|
662
|
+
/** True when at least one task in the current schedule controls this entity —
|
|
663
|
+
* used to keep the Inspector's empty-state from hiding a populated card.
|
|
664
|
+
* Federation-aware: matches globalId first (see `ScheduleCard`). */
|
|
665
|
+
const hasScheduleForSelection = useMemo(() => {
|
|
666
|
+
if (!selectedEntity || !scheduleData || scheduleData.tasks.length === 0) return false;
|
|
667
|
+
const expressId = selectedEntity.expressId;
|
|
668
|
+
const gid = selectedEntityGlobalId;
|
|
669
|
+
for (const task of scheduleData.tasks) {
|
|
670
|
+
const taskHasGlobalIds = task.productGlobalIds.some(Boolean);
|
|
671
|
+
if (gid && taskHasGlobalIds) {
|
|
672
|
+
if (task.productGlobalIds.includes(gid)) return true;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (expressId > 0 && task.productExpressIds.includes(expressId)) return true;
|
|
676
|
+
}
|
|
677
|
+
return false;
|
|
678
|
+
}, [selectedEntity, scheduleData, selectedEntityGlobalId]);
|
|
548
679
|
|
|
549
680
|
// Extract georeferencing info for the model (used in coordinates section)
|
|
550
681
|
const georef = useMemo(() => {
|
|
@@ -829,15 +960,19 @@ export function PropertiesPanel() {
|
|
|
829
960
|
|
|
830
961
|
const renderedEntityType = isNativeLazySelection
|
|
831
962
|
? (nativeDetails?.summary.type ?? 'Loading...')
|
|
832
|
-
: (entityNode?.type ?? 'Unknown');
|
|
963
|
+
: (entityNode?.type ?? overlayEntity?.type ?? 'Unknown');
|
|
833
964
|
const renderedEntityName = isNativeLazySelection
|
|
834
965
|
? (nativeDetails?.summary.name ?? `#${selectedEntity?.expressId ?? ''}`)
|
|
835
|
-
: entityNode?.name;
|
|
966
|
+
: (entityNode?.name ?? overlayAttr(2) ?? undefined);
|
|
836
967
|
const renderedEntityGlobalId = isNativeLazySelection
|
|
837
968
|
? (nativeDetails?.summary.globalId ?? null)
|
|
838
|
-
: entityNode?.globalId;
|
|
839
|
-
const renderedEntityDescription = isNativeLazySelection
|
|
840
|
-
|
|
969
|
+
: (entityNode?.globalId ?? overlayAttr(0));
|
|
970
|
+
const renderedEntityDescription = isNativeLazySelection
|
|
971
|
+
? undefined
|
|
972
|
+
: (entityNode?.description ?? overlayAttr(3) ?? undefined);
|
|
973
|
+
const renderedEntityObjectType = isNativeLazySelection
|
|
974
|
+
? undefined
|
|
975
|
+
: (entityNode?.objectType ?? overlayAttr(4) ?? undefined);
|
|
841
976
|
const renderedSpatialInfo = isNativeLazySelection ? nativeSpatialInfo : spatialInfo;
|
|
842
977
|
const renderedOccurrenceProperties = isNativeLazySelection ? nativeOccurrenceProperties : occurrenceProperties;
|
|
843
978
|
const renderedInheritedTypeProperties = isNativeLazySelection ? [] : inheritedTypeProperties;
|
|
@@ -910,7 +1045,12 @@ export function PropertiesPanel() {
|
|
|
910
1045
|
);
|
|
911
1046
|
}
|
|
912
1047
|
|
|
913
|
-
|
|
1048
|
+
// Newly-created/duplicated entities live only in the mutation overlay,
|
|
1049
|
+
// so the synthesized attributes + Raw STEP tab fall back to
|
|
1050
|
+
// `overlayEntity` when `entityNode` is empty. Without including
|
|
1051
|
+
// `overlayEntity` here the panel collapses to the model-metadata
|
|
1052
|
+
// view the moment a fresh add lands.
|
|
1053
|
+
if (!selectedEntityId || (!isNativeLazySelection && (!modelQuery || (!entityNode && !overlayEntity)))) {
|
|
914
1054
|
// Show model metadata when a single model is loaded and nothing selected.
|
|
915
1055
|
// Handles both federated models (models.size >= 1) and legacy single-model path (models.size === 0).
|
|
916
1056
|
if (models.size === 1) {
|
|
@@ -937,7 +1077,7 @@ export function PropertiesPanel() {
|
|
|
937
1077
|
return (
|
|
938
1078
|
<div className="h-full flex flex-col border-l-2 border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-black">
|
|
939
1079
|
<div className="p-3 border-b-2 border-zinc-200 dark:border-zinc-800 bg-white dark:bg-black">
|
|
940
|
-
<h2 className="font-bold uppercase tracking-wider text-xs text-zinc-900 dark:text-zinc-100">
|
|
1080
|
+
<h2 className="font-bold uppercase tracking-wider text-xs text-zinc-900 dark:text-zinc-100">Inspector</h2>
|
|
941
1081
|
</div>
|
|
942
1082
|
<div className="flex-1 flex flex-col items-center justify-center text-center p-6 bg-white dark:bg-black">
|
|
943
1083
|
<div className="w-16 h-16 border-2 border-dashed border-zinc-300 dark:border-zinc-800 flex items-center justify-center mb-4 bg-zinc-100 dark:bg-zinc-950">
|
|
@@ -1266,10 +1406,32 @@ export function PropertiesPanel() {
|
|
|
1266
1406
|
<Tag className="h-3 w-3 shrink-0 panel-compact-icon" />
|
|
1267
1407
|
<span className="panel-compact-text">bSDD</span>
|
|
1268
1408
|
</TabsTrigger>
|
|
1409
|
+
<TabsTrigger
|
|
1410
|
+
value="raw-step"
|
|
1411
|
+
title="Raw STEP — developer view of positional arguments"
|
|
1412
|
+
className="properties-tab-trigger raw-step-tab-trigger shrink-0 grow-0 px-2 font-mono"
|
|
1413
|
+
>
|
|
1414
|
+
{/* Bracket glyphs read as "code" without an icon dependency,
|
|
1415
|
+
stay readable at 9px, and free up width for the three
|
|
1416
|
+
primary tabs to keep their text visible at the default
|
|
1417
|
+
panel size. */}
|
|
1418
|
+
<span aria-hidden className="text-[10px] leading-none tracking-tight"></></span>
|
|
1419
|
+
<span className="sr-only">Raw STEP</span>
|
|
1420
|
+
</TabsTrigger>
|
|
1269
1421
|
</TabsList>
|
|
1270
1422
|
|
|
1271
1423
|
<ScrollArea className="flex-1 bg-white dark:bg-black">
|
|
1272
1424
|
<TabsContent value="properties" className="m-0 p-3 overflow-hidden">
|
|
1425
|
+
{/* Task edit card — renders when exactly one Gantt task is
|
|
1426
|
+
selected. Shown above any entity properties because the
|
|
1427
|
+
user's attention shifted to editing the task, not the 3D
|
|
1428
|
+
element. Other tabs (quantities / relationships / bSDD)
|
|
1429
|
+
still show entity content regardless. */}
|
|
1430
|
+
{singleSelectedTaskGlobalId && (
|
|
1431
|
+
<div className="mb-3">
|
|
1432
|
+
<TaskEditCard taskGlobalId={singleSelectedTaskGlobalId} />
|
|
1433
|
+
</div>
|
|
1434
|
+
)}
|
|
1273
1435
|
{/* Edit toolbar - only shown when edit mode is active */}
|
|
1274
1436
|
{editMode && selectedEntity && !isNativeLazySelection && (
|
|
1275
1437
|
<EditToolbar
|
|
@@ -1281,7 +1443,12 @@ export function PropertiesPanel() {
|
|
|
1281
1443
|
schemaVersion={activeDataStore?.schemaVersion}
|
|
1282
1444
|
/>
|
|
1283
1445
|
)}
|
|
1284
|
-
{renderedMergedProperties.length === 0
|
|
1446
|
+
{renderedMergedProperties.length === 0
|
|
1447
|
+
&& renderedClassifications.length === 0
|
|
1448
|
+
&& !renderedMaterialInfo
|
|
1449
|
+
&& renderedDocuments.length === 0
|
|
1450
|
+
&& !renderedEntityRelationships
|
|
1451
|
+
&& !hasScheduleForSelection ? (
|
|
1285
1452
|
<p className="text-sm text-zinc-500 dark:text-zinc-500 text-center py-8 font-mono">No property sets</p>
|
|
1286
1453
|
) : (
|
|
1287
1454
|
<div className="space-y-3 w-full overflow-hidden">
|
|
@@ -1377,6 +1544,21 @@ export function PropertiesPanel() {
|
|
|
1377
1544
|
<RelationshipsCard relationships={renderedEntityRelationships} />
|
|
1378
1545
|
</>
|
|
1379
1546
|
)}
|
|
1547
|
+
|
|
1548
|
+
{/* 4D / Construction schedule — controlling tasks for this entity.
|
|
1549
|
+
Gated on `hasScheduleForSelection` so the separator above
|
|
1550
|
+
doesn't render on its own when ScheduleCard would return null. */}
|
|
1551
|
+
{selectedEntity && scheduleData && hasScheduleForSelection && (
|
|
1552
|
+
<>
|
|
1553
|
+
<div className="border-t border-zinc-200 dark:border-zinc-800 pt-2 mt-2" />
|
|
1554
|
+
<ScheduleCard
|
|
1555
|
+
scheduleData={scheduleData}
|
|
1556
|
+
selectedExpressId={selectedEntity.expressId}
|
|
1557
|
+
selectedGlobalId={selectedEntityGlobalId}
|
|
1558
|
+
isGenerated={scheduleIsGenerated}
|
|
1559
|
+
/>
|
|
1560
|
+
</>
|
|
1561
|
+
)}
|
|
1380
1562
|
</div>
|
|
1381
1563
|
)}
|
|
1382
1564
|
</TabsContent>
|
|
@@ -1407,6 +1589,24 @@ export function PropertiesPanel() {
|
|
|
1407
1589
|
/>
|
|
1408
1590
|
)}
|
|
1409
1591
|
</TabsContent>
|
|
1592
|
+
|
|
1593
|
+
<TabsContent value="raw-step" className="m-0 p-3 overflow-hidden">
|
|
1594
|
+
{selectedEntity && !isNativeLazySelection ? (
|
|
1595
|
+
<RawStepCard
|
|
1596
|
+
modelId={selectedEntity.modelId === 'legacy' ? '__legacy__' : selectedEntity.modelId}
|
|
1597
|
+
entityId={selectedEntity.expressId}
|
|
1598
|
+
entityType={entityType}
|
|
1599
|
+
dataStore={activeDataStore ?? null}
|
|
1600
|
+
enableEditing={editMode}
|
|
1601
|
+
/>
|
|
1602
|
+
) : (
|
|
1603
|
+
<p className="text-sm text-zinc-500 dark:text-zinc-500 text-center py-8 font-mono">
|
|
1604
|
+
{isNativeLazySelection
|
|
1605
|
+
? 'Raw STEP is not available for native-metadata selections'
|
|
1606
|
+
: 'Select an entity to inspect raw STEP arguments'}
|
|
1607
|
+
</p>
|
|
1608
|
+
)}
|
|
1609
|
+
</TabsContent>
|
|
1410
1610
|
</ScrollArea>
|
|
1411
1611
|
</Tabs>
|
|
1412
1612
|
</div>
|