@ifc-lite/viewer 1.7.0 → 1.9.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/CHANGELOG.md +88 -0
- package/dist/assets/{Arrow.dom-BGPQieQQ.js → Arrow.dom-CusgkT03.js} +1 -1
- package/dist/assets/browser-BXNIkE8a.js +694 -0
- package/dist/assets/emscripten-module-BTRCZGcB.wasm +0 -0
- package/dist/assets/emscripten-module-CGIn_cMh.wasm +0 -0
- package/dist/assets/emscripten-module-DYvzWiHh.wasm +0 -0
- package/dist/assets/emscripten-module-NWak2PoB.wasm +0 -0
- package/dist/assets/emscripten-module.browser-CY5t0Vfq.js +1 -0
- package/dist/assets/esbuild-COv63sf-.js +1 -0
- package/dist/assets/esbuild-Cpd5nU_H.wasm +0 -0
- package/dist/assets/ffi-DlhRHxHv.js +1 -0
- package/dist/assets/index-6Mr3byM-.js +216 -0
- package/dist/assets/index-CGbokkQ9.css +1 -0
- package/dist/assets/index-huvR-kGC.js +98305 -0
- package/dist/assets/module-6F3E5H7Y-tx0BadV3.js +6 -0
- package/dist/assets/{native-bridge-DD0SNyQ5.js → native-bridge-DsHOKdgD.js} +1 -1
- package/dist/assets/{wasm-bridge-D54YMO7X.js → wasm-bridge-Bd73HXn-.js} +1 -1
- package/dist/index.html +12 -3
- package/index.html +10 -1
- package/package.json +30 -21
- package/src/App.tsx +6 -1
- package/src/components/ui/dialog.tsx +8 -6
- package/src/components/viewer/CodeEditor.tsx +309 -0
- package/src/components/viewer/CommandPalette.tsx +597 -0
- package/src/components/viewer/Drawing2DCanvas.tsx +364 -1
- package/src/components/viewer/EntityContextMenu.tsx +47 -20
- package/src/components/viewer/ExportDialog.tsx +166 -17
- package/src/components/viewer/HierarchyPanel.tsx +3 -1
- package/src/components/viewer/LensPanel.tsx +848 -85
- package/src/components/viewer/MainToolbar.tsx +145 -84
- package/src/components/viewer/ScriptPanel.tsx +416 -0
- package/src/components/viewer/Section2DPanel.tsx +269 -29
- package/src/components/viewer/TextAnnotationEditor.tsx +112 -0
- package/src/components/viewer/ViewerLayout.tsx +63 -11
- package/src/components/viewer/Viewport.tsx +58 -23
- package/src/components/viewer/ViewportContainer.tsx +2 -0
- package/src/components/viewer/hierarchy/HierarchyNode.tsx +1 -1
- package/src/components/viewer/hierarchy/types.ts +1 -1
- package/src/components/viewer/lists/ListResultsTable.tsx +53 -19
- package/src/components/viewer/tools/cloudPathGenerator.test.ts +118 -0
- package/src/components/viewer/tools/cloudPathGenerator.ts +275 -0
- package/src/components/viewer/tools/computePolygonArea.test.ts +165 -0
- package/src/components/viewer/tools/computePolygonArea.ts +72 -0
- package/src/components/viewer/useGeometryStreaming.ts +25 -5
- package/src/hooks/ids/idsExportService.ts +1 -1
- package/src/hooks/useAnnotation2D.ts +551 -0
- package/src/hooks/useDrawingExport.ts +83 -1
- package/src/hooks/useKeyboardShortcuts.ts +114 -14
- package/src/hooks/useLens.ts +40 -55
- package/src/hooks/useLensDiscovery.ts +46 -0
- package/src/hooks/useModelSelection.ts +5 -22
- package/src/hooks/useSandbox.ts +113 -0
- package/src/index.css +7 -1
- package/src/lib/lens/adapter.ts +127 -1
- package/src/lib/lists/columnToAutoColor.ts +33 -0
- package/src/lib/recent-files.ts +122 -0
- package/src/lib/scripts/persistence.ts +132 -0
- package/src/lib/scripts/templates/bim-globals.d.ts +111 -0
- package/src/lib/scripts/templates/data-quality-audit.ts +149 -0
- package/src/lib/scripts/templates/envelope-check.ts +164 -0
- package/src/lib/scripts/templates/federation-compare.ts +189 -0
- package/src/lib/scripts/templates/fire-safety-check.ts +161 -0
- package/src/lib/scripts/templates/mep-equipment-schedule.ts +175 -0
- package/src/lib/scripts/templates/quantity-takeoff.ts +145 -0
- package/src/lib/scripts/templates/reset-view.ts +6 -0
- package/src/lib/scripts/templates/space-validation.ts +189 -0
- package/src/lib/scripts/templates/tsconfig.json +13 -0
- package/src/lib/scripts/templates.ts +86 -0
- package/src/sdk/BimProvider.tsx +50 -0
- package/src/sdk/adapters/export-adapter.ts +283 -0
- package/src/sdk/adapters/lens-adapter.ts +44 -0
- package/src/sdk/adapters/model-adapter.ts +32 -0
- package/src/sdk/adapters/model-compat.ts +80 -0
- package/src/sdk/adapters/mutate-adapter.ts +45 -0
- package/src/sdk/adapters/query-adapter.ts +241 -0
- package/src/sdk/adapters/selection-adapter.ts +29 -0
- package/src/sdk/adapters/spatial-adapter.ts +37 -0
- package/src/sdk/adapters/types.ts +11 -0
- package/src/sdk/adapters/viewer-adapter.ts +103 -0
- package/src/sdk/adapters/visibility-adapter.ts +61 -0
- package/src/sdk/local-backend.ts +144 -0
- package/src/sdk/useBimHost.ts +69 -0
- package/src/store/constants.ts +10 -2
- package/src/store/index.ts +28 -2
- package/src/store/resolveEntityRef.ts +44 -0
- package/src/store/slices/drawing2DSlice.ts +321 -0
- package/src/store/slices/lensSlice.ts +46 -4
- package/src/store/slices/pinboardSlice.ts +171 -42
- package/src/store/slices/scriptSlice.ts +218 -0
- package/src/store/slices/uiSlice.ts +2 -0
- package/src/store.ts +3 -0
- package/tsconfig.json +5 -2
- package/vite.config.ts +8 -0
- package/dist/assets/index-dgdgiQ9p.js +0 -75456
- package/dist/assets/index-yTqs8kgX.css +0 -1
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import React, { useCallback, useRef, useState, useEffect, useMemo } from 'react';
|
|
15
|
-
import { X, Download, Eye, EyeOff, Maximize2, ZoomIn, ZoomOut, Loader2, Printer, GripVertical, MoreHorizontal, RefreshCw, Pin, PinOff, Palette, Ruler, Trash2, FileText, Shapes, Box } from 'lucide-react';
|
|
15
|
+
import { X, Download, Eye, EyeOff, Maximize2, ZoomIn, ZoomOut, Loader2, Printer, GripVertical, MoreHorizontal, RefreshCw, Pin, PinOff, Palette, Ruler, Trash2, FileText, Shapes, Box, PenTool, Hexagon, Type, Cloud, MousePointer2 } from 'lucide-react';
|
|
16
16
|
import { Button } from '@/components/ui/button';
|
|
17
17
|
import {
|
|
18
18
|
DropdownMenu,
|
|
@@ -28,9 +28,11 @@ import { type GeometryResult } from '@ifc-lite/geometry';
|
|
|
28
28
|
import { DrawingSettingsPanel } from './DrawingSettingsPanel';
|
|
29
29
|
import { SheetSetupPanel } from './SheetSetupPanel';
|
|
30
30
|
import { TitleBlockEditor } from './TitleBlockEditor';
|
|
31
|
+
import { TextAnnotationEditor } from './TextAnnotationEditor';
|
|
31
32
|
import { Drawing2DCanvas } from './Drawing2DCanvas';
|
|
32
33
|
import { useDrawingGeneration } from '@/hooks/useDrawingGeneration';
|
|
33
34
|
import { useMeasure2D } from '@/hooks/useMeasure2D';
|
|
35
|
+
import { useAnnotation2D } from '@/hooks/useAnnotation2D';
|
|
34
36
|
import { useViewControls } from '@/hooks/useViewControls';
|
|
35
37
|
import { useDrawingExport } from '@/hooks/useDrawingExport';
|
|
36
38
|
|
|
@@ -98,6 +100,39 @@ export function Section2DPanel({
|
|
|
98
100
|
const measure2DSnapPoint = useViewerStore((s) => s.measure2DSnapPoint);
|
|
99
101
|
const setMeasure2DSnapPoint = useViewerStore((s) => s.setMeasure2DSnapPoint);
|
|
100
102
|
|
|
103
|
+
// Annotation tool state
|
|
104
|
+
const annotation2DActiveTool = useViewerStore((s) => s.annotation2DActiveTool);
|
|
105
|
+
const setAnnotation2DActiveTool = useViewerStore((s) => s.setAnnotation2DActiveTool);
|
|
106
|
+
const annotation2DCursorPos = useViewerStore((s) => s.annotation2DCursorPos);
|
|
107
|
+
const setAnnotation2DCursorPos = useViewerStore((s) => s.setAnnotation2DCursorPos);
|
|
108
|
+
// Polygon area state
|
|
109
|
+
const polygonArea2DPoints = useViewerStore((s) => s.polygonArea2DPoints);
|
|
110
|
+
const polygonArea2DResults = useViewerStore((s) => s.polygonArea2DResults);
|
|
111
|
+
const addPolygonArea2DPoint = useViewerStore((s) => s.addPolygonArea2DPoint);
|
|
112
|
+
const completePolygonArea2D = useViewerStore((s) => s.completePolygonArea2D);
|
|
113
|
+
const cancelPolygonArea2D = useViewerStore((s) => s.cancelPolygonArea2D);
|
|
114
|
+
const clearPolygonArea2DResults = useViewerStore((s) => s.clearPolygonArea2DResults);
|
|
115
|
+
// Text annotation state
|
|
116
|
+
const textAnnotations2D = useViewerStore((s) => s.textAnnotations2D);
|
|
117
|
+
const textAnnotation2DEditing = useViewerStore((s) => s.textAnnotation2DEditing);
|
|
118
|
+
const addTextAnnotation2D = useViewerStore((s) => s.addTextAnnotation2D);
|
|
119
|
+
const updateTextAnnotation2D = useViewerStore((s) => s.updateTextAnnotation2D);
|
|
120
|
+
const removeTextAnnotation2D = useViewerStore((s) => s.removeTextAnnotation2D);
|
|
121
|
+
const setTextAnnotation2DEditing = useViewerStore((s) => s.setTextAnnotation2DEditing);
|
|
122
|
+
// Cloud annotation state
|
|
123
|
+
const cloudAnnotation2DPoints = useViewerStore((s) => s.cloudAnnotation2DPoints);
|
|
124
|
+
const cloudAnnotations2D = useViewerStore((s) => s.cloudAnnotations2D);
|
|
125
|
+
const addCloudAnnotation2DPoint = useViewerStore((s) => s.addCloudAnnotation2DPoint);
|
|
126
|
+
const completeCloudAnnotation2D = useViewerStore((s) => s.completeCloudAnnotation2D);
|
|
127
|
+
const cancelCloudAnnotation2D = useViewerStore((s) => s.cancelCloudAnnotation2D);
|
|
128
|
+
// Selection
|
|
129
|
+
const selectedAnnotation2D = useViewerStore((s) => s.selectedAnnotation2D);
|
|
130
|
+
const setSelectedAnnotation2D = useViewerStore((s) => s.setSelectedAnnotation2D);
|
|
131
|
+
const deleteSelectedAnnotation2D = useViewerStore((s) => s.deleteSelectedAnnotation2D);
|
|
132
|
+
const moveAnnotation2D = useViewerStore((s) => s.moveAnnotation2D);
|
|
133
|
+
// Bulk
|
|
134
|
+
const clearAllAnnotations2D = useViewerStore((s) => s.clearAllAnnotations2D);
|
|
135
|
+
|
|
101
136
|
const sectionPlane = useViewerStore((s) => s.sectionPlane);
|
|
102
137
|
const activeTool = useViewerStore((s) => s.activeTool);
|
|
103
138
|
const models = useViewerStore((s) => s.models);
|
|
@@ -228,7 +263,7 @@ export function Section2DPanel({
|
|
|
228
263
|
isPinned, cachedSheetTransformRef,
|
|
229
264
|
});
|
|
230
265
|
|
|
231
|
-
const
|
|
266
|
+
const measureHandlers = useMeasure2D({
|
|
232
267
|
drawing, viewTransform, setViewTransform, sectionAxis: sectionPlane.axis, containerRef,
|
|
233
268
|
measure2DMode, measure2DStart, measure2DCurrent,
|
|
234
269
|
measure2DShiftLocked, measure2DLockedAxis,
|
|
@@ -236,10 +271,67 @@ export function Section2DPanel({
|
|
|
236
271
|
setMeasure2DSnapPoint, cancelMeasure2D, completeMeasure2D,
|
|
237
272
|
});
|
|
238
273
|
|
|
274
|
+
const annotationHandlers = useAnnotation2D({
|
|
275
|
+
drawing, viewTransform, sectionAxis: sectionPlane.axis, containerRef,
|
|
276
|
+
activeTool: annotation2DActiveTool, setActiveTool: setAnnotation2DActiveTool,
|
|
277
|
+
polygonArea2DPoints, addPolygonArea2DPoint, completePolygonArea2D, cancelPolygonArea2D,
|
|
278
|
+
textAnnotations2D, addTextAnnotation2D, setTextAnnotation2DEditing,
|
|
279
|
+
cloudAnnotation2DPoints, cloudAnnotations2D, addCloudAnnotation2DPoint, completeCloudAnnotation2D, cancelCloudAnnotation2D,
|
|
280
|
+
measure2DResults, polygonArea2DResults,
|
|
281
|
+
selectedAnnotation2D, setSelectedAnnotation2D, deleteSelectedAnnotation2D, moveAnnotation2D,
|
|
282
|
+
setAnnotation2DCursorPos, setMeasure2DSnapPoint,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Unified mouse handlers that dispatch to the right tool
|
|
286
|
+
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
|
287
|
+
if (annotation2DActiveTool === 'measure') {
|
|
288
|
+
measureHandlers.handleMouseDown(e);
|
|
289
|
+
} else if (annotation2DActiveTool === 'none') {
|
|
290
|
+
// Try annotation selection/drag first; if it consumed the click, don't pan
|
|
291
|
+
const consumed = annotationHandlers.handleMouseDown(e);
|
|
292
|
+
if (!consumed) {
|
|
293
|
+
measureHandlers.handleMouseDown(e);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
annotationHandlers.handleMouseDown(e);
|
|
297
|
+
}
|
|
298
|
+
}, [annotation2DActiveTool, measureHandlers, annotationHandlers]);
|
|
299
|
+
|
|
300
|
+
const handleMouseMove = useCallback((e: React.MouseEvent) => {
|
|
301
|
+
// If dragging an annotation, let the annotation handler handle it
|
|
302
|
+
if (annotationHandlers.isDraggingRef.current) {
|
|
303
|
+
annotationHandlers.handleMouseMove(e);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (annotation2DActiveTool === 'measure' || annotation2DActiveTool === 'none') {
|
|
307
|
+
measureHandlers.handleMouseMove(e);
|
|
308
|
+
} else {
|
|
309
|
+
annotationHandlers.handleMouseMove(e);
|
|
310
|
+
}
|
|
311
|
+
}, [annotation2DActiveTool, measureHandlers, annotationHandlers]);
|
|
312
|
+
|
|
313
|
+
const handleMouseUp = useCallback((e: React.MouseEvent) => {
|
|
314
|
+
annotationHandlers.handleMouseUp(e);
|
|
315
|
+
measureHandlers.handleMouseUp();
|
|
316
|
+
}, [measureHandlers, annotationHandlers]);
|
|
317
|
+
|
|
318
|
+
const handleMouseLeave = useCallback(() => {
|
|
319
|
+
measureHandlers.handleMouseLeave();
|
|
320
|
+
}, [measureHandlers]);
|
|
321
|
+
|
|
322
|
+
const handleMouseEnter = useCallback((e: React.MouseEvent) => {
|
|
323
|
+
measureHandlers.handleMouseEnter(e);
|
|
324
|
+
}, [measureHandlers]);
|
|
325
|
+
|
|
326
|
+
const handleDoubleClick = useCallback((e: React.MouseEvent) => {
|
|
327
|
+
annotationHandlers.handleDoubleClick(e);
|
|
328
|
+
}, [annotationHandlers]);
|
|
329
|
+
|
|
239
330
|
const { formatDistance, handleExportSVG, handlePrint } = useDrawingExport({
|
|
240
331
|
drawing, displayOptions, sectionPlane, activePresetId,
|
|
241
332
|
entityColorMap, overridesEnabled, overrideEngine,
|
|
242
|
-
measure2DResults,
|
|
333
|
+
measure2DResults, polygonArea2DResults, textAnnotations2D, cloudAnnotations2D,
|
|
334
|
+
sheetEnabled, activeSheet,
|
|
243
335
|
});
|
|
244
336
|
|
|
245
337
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -271,6 +363,42 @@ export function Section2DPanel({
|
|
|
271
363
|
setIsPinned((prev) => !prev);
|
|
272
364
|
}, []);
|
|
273
365
|
|
|
366
|
+
// Text editor handlers
|
|
367
|
+
const handleTextConfirm = useCallback((id: string, text: string) => {
|
|
368
|
+
updateTextAnnotation2D(id, { text });
|
|
369
|
+
setTextAnnotation2DEditing(null);
|
|
370
|
+
}, [updateTextAnnotation2D, setTextAnnotation2DEditing]);
|
|
371
|
+
|
|
372
|
+
const handleTextCancel = useCallback((id: string) => {
|
|
373
|
+
// If text is empty (just created), remove it
|
|
374
|
+
const annotation = textAnnotations2D.find((a) => a.id === id);
|
|
375
|
+
if (annotation && !annotation.text.trim()) {
|
|
376
|
+
removeTextAnnotation2D(id);
|
|
377
|
+
}
|
|
378
|
+
setTextAnnotation2DEditing(null);
|
|
379
|
+
}, [textAnnotations2D, removeTextAnnotation2D, setTextAnnotation2DEditing]);
|
|
380
|
+
|
|
381
|
+
// Check if any annotations exist
|
|
382
|
+
const hasAnnotations = measure2DResults.length > 0 ||
|
|
383
|
+
polygonArea2DResults.length > 0 ||
|
|
384
|
+
textAnnotations2D.length > 0 ||
|
|
385
|
+
cloudAnnotations2D.length > 0;
|
|
386
|
+
|
|
387
|
+
// Cursor style based on active tool
|
|
388
|
+
const cursorClass = useMemo(() => {
|
|
389
|
+
if (selectedAnnotation2D && annotation2DActiveTool === 'none') return 'cursor-move';
|
|
390
|
+
switch (annotation2DActiveTool) {
|
|
391
|
+
case 'measure':
|
|
392
|
+
case 'polygon-area':
|
|
393
|
+
case 'cloud':
|
|
394
|
+
return 'cursor-crosshair';
|
|
395
|
+
case 'text':
|
|
396
|
+
return 'cursor-text';
|
|
397
|
+
default:
|
|
398
|
+
return 'cursor-grab active:cursor-grabbing';
|
|
399
|
+
}
|
|
400
|
+
}, [annotation2DActiveTool, selectedAnnotation2D]);
|
|
401
|
+
|
|
274
402
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
275
403
|
// RESIZE HANDLING
|
|
276
404
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -400,25 +528,55 @@ export function Section2DPanel({
|
|
|
400
528
|
{displayOptions.useSymbolicRepresentations ? <Shapes className="h-4 w-4" /> : <Box className="h-4 w-4" />}
|
|
401
529
|
</Button>
|
|
402
530
|
|
|
403
|
-
{/*
|
|
404
|
-
<
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
531
|
+
{/* Annotation Tools Dropdown */}
|
|
532
|
+
<DropdownMenu>
|
|
533
|
+
<DropdownMenuTrigger asChild>
|
|
534
|
+
<Button
|
|
535
|
+
variant={annotation2DActiveTool !== 'none' ? 'default' : 'ghost'}
|
|
536
|
+
size="icon-sm"
|
|
537
|
+
title="Annotation tools"
|
|
538
|
+
>
|
|
539
|
+
<PenTool className="h-4 w-4" />
|
|
540
|
+
</Button>
|
|
541
|
+
</DropdownMenuTrigger>
|
|
542
|
+
<DropdownMenuContent align="start">
|
|
543
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool('none')}>
|
|
544
|
+
<MousePointer2 className="h-4 w-4 mr-2" />
|
|
545
|
+
Select / Pan
|
|
546
|
+
{annotation2DActiveTool === 'none' && <span className="ml-auto text-xs text-primary">Active</span>}
|
|
547
|
+
</DropdownMenuItem>
|
|
548
|
+
<DropdownMenuSeparator />
|
|
549
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool(annotation2DActiveTool === 'measure' ? 'none' : 'measure')}>
|
|
550
|
+
<Ruler className="h-4 w-4 mr-2" />
|
|
551
|
+
Distance Measure
|
|
552
|
+
{annotation2DActiveTool === 'measure' && <span className="ml-auto text-xs text-primary">Active</span>}
|
|
553
|
+
</DropdownMenuItem>
|
|
554
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool(annotation2DActiveTool === 'polygon-area' ? 'none' : 'polygon-area')}>
|
|
555
|
+
<Hexagon className="h-4 w-4 mr-2" />
|
|
556
|
+
Area Measure
|
|
557
|
+
{annotation2DActiveTool === 'polygon-area' && <span className="ml-auto text-xs text-primary">Active</span>}
|
|
558
|
+
</DropdownMenuItem>
|
|
559
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool(annotation2DActiveTool === 'text' ? 'none' : 'text')}>
|
|
560
|
+
<Type className="h-4 w-4 mr-2" />
|
|
561
|
+
Text Box
|
|
562
|
+
{annotation2DActiveTool === 'text' && <span className="ml-auto text-xs text-primary">Active</span>}
|
|
563
|
+
</DropdownMenuItem>
|
|
564
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool(annotation2DActiveTool === 'cloud' ? 'none' : 'cloud')}>
|
|
565
|
+
<Cloud className="h-4 w-4 mr-2" />
|
|
566
|
+
Revision Cloud
|
|
567
|
+
{annotation2DActiveTool === 'cloud' && <span className="ml-auto text-xs text-primary">Active</span>}
|
|
568
|
+
</DropdownMenuItem>
|
|
569
|
+
{hasAnnotations && (
|
|
570
|
+
<>
|
|
571
|
+
<DropdownMenuSeparator />
|
|
572
|
+
<DropdownMenuItem onClick={clearAllAnnotations2D}>
|
|
573
|
+
<Trash2 className="h-4 w-4 mr-2" />
|
|
574
|
+
Clear All Annotations
|
|
575
|
+
</DropdownMenuItem>
|
|
576
|
+
</>
|
|
577
|
+
)}
|
|
578
|
+
</DropdownMenuContent>
|
|
579
|
+
</DropdownMenu>
|
|
422
580
|
|
|
423
581
|
{/* Graphic Override Settings */}
|
|
424
582
|
<Button
|
|
@@ -537,14 +695,30 @@ export function Section2DPanel({
|
|
|
537
695
|
{displayOptions.useSymbolicRepresentations ? <Shapes className="h-4 w-4 mr-2" /> : <Box className="h-4 w-4 mr-2" />}
|
|
538
696
|
{displayOptions.useSymbolicRepresentations ? 'Symbolic (Plan)' : 'Section Cut (Body)'}
|
|
539
697
|
</DropdownMenuItem>
|
|
540
|
-
<DropdownMenuItem onClick={
|
|
698
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool('none')}>
|
|
699
|
+
<MousePointer2 className="h-4 w-4 mr-2" />
|
|
700
|
+
Select / Pan {annotation2DActiveTool === 'none' ? '(On)' : ''}
|
|
701
|
+
</DropdownMenuItem>
|
|
702
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool(annotation2DActiveTool === 'measure' ? 'none' : 'measure')}>
|
|
541
703
|
<Ruler className="h-4 w-4 mr-2" />
|
|
542
|
-
Measure {
|
|
704
|
+
Distance Measure {annotation2DActiveTool === 'measure' ? '(On)' : ''}
|
|
705
|
+
</DropdownMenuItem>
|
|
706
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool(annotation2DActiveTool === 'polygon-area' ? 'none' : 'polygon-area')}>
|
|
707
|
+
<Hexagon className="h-4 w-4 mr-2" />
|
|
708
|
+
Area Measure {annotation2DActiveTool === 'polygon-area' ? '(On)' : ''}
|
|
709
|
+
</DropdownMenuItem>
|
|
710
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool(annotation2DActiveTool === 'text' ? 'none' : 'text')}>
|
|
711
|
+
<Type className="h-4 w-4 mr-2" />
|
|
712
|
+
Text Box {annotation2DActiveTool === 'text' ? '(On)' : ''}
|
|
713
|
+
</DropdownMenuItem>
|
|
714
|
+
<DropdownMenuItem onClick={() => setAnnotation2DActiveTool(annotation2DActiveTool === 'cloud' ? 'none' : 'cloud')}>
|
|
715
|
+
<Cloud className="h-4 w-4 mr-2" />
|
|
716
|
+
Revision Cloud {annotation2DActiveTool === 'cloud' ? '(On)' : ''}
|
|
543
717
|
</DropdownMenuItem>
|
|
544
|
-
{
|
|
545
|
-
<DropdownMenuItem onClick={
|
|
718
|
+
{hasAnnotations && (
|
|
719
|
+
<DropdownMenuItem onClick={clearAllAnnotations2D}>
|
|
546
720
|
<Trash2 className="h-4 w-4 mr-2" />
|
|
547
|
-
Clear
|
|
721
|
+
Clear All Annotations
|
|
548
722
|
</DropdownMenuItem>
|
|
549
723
|
)}
|
|
550
724
|
<DropdownMenuSeparator />
|
|
@@ -602,13 +776,13 @@ export function Section2DPanel({
|
|
|
602
776
|
{/* Drawing Canvas */}
|
|
603
777
|
<div
|
|
604
778
|
ref={containerRef}
|
|
605
|
-
className={`relative flex-1 overflow-hidden bg-white dark:bg-zinc-950 rounded-b-lg ${
|
|
606
|
-
}`}
|
|
779
|
+
className={`relative flex-1 overflow-hidden bg-white dark:bg-zinc-950 rounded-b-lg ${cursorClass}`}
|
|
607
780
|
onMouseDown={handleMouseDown}
|
|
608
781
|
onMouseMove={handleMouseMove}
|
|
609
782
|
onMouseUp={handleMouseUp}
|
|
610
783
|
onMouseEnter={handleMouseEnter}
|
|
611
784
|
onMouseLeave={handleMouseLeave}
|
|
785
|
+
onDoubleClick={handleDoubleClick}
|
|
612
786
|
>
|
|
613
787
|
{status === 'generating' && (
|
|
614
788
|
<div className="absolute inset-0 flex flex-col items-center justify-center bg-background/80">
|
|
@@ -658,6 +832,15 @@ export function Section2DPanel({
|
|
|
658
832
|
sectionAxis={sectionPlane.axis}
|
|
659
833
|
isPinned={isPinned}
|
|
660
834
|
cachedSheetTransformRef={cachedSheetTransformRef}
|
|
835
|
+
annotation2DActiveTool={annotation2DActiveTool}
|
|
836
|
+
annotation2DCursorPos={annotation2DCursorPos}
|
|
837
|
+
polygonAreaPoints={polygonArea2DPoints}
|
|
838
|
+
polygonAreaResults={polygonArea2DResults}
|
|
839
|
+
textAnnotations={textAnnotations2D}
|
|
840
|
+
textAnnotationEditing={textAnnotation2DEditing}
|
|
841
|
+
cloudAnnotationPoints={cloudAnnotation2DPoints}
|
|
842
|
+
cloudAnnotations={cloudAnnotations2D}
|
|
843
|
+
selectedAnnotation={selectedAnnotation2D}
|
|
661
844
|
/>
|
|
662
845
|
{/* Subtle updating indicator - shows while regenerating without hiding the drawing */}
|
|
663
846
|
{isRegenerating && (
|
|
@@ -669,6 +852,25 @@ export function Section2DPanel({
|
|
|
669
852
|
</>
|
|
670
853
|
)}
|
|
671
854
|
|
|
855
|
+
{/* Text Annotation Editor Overlay */}
|
|
856
|
+
{textAnnotation2DEditing && (() => {
|
|
857
|
+
const editingAnnotation = textAnnotations2D.find((a) => a.id === textAnnotation2DEditing);
|
|
858
|
+
if (!editingAnnotation) return null;
|
|
859
|
+
const scaleX = sectionPlane.axis === 'side' ? -viewTransform.scale : viewTransform.scale;
|
|
860
|
+
const scaleY = sectionPlane.axis === 'down' ? viewTransform.scale : -viewTransform.scale;
|
|
861
|
+
const screenX = editingAnnotation.position.x * scaleX + viewTransform.x;
|
|
862
|
+
const screenY = editingAnnotation.position.y * scaleY + viewTransform.y;
|
|
863
|
+
return (
|
|
864
|
+
<TextAnnotationEditor
|
|
865
|
+
annotation={editingAnnotation}
|
|
866
|
+
screenX={screenX}
|
|
867
|
+
screenY={screenY}
|
|
868
|
+
onConfirm={handleTextConfirm}
|
|
869
|
+
onCancel={handleTextCancel}
|
|
870
|
+
/>
|
|
871
|
+
);
|
|
872
|
+
})()}
|
|
873
|
+
|
|
672
874
|
{/* Measure mode tip - bottom right */}
|
|
673
875
|
{measure2DMode && measure2DStart && (
|
|
674
876
|
<div className="absolute bottom-2 right-2 pointer-events-none z-10">
|
|
@@ -679,6 +881,44 @@ export function Section2DPanel({
|
|
|
679
881
|
</div>
|
|
680
882
|
)}
|
|
681
883
|
|
|
884
|
+
{/* Polygon area tip */}
|
|
885
|
+
{annotation2DActiveTool === 'polygon-area' && (
|
|
886
|
+
<div className="absolute bottom-2 right-2 pointer-events-none z-10">
|
|
887
|
+
<div className="text-[10px] text-black bg-white/80 px-1.5 py-0.5 rounded">
|
|
888
|
+
{polygonArea2DPoints.length === 0 ? 'Click to place first vertex · Hold Shift to constrain' :
|
|
889
|
+
polygonArea2DPoints.length < 3 ? `${polygonArea2DPoints.length} vertices — need at least 3 · Shift = constrain` :
|
|
890
|
+
'Double-click or click first vertex to close · Shift = constrain'}
|
|
891
|
+
</div>
|
|
892
|
+
</div>
|
|
893
|
+
)}
|
|
894
|
+
|
|
895
|
+
{/* Cloud tool tip */}
|
|
896
|
+
{annotation2DActiveTool === 'cloud' && (
|
|
897
|
+
<div className="absolute bottom-2 right-2 pointer-events-none z-10">
|
|
898
|
+
<div className="text-[10px] text-black bg-white/80 px-1.5 py-0.5 rounded">
|
|
899
|
+
{cloudAnnotation2DPoints.length === 0 ? 'Click to place first corner' : 'Click to place second corner · Shift = square'}
|
|
900
|
+
</div>
|
|
901
|
+
</div>
|
|
902
|
+
)}
|
|
903
|
+
|
|
904
|
+
{/* Text tool tip */}
|
|
905
|
+
{annotation2DActiveTool === 'text' && !textAnnotation2DEditing && (
|
|
906
|
+
<div className="absolute bottom-2 right-2 pointer-events-none z-10">
|
|
907
|
+
<div className="text-[10px] text-black bg-white/80 px-1.5 py-0.5 rounded">
|
|
908
|
+
Click to place text box
|
|
909
|
+
</div>
|
|
910
|
+
</div>
|
|
911
|
+
)}
|
|
912
|
+
|
|
913
|
+
{/* Selection tip */}
|
|
914
|
+
{selectedAnnotation2D && annotation2DActiveTool === 'none' && (
|
|
915
|
+
<div className="absolute bottom-2 right-2 pointer-events-none z-10">
|
|
916
|
+
<div className="text-[10px] text-black bg-white/80 px-1.5 py-0.5 rounded">
|
|
917
|
+
{selectedAnnotation2D.type === 'text' ? 'Del = delete · Drag to move · Double-click to edit' : 'Del = delete · Drag to move'} · Esc = deselect
|
|
918
|
+
</div>
|
|
919
|
+
</div>
|
|
920
|
+
)}
|
|
921
|
+
|
|
682
922
|
{status === 'ready' && drawing && drawing.cutPolygons.length === 0 && (!drawing.lines || drawing.lines.length === 0) && (
|
|
683
923
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
684
924
|
<div className="text-center text-muted-foreground">
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Inline text editor overlay for text annotations on the 2D drawing.
|
|
7
|
+
* Positioned absolutely over the canvas at the annotation's screen coordinates.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
|
+
import type { TextAnnotation2D } from '@/store/slices/drawing2DSlice';
|
|
12
|
+
|
|
13
|
+
interface TextAnnotationEditorProps {
|
|
14
|
+
/** The text annotation being edited */
|
|
15
|
+
annotation: TextAnnotation2D;
|
|
16
|
+
/** Screen position (top-left of the editor) */
|
|
17
|
+
screenX: number;
|
|
18
|
+
screenY: number;
|
|
19
|
+
/** Called with the new text when user confirms (Enter) */
|
|
20
|
+
onConfirm: (id: string, text: string) => void;
|
|
21
|
+
/** Called when user cancels (Escape) or submits empty text */
|
|
22
|
+
onCancel: (id: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function TextAnnotationEditor({
|
|
26
|
+
annotation,
|
|
27
|
+
screenX,
|
|
28
|
+
screenY,
|
|
29
|
+
onConfirm,
|
|
30
|
+
onCancel,
|
|
31
|
+
}: TextAnnotationEditorProps): React.ReactElement {
|
|
32
|
+
const [text, setText] = useState(annotation.text);
|
|
33
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
34
|
+
// Guard against blur firing during the initial click that created this editor.
|
|
35
|
+
// Without this, the mouseup from the placement click can steal focus from the
|
|
36
|
+
// textarea before the user has a chance to type, causing an immediate cancel.
|
|
37
|
+
const readyRef = useRef(false);
|
|
38
|
+
|
|
39
|
+
// Auto-focus on mount, but defer slightly so the originating mouseup
|
|
40
|
+
// from the placement click doesn't immediately steal focus / trigger blur.
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const timer = requestAnimationFrame(() => {
|
|
43
|
+
textareaRef.current?.focus();
|
|
44
|
+
// Mark ready after focus is established so blur handler is enabled
|
|
45
|
+
readyRef.current = true;
|
|
46
|
+
});
|
|
47
|
+
return () => cancelAnimationFrame(timer);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
51
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
const trimmed = text.trim();
|
|
54
|
+
if (trimmed) {
|
|
55
|
+
onConfirm(annotation.id, trimmed);
|
|
56
|
+
} else {
|
|
57
|
+
onCancel(annotation.id);
|
|
58
|
+
}
|
|
59
|
+
} else if (e.key === 'Escape') {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
onCancel(annotation.id);
|
|
62
|
+
}
|
|
63
|
+
// Stop propagation so the canvas doesn't receive these events
|
|
64
|
+
e.stopPropagation();
|
|
65
|
+
}, [text, annotation.id, onConfirm, onCancel]);
|
|
66
|
+
|
|
67
|
+
const handleBlur = useCallback(() => {
|
|
68
|
+
// Ignore blur events that fire before the editor is fully ready
|
|
69
|
+
// (e.g. from the originating click's mouseup stealing focus)
|
|
70
|
+
if (!readyRef.current) return;
|
|
71
|
+
|
|
72
|
+
const trimmed = text.trim();
|
|
73
|
+
if (trimmed) {
|
|
74
|
+
onConfirm(annotation.id, trimmed);
|
|
75
|
+
} else {
|
|
76
|
+
onCancel(annotation.id);
|
|
77
|
+
}
|
|
78
|
+
}, [text, annotation.id, onConfirm, onCancel]);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
className="absolute z-20 pointer-events-auto"
|
|
83
|
+
style={{
|
|
84
|
+
left: screenX,
|
|
85
|
+
top: screenY,
|
|
86
|
+
}}
|
|
87
|
+
// Prevent mousedown from propagating to canvas (which would place another annotation)
|
|
88
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
89
|
+
onClick={(e) => e.stopPropagation()}
|
|
90
|
+
>
|
|
91
|
+
<textarea
|
|
92
|
+
ref={textareaRef}
|
|
93
|
+
value={text}
|
|
94
|
+
onChange={(e) => setText(e.target.value)}
|
|
95
|
+
onKeyDown={handleKeyDown}
|
|
96
|
+
onBlur={handleBlur}
|
|
97
|
+
placeholder="Type annotation text..."
|
|
98
|
+
className="min-w-[120px] max-w-[300px] min-h-[32px] px-2 py-1 text-sm border-2 border-blue-500 rounded resize shadow-lg outline-none"
|
|
99
|
+
rows={2}
|
|
100
|
+
style={{
|
|
101
|
+
fontSize: annotation.fontSize,
|
|
102
|
+
backgroundColor: '#ffffff',
|
|
103
|
+
color: '#000000',
|
|
104
|
+
caretColor: '#000000',
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
<div className="text-[10px] text-muted-foreground mt-0.5 bg-white/80 px-1 rounded">
|
|
108
|
+
Enter to confirm · Shift+Enter for newline · Esc to cancel
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
6
6
|
import { Panel, Group as PanelGroup, Separator as PanelResizeHandle } from 'react-resizable-panels';
|
|
7
|
+
import type { PanelImperativeHandle } from 'react-resizable-panels';
|
|
7
8
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
|
8
9
|
import { MainToolbar } from './MainToolbar';
|
|
9
10
|
import { HierarchyPanel } from './HierarchyPanel';
|
|
@@ -19,6 +20,8 @@ import { BCFPanel } from './BCFPanel';
|
|
|
19
20
|
import { IDSPanel } from './IDSPanel';
|
|
20
21
|
import { LensPanel } from './LensPanel';
|
|
21
22
|
import { ListPanel } from './lists/ListPanel';
|
|
23
|
+
import { ScriptPanel } from './ScriptPanel';
|
|
24
|
+
import { CommandPalette } from './CommandPalette';
|
|
22
25
|
|
|
23
26
|
const BOTTOM_PANEL_MIN_HEIGHT = 120;
|
|
24
27
|
const BOTTOM_PANEL_DEFAULT_HEIGHT = 300;
|
|
@@ -29,6 +32,21 @@ export function ViewerLayout() {
|
|
|
29
32
|
useKeyboardShortcuts();
|
|
30
33
|
const shortcutsDialog = useKeyboardShortcutsDialog();
|
|
31
34
|
|
|
35
|
+
// Command palette state
|
|
36
|
+
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
|
37
|
+
|
|
38
|
+
// Ctrl+K / Cmd+K to open command palette
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const handler = (e: globalThis.KeyboardEvent) => {
|
|
41
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
setCommandPaletteOpen((prev) => !prev);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
window.addEventListener('keydown', handler);
|
|
47
|
+
return () => window.removeEventListener('keydown', handler);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
32
50
|
// Initialize theme on mount
|
|
33
51
|
const theme = useViewerStore((s) => s.theme);
|
|
34
52
|
const isMobile = useViewerStore((s) => s.isMobile);
|
|
@@ -45,6 +63,27 @@ export function ViewerLayout() {
|
|
|
45
63
|
const setListPanelVisible = useViewerStore((s) => s.setListPanelVisible);
|
|
46
64
|
const lensPanelVisible = useViewerStore((s) => s.lensPanelVisible);
|
|
47
65
|
const setLensPanelVisible = useViewerStore((s) => s.setLensPanelVisible);
|
|
66
|
+
const scriptPanelVisible = useViewerStore((s) => s.scriptPanelVisible);
|
|
67
|
+
const setScriptPanelVisible = useViewerStore((s) => s.setScriptPanelVisible);
|
|
68
|
+
|
|
69
|
+
// Panel refs for programmatic collapse/expand (command palette, keyboard shortcuts)
|
|
70
|
+
const leftPanelRef = useRef<PanelImperativeHandle>(null);
|
|
71
|
+
const rightPanelRef = useRef<PanelImperativeHandle>(null);
|
|
72
|
+
|
|
73
|
+
// Sync store state → Panel collapse/expand on desktop
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const panel = leftPanelRef.current;
|
|
76
|
+
if (!panel) return;
|
|
77
|
+
if (leftPanelCollapsed && !panel.isCollapsed()) panel.collapse();
|
|
78
|
+
else if (!leftPanelCollapsed && panel.isCollapsed()) panel.expand();
|
|
79
|
+
}, [leftPanelCollapsed]);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const panel = rightPanelRef.current;
|
|
83
|
+
if (!panel) return;
|
|
84
|
+
if (rightPanelCollapsed && !panel.isCollapsed()) panel.collapse();
|
|
85
|
+
else if (!rightPanelCollapsed && panel.isCollapsed()) panel.expand();
|
|
86
|
+
}, [rightPanelCollapsed]);
|
|
48
87
|
|
|
49
88
|
// Bottom panel resize state (pixel height, persisted in ref to avoid re-renders during drag)
|
|
50
89
|
const [bottomHeight, setBottomHeight] = useState(BOTTOM_PANEL_DEFAULT_HEIGHT);
|
|
@@ -113,12 +152,7 @@ export function ViewerLayout() {
|
|
|
113
152
|
return () => window.removeEventListener('resize', checkMobile);
|
|
114
153
|
}, [setIsMobile, setLeftPanelCollapsed, setRightPanelCollapsed]);
|
|
115
154
|
|
|
116
|
-
//
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
const currentTheme = useViewerStore.getState().theme;
|
|
119
|
-
document.documentElement.classList.toggle('dark', currentTheme === 'dark');
|
|
120
|
-
}, []);
|
|
121
|
-
|
|
155
|
+
// Keep DOM class in sync when theme changes (initial class is set by inline script in index.html)
|
|
122
156
|
useEffect(() => {
|
|
123
157
|
document.documentElement.classList.toggle('dark', theme === 'dark');
|
|
124
158
|
}, [theme]);
|
|
@@ -133,6 +167,7 @@ export function ViewerLayout() {
|
|
|
133
167
|
{/* Global Overlays */}
|
|
134
168
|
<EntityContextMenu />
|
|
135
169
|
<HoverTooltip />
|
|
170
|
+
<CommandPalette open={commandPaletteOpen} onOpenChange={setCommandPaletteOpen} />
|
|
136
171
|
|
|
137
172
|
{/* Main Toolbar */}
|
|
138
173
|
<MainToolbar onShowShortcuts={shortcutsDialog.toggle} />
|
|
@@ -150,6 +185,11 @@ export function ViewerLayout() {
|
|
|
150
185
|
minSize={10}
|
|
151
186
|
collapsible
|
|
152
187
|
collapsedSize={0}
|
|
188
|
+
panelRef={leftPanelRef}
|
|
189
|
+
onResize={() => {
|
|
190
|
+
const collapsed = leftPanelRef.current?.isCollapsed() ?? false;
|
|
191
|
+
if (collapsed !== leftPanelCollapsed) setLeftPanelCollapsed(collapsed);
|
|
192
|
+
}}
|
|
153
193
|
>
|
|
154
194
|
<div className="h-full w-full overflow-hidden">
|
|
155
195
|
<HierarchyPanel />
|
|
@@ -174,6 +214,11 @@ export function ViewerLayout() {
|
|
|
174
214
|
minSize={15}
|
|
175
215
|
collapsible
|
|
176
216
|
collapsedSize={0}
|
|
217
|
+
panelRef={rightPanelRef}
|
|
218
|
+
onResize={() => {
|
|
219
|
+
const collapsed = rightPanelRef.current?.isCollapsed() ?? false;
|
|
220
|
+
if (collapsed !== rightPanelCollapsed) setRightPanelCollapsed(collapsed);
|
|
221
|
+
}}
|
|
177
222
|
>
|
|
178
223
|
<div className="h-full w-full overflow-hidden">
|
|
179
224
|
{lensPanelVisible ? (
|
|
@@ -190,8 +235,8 @@ export function ViewerLayout() {
|
|
|
190
235
|
</PanelGroup>
|
|
191
236
|
</div>
|
|
192
237
|
|
|
193
|
-
{/* Bottom Panel - Lists (custom resizable, outside PanelGroup) */}
|
|
194
|
-
{listPanelVisible && (
|
|
238
|
+
{/* Bottom Panel - Lists or Script (custom resizable, outside PanelGroup) */}
|
|
239
|
+
{(listPanelVisible || scriptPanelVisible) && (
|
|
195
240
|
<div style={{ height: bottomHeight, flexShrink: 0 }} className="relative">
|
|
196
241
|
{/* Drag handle */}
|
|
197
242
|
<div
|
|
@@ -199,7 +244,11 @@ export function ViewerLayout() {
|
|
|
199
244
|
onMouseDown={handleResizeStart}
|
|
200
245
|
/>
|
|
201
246
|
<div className="h-full w-full overflow-hidden border-t pt-1.5">
|
|
202
|
-
|
|
247
|
+
{scriptPanelVisible ? (
|
|
248
|
+
<ScriptPanel onClose={() => setScriptPanelVisible(false)} />
|
|
249
|
+
) : (
|
|
250
|
+
<ListPanel onClose={() => setListPanelVisible(false)} />
|
|
251
|
+
)}
|
|
203
252
|
</div>
|
|
204
253
|
</div>
|
|
205
254
|
)}
|
|
@@ -240,12 +289,13 @@ export function ViewerLayout() {
|
|
|
240
289
|
<div className="absolute inset-x-0 bottom-0 h-[50vh] bg-background border-t rounded-t-xl shadow-xl z-40 animate-in slide-in-from-bottom">
|
|
241
290
|
<div className="flex items-center justify-between p-2 border-b">
|
|
242
291
|
<span className="font-medium text-sm">
|
|
243
|
-
{listPanelVisible ? 'Lists' : lensPanelVisible ? 'Lens' : idsPanelVisible ? 'IDS Validation' : bcfPanelVisible ? 'BCF Issues' : 'Properties'}
|
|
292
|
+
{scriptPanelVisible ? 'Script' : listPanelVisible ? 'Lists' : lensPanelVisible ? 'Lens' : idsPanelVisible ? 'IDS Validation' : bcfPanelVisible ? 'BCF Issues' : 'Properties'}
|
|
244
293
|
</span>
|
|
245
294
|
<button
|
|
246
295
|
className="p-1 hover:bg-muted rounded"
|
|
247
296
|
onClick={() => {
|
|
248
297
|
setRightPanelCollapsed(true);
|
|
298
|
+
if (scriptPanelVisible) setScriptPanelVisible(false);
|
|
249
299
|
if (listPanelVisible) setListPanelVisible(false);
|
|
250
300
|
if (bcfPanelVisible) setBcfPanelVisible(false);
|
|
251
301
|
if (lensPanelVisible) setLensPanelVisible(false);
|
|
@@ -259,7 +309,9 @@ export function ViewerLayout() {
|
|
|
259
309
|
</button>
|
|
260
310
|
</div>
|
|
261
311
|
<div className="h-[calc(50vh-48px)] overflow-auto">
|
|
262
|
-
{
|
|
312
|
+
{scriptPanelVisible ? (
|
|
313
|
+
<ScriptPanel onClose={() => setScriptPanelVisible(false)} />
|
|
314
|
+
) : listPanelVisible ? (
|
|
263
315
|
<ListPanel onClose={() => setListPanelVisible(false)} />
|
|
264
316
|
) : lensPanelVisible ? (
|
|
265
317
|
<LensPanel onClose={() => setLensPanelVisible(false)} />
|