@pascal-app/editor 0.9.2 → 1.0.0-beta.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/package.json +9 -7
- package/src/components/editor/bake-exporter.tsx +3 -1
- package/src/components/editor/bake-thumbnail.tsx +95 -0
- package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
- package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
- package/src/components/editor/custom-camera-controls.tsx +307 -212
- package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
- package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
- package/src/components/editor/first-person/build-collider-world.test.ts +66 -2
- package/src/components/editor/first-person/build-collider-world.ts +20 -0
- package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
- package/src/components/editor/first-person/terrain-collider.ts +212 -0
- package/src/components/editor/first-person-controls.tsx +295 -132
- package/src/components/editor/floating-action-menu.tsx +59 -65
- package/src/components/editor/floorplan-background-selection.test.ts +16 -2
- package/src/components/editor/floorplan-background-selection.ts +4 -18
- package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
- package/src/components/editor/floorplan-camera-sync.ts +213 -0
- package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
- package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
- package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
- package/src/components/editor/floorplan-panel.tsx +931 -861
- package/src/components/editor/group-actions.ts +155 -15
- package/src/components/editor/group-floating-action-menu.tsx +1 -1
- package/src/components/editor/group-rotate-handle.tsx +1 -1
- package/src/components/editor/group-selection-box-3d.tsx +1 -1
- package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
- package/src/components/editor/handles/handle-drag-history.ts +14 -0
- package/src/components/editor/handles/preview-overrides.test.ts +26 -0
- package/src/components/editor/handles/preview-overrides.ts +13 -0
- package/src/components/editor/handles/resize-snap.test.ts +52 -0
- package/src/components/editor/handles/resize-snap.ts +23 -0
- package/src/components/editor/handles/use-handle-drag.ts +17 -7
- package/src/components/editor/index.tsx +42 -9
- package/src/components/editor/measurement-pill.tsx +6 -1
- package/src/components/editor/node-arrow-handles.tsx +51 -15
- package/src/components/editor/quick-measurement-card.tsx +9 -3
- package/src/components/editor/quick-measurement-hud.tsx +7 -1
- package/src/components/editor/selection-manager.tsx +3 -1
- package/src/components/editor/site-edge-labels.tsx +24 -2
- package/src/components/editor/snapshot-capture-overlay.tsx +4 -1
- package/src/components/editor/thumbnail-generator.tsx +66 -294
- package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
- package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
- package/src/components/editor/use-mesh-settle-epoch.ts +1 -0
- package/src/components/editor/wall-measurement-label.tsx +8 -16
- package/src/components/editor/wall-move-side-handles.tsx +193 -18
- package/src/components/editor/wall-opening-highlights.tsx +63 -32
- package/src/components/editor/wall-snap-beacon-layer.tsx +6 -4
- package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
- package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +39 -8
- package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
- package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
- package/src/components/editor-2d/floorplan-group-move.tsx +47 -4
- package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +21 -5
- package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
- package/src/components/editor-2d/floorplan-registry-action-menu.tsx +93 -59
- package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +27 -0
- package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
- package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
- package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
- package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
- package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
- package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +75 -1
- package/src/components/editor-2d/renderers/floorplan-label-angle.ts +92 -0
- package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +93 -28
- package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +279 -0
- package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +621 -281
- package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +12 -7
- package/src/components/tools/fence/fence-drafting.ts +27 -0
- package/src/components/tools/item/use-draft-node.ts +131 -72
- package/src/components/tools/item/use-placement-coordinator.tsx +52 -4
- package/src/components/tools/registry/move-registry-node-tool.tsx +75 -4
- package/src/components/tools/roof/roof-tool.tsx +16 -0
- package/src/components/tools/shared/floor-stack-preview.ts +4 -0
- package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
- package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
- package/src/components/tools/shared/pointer-support-cap.ts +273 -0
- package/src/components/tools/site/site-boundary-editor.tsx +13 -6
- package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
- package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
- package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
- package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
- package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
- package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
- package/src/components/tools/stair/stair-click-guard.ts +72 -0
- package/src/components/tools/stair/stair-tool.tsx +98 -47
- package/src/components/tools/tool-manager.tsx +67 -9
- package/src/components/tools/wall/wall-drafting.test.ts +118 -1
- package/src/components/tools/wall/wall-drafting.ts +128 -9
- package/src/components/tools/wall/wall-snap-geometry.test.ts +8 -0
- package/src/components/tools/wall/wall-snap-geometry.ts +70 -3
- package/src/components/tools/zone/zone-tool.tsx +16 -0
- package/src/components/ui/action-menu/control-modes.tsx +4 -1
- package/src/components/ui/action-menu/measurement-control.tsx +107 -28
- package/src/components/ui/command-palette/editor-commands.tsx +14 -1
- package/src/components/ui/controls/material-paint-panel.tsx +7 -1
- package/src/components/ui/controls/material-picker.tsx +86 -7
- package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
- package/src/components/ui/floating-level-selector.tsx +73 -19
- package/src/components/ui/helpers/helper-manager.tsx +46 -0
- package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
- package/src/components/ui/panels/panel-manager.tsx +14 -1
- package/src/components/ui/panels/parametric-inspector.tsx +4 -2
- package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
- package/src/components/ui/panels/selection-breakdown.ts +20 -0
- package/src/components/ui/sidebar/panels/settings-panel/index.tsx +14 -4
- package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +16 -0
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +2 -0
- package/src/components/viewer/viewer-controls-bar.tsx +466 -0
- package/src/components/viewer/viewer-scene-header.tsx +235 -0
- package/src/components/viewer-overlay.tsx +20 -655
- package/src/components/walkthrough-hud.tsx +111 -0
- package/src/hooks/use-grid-events.ts +24 -0
- package/src/hooks/use-keyboard.ts +53 -30
- package/src/index.tsx +137 -4
- package/src/lib/active-placement-surface.test.ts +24 -0
- package/src/lib/active-placement-surface.ts +8 -1
- package/src/lib/camera-pose.test.ts +237 -0
- package/src/lib/camera-pose.ts +172 -0
- package/src/lib/direct-manipulation.test.ts +83 -0
- package/src/lib/direct-manipulation.ts +9 -0
- package/src/lib/door-interaction.ts +1 -1
- package/src/lib/elevation-guides.test.ts +107 -0
- package/src/lib/elevation-guides.ts +236 -0
- package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
- package/src/lib/floorplan/annotation-visibility.ts +133 -0
- package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
- package/src/lib/floorplan/drawing-coordination.ts +13 -0
- package/src/lib/floorplan/floorplan-export.test.ts +319 -0
- package/src/lib/floorplan/floorplan-export.tsx +745 -131
- package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
- package/src/lib/floorplan/floorplan-extension.ts +181 -0
- package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
- package/src/lib/floorplan/floorplan-mode.ts +73 -0
- package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
- package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
- package/src/lib/floorplan/geometry.ts +2 -3
- package/src/lib/fresh-planar-placement.test.ts +44 -1
- package/src/lib/fresh-planar-placement.ts +21 -4
- package/src/lib/glb-export.test.ts +116 -1
- package/src/lib/glb-export.ts +204 -7
- package/src/lib/ground-surface.ts +95 -0
- package/src/lib/history.test.ts +96 -0
- package/src/lib/history.ts +22 -0
- package/src/lib/interaction/overlay-policy.test.ts +2 -1
- package/src/lib/interaction/scope.ts +40 -8
- package/src/lib/level-duplication.test.ts +2 -1
- package/src/lib/measurements.test.ts +5 -0
- package/src/lib/measurements.ts +12 -1
- package/src/lib/quick-action-feedback.ts +33 -0
- package/src/lib/quick-action-nodes.test.ts +94 -0
- package/src/lib/quick-action-nodes.ts +47 -0
- package/src/lib/scene-clipboard.test.ts +130 -1
- package/src/lib/scene-clipboard.ts +237 -13
- package/src/lib/selection-routing.test.ts +16 -1
- package/src/lib/selection-routing.ts +5 -0
- package/src/lib/sfx-bus.ts +33 -2
- package/src/lib/sfx-player.test.ts +148 -6
- package/src/lib/sfx-player.ts +156 -8
- package/src/lib/site-boundary.test.ts +53 -0
- package/src/lib/site-boundary.ts +27 -0
- package/src/lib/snapping-mode.test.ts +29 -2
- package/src/lib/snapping-mode.ts +15 -3
- package/src/lib/stair-duplication.test.ts +103 -0
- package/src/lib/stair-duplication.ts +57 -85
- package/src/lib/stair-levels.ts +2 -0
- package/src/lib/terrain-sculpt.test.ts +429 -0
- package/src/lib/terrain-sculpt.ts +301 -0
- package/src/lib/terrain-verb-color.test.ts +51 -0
- package/src/lib/terrain-verb-color.ts +58 -0
- package/src/lib/touch-gesture-priority.test.ts +45 -0
- package/src/lib/touch-gesture-priority.ts +30 -0
- package/src/lib/window-interaction.ts +1 -1
- package/src/store/camera-pose-store.test.ts +29 -0
- package/src/store/camera-pose-store.ts +27 -0
- package/src/store/live-draft-preview-stores.test.ts +136 -0
- package/src/store/navigation-sync-pose-store.test.ts +34 -0
- package/src/store/navigation-sync-pose-store.ts +27 -0
- package/src/store/terrain-sculpt-mode.test.ts +223 -0
- package/src/store/use-delete-confirmation.ts +27 -0
- package/src/store/use-drawing-view.test.ts +23 -0
- package/src/store/use-drawing-view.ts +84 -0
- package/src/store/use-editor.tsx +184 -15
- package/src/store/use-elevation-guides.ts +40 -0
- package/src/store/use-fence-curve-draft.ts +38 -8
- package/src/store/use-first-person-hud.ts +40 -0
- package/src/store/use-floorplan-annotation-visibility.ts +60 -0
- package/src/store/use-floorplan-draft-preview.ts +54 -4
- package/src/store/use-floorplan-mode.ts +110 -0
- package/src/store/use-interaction-scope.test.ts +23 -3
- package/src/store/use-interaction-scope.ts +8 -0
- package/src/store/use-measurement-draft.test.ts +29 -0
- package/src/store/use-measurement-draft.ts +33 -6
- package/src/store/use-path-draft-preview.ts +90 -0
- package/src/store/use-stair-build-preview.ts +12 -2
- package/src/lib/floorplan/selection-tool.ts +0 -271
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AnyNode,
|
|
5
|
+
type AnyNodeId,
|
|
6
|
+
type BuildingNode,
|
|
7
|
+
getLevelDisplayName,
|
|
8
|
+
type LevelNode,
|
|
9
|
+
useScene,
|
|
10
|
+
type ZoneNode,
|
|
11
|
+
} from '@pascal-app/core'
|
|
12
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
13
|
+
import { ArrowLeft, ChevronRight, Layers } from 'lucide-react'
|
|
14
|
+
import Link from 'next/link'
|
|
15
|
+
import type { ReactNode } from 'react'
|
|
16
|
+
import { useShallow } from 'zustand/react/shallow'
|
|
17
|
+
import { cn } from '../../lib/utils'
|
|
18
|
+
|
|
19
|
+
const getNodeName = (node: AnyNode): string => {
|
|
20
|
+
if ('name' in node && node.name) return node.name
|
|
21
|
+
if (node.type === 'wall') return 'Wall'
|
|
22
|
+
if (node.type === 'fence') return 'Fence'
|
|
23
|
+
if (node.type === 'item') return (node as { asset: { name: string } }).asset?.name || 'Item'
|
|
24
|
+
if (node.type === 'slab') return 'Slab'
|
|
25
|
+
if (node.type === 'ceiling') return 'Ceiling'
|
|
26
|
+
if (node.type === 'roof') return 'Roof'
|
|
27
|
+
if (node.type === 'roof-segment') return 'Roof Segment'
|
|
28
|
+
return node.type
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ViewerSceneHeaderProps = {
|
|
32
|
+
projectName?: string | null
|
|
33
|
+
owner?: { username?: string | null } | null
|
|
34
|
+
onBack?: () => void
|
|
35
|
+
/** Fallback destination when no `onBack` handler is supplied. Must already be
|
|
36
|
+
* sanitized by the caller. */
|
|
37
|
+
backHref?: string
|
|
38
|
+
/** Extra row under the project info (e.g. likes/fork actions). */
|
|
39
|
+
stats?: ReactNode
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const ViewerSceneHeader = ({
|
|
43
|
+
projectName,
|
|
44
|
+
owner,
|
|
45
|
+
onBack,
|
|
46
|
+
backHref = '/',
|
|
47
|
+
stats,
|
|
48
|
+
}: ViewerSceneHeaderProps) => {
|
|
49
|
+
const selection = useViewer((s) => s.selection)
|
|
50
|
+
|
|
51
|
+
// Subscribe only to the specific nodes we read so that creating an unrelated
|
|
52
|
+
// node elsewhere in the scene doesn't re-render this overlay.
|
|
53
|
+
const firstSelectedId = selection.selectedIds[0] ?? null
|
|
54
|
+
const building = useScene((s) =>
|
|
55
|
+
selection.buildingId ? (s.nodes[selection.buildingId] as BuildingNode | undefined) : null,
|
|
56
|
+
)
|
|
57
|
+
const level = useScene((s) =>
|
|
58
|
+
selection.levelId ? (s.nodes[selection.levelId] as LevelNode | undefined) : null,
|
|
59
|
+
)
|
|
60
|
+
const zone = useScene((s) =>
|
|
61
|
+
selection.zoneId ? (s.nodes[selection.zoneId] as ZoneNode | undefined) : null,
|
|
62
|
+
)
|
|
63
|
+
const selectedNode = useScene((s) =>
|
|
64
|
+
firstSelectedId ? (s.nodes[firstSelectedId as AnyNodeId] as AnyNode | undefined) : null,
|
|
65
|
+
)
|
|
66
|
+
// Highest first so the list reads top-down like a building section.
|
|
67
|
+
const levels = useScene(
|
|
68
|
+
useShallow((s) => {
|
|
69
|
+
if (!building) return []
|
|
70
|
+
return building.children
|
|
71
|
+
.map((id) => s.nodes[id as AnyNodeId] as LevelNode | undefined)
|
|
72
|
+
.filter((n): n is LevelNode => n?.type === 'level')
|
|
73
|
+
.sort((a, b) => b.level - a.level)
|
|
74
|
+
}),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const handleLevelClick = (levelId: LevelNode['id']) => {
|
|
78
|
+
// When switching levels, deselect zone and items
|
|
79
|
+
useViewer.getState().setSelection({ levelId })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const handleBreadcrumbClick = (depth: 'root' | 'building' | 'level') => {
|
|
83
|
+
switch (depth) {
|
|
84
|
+
case 'root':
|
|
85
|
+
useViewer.getState().resetSelection()
|
|
86
|
+
break
|
|
87
|
+
case 'building':
|
|
88
|
+
useViewer.getState().setSelection({ levelId: null })
|
|
89
|
+
break
|
|
90
|
+
case 'level':
|
|
91
|
+
useViewer.getState().setSelection({ zoneId: null })
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="dark absolute top-4 left-4 z-20 flex flex-col gap-3 text-foreground">
|
|
98
|
+
<div className="corner-smooth pointer-events-auto flex min-w-[200px] flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/95 shadow-elevation-4 backdrop-blur-xl transition-colors duration-200 ease-out">
|
|
99
|
+
{/* Project info + back */}
|
|
100
|
+
<div className="flex items-center gap-3 px-3 py-2.5">
|
|
101
|
+
{onBack ? (
|
|
102
|
+
<button
|
|
103
|
+
aria-label="Back"
|
|
104
|
+
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-white/10"
|
|
105
|
+
onClick={onBack}
|
|
106
|
+
type="button"
|
|
107
|
+
>
|
|
108
|
+
<ArrowLeft className="h-4 w-4 text-muted-foreground" />
|
|
109
|
+
</button>
|
|
110
|
+
) : (
|
|
111
|
+
<Link
|
|
112
|
+
aria-label="Back"
|
|
113
|
+
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors hover:bg-white/10"
|
|
114
|
+
href={backHref}
|
|
115
|
+
prefetch={false}
|
|
116
|
+
>
|
|
117
|
+
<ArrowLeft className="h-4 w-4 text-muted-foreground" />
|
|
118
|
+
</Link>
|
|
119
|
+
)}
|
|
120
|
+
<div className="min-w-0 flex-1">
|
|
121
|
+
<div className="truncate font-medium text-foreground text-sm">
|
|
122
|
+
{projectName || 'Untitled'}
|
|
123
|
+
</div>
|
|
124
|
+
{owner?.username && (
|
|
125
|
+
<Link
|
|
126
|
+
className="text-muted-foreground text-xs transition-colors hover:text-foreground"
|
|
127
|
+
href={`/u/${owner.username}`}
|
|
128
|
+
>
|
|
129
|
+
@{owner.username}
|
|
130
|
+
</Link>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
{stats && (
|
|
136
|
+
<div className="flex items-center gap-1 border-border/40 border-t px-3 py-2">{stats}</div>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
{/* Breadcrumb — only shown when navigated into a building */}
|
|
140
|
+
{building && (
|
|
141
|
+
<div className="border-border/40 border-t px-3 py-2">
|
|
142
|
+
<div className="flex items-center gap-1.5 text-xs">
|
|
143
|
+
<button
|
|
144
|
+
className="text-muted-foreground transition-colors hover:text-foreground"
|
|
145
|
+
onClick={() => handleBreadcrumbClick('root')}
|
|
146
|
+
>
|
|
147
|
+
Site
|
|
148
|
+
</button>
|
|
149
|
+
|
|
150
|
+
<ChevronRight className="h-3 w-3 text-muted-foreground/50" />
|
|
151
|
+
<button
|
|
152
|
+
className={`truncate transition-colors ${level ? 'text-muted-foreground hover:text-foreground' : 'font-medium text-foreground'}`}
|
|
153
|
+
onClick={() => handleBreadcrumbClick('building')}
|
|
154
|
+
>
|
|
155
|
+
{building.name || 'Building'}
|
|
156
|
+
</button>
|
|
157
|
+
|
|
158
|
+
{level && (
|
|
159
|
+
<>
|
|
160
|
+
<ChevronRight className="h-3 w-3 text-muted-foreground/50" />
|
|
161
|
+
<button
|
|
162
|
+
className={`truncate transition-colors ${zone ? 'text-muted-foreground hover:text-foreground' : 'font-medium text-foreground'}`}
|
|
163
|
+
onClick={() => handleBreadcrumbClick('level')}
|
|
164
|
+
>
|
|
165
|
+
{getLevelDisplayName(level)}
|
|
166
|
+
</button>
|
|
167
|
+
</>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{zone && (
|
|
171
|
+
<>
|
|
172
|
+
<ChevronRight className="h-3 w-3 text-muted-foreground/50" />
|
|
173
|
+
<span
|
|
174
|
+
className={`truncate transition-colors ${selectedNode ? 'text-muted-foreground' : 'font-medium text-foreground'}`}
|
|
175
|
+
>
|
|
176
|
+
{zone.name}
|
|
177
|
+
</span>
|
|
178
|
+
</>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
{selectedNode && zone && (
|
|
182
|
+
<>
|
|
183
|
+
<ChevronRight className="h-3 w-3 text-muted-foreground/50" />
|
|
184
|
+
<span className="truncate font-medium text-foreground">
|
|
185
|
+
{getNodeName(selectedNode)}
|
|
186
|
+
</span>
|
|
187
|
+
</>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
{/* Level list (only when a building is selected) */}
|
|
195
|
+
{building && levels.length > 0 && (
|
|
196
|
+
<div className="corner-smooth pointer-events-auto flex w-48 flex-col overflow-hidden rounded-2xl border border-border/40 bg-background/95 py-1 shadow-elevation-4 backdrop-blur-xl transition-colors duration-200 ease-out">
|
|
197
|
+
<span className="px-3 py-2 font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
|
|
198
|
+
Levels
|
|
199
|
+
</span>
|
|
200
|
+
<div className="flex flex-col">
|
|
201
|
+
{levels.map((lvl) => {
|
|
202
|
+
const isSelected = lvl.id === selection.levelId
|
|
203
|
+
return (
|
|
204
|
+
<button
|
|
205
|
+
className={cn(
|
|
206
|
+
'group/row relative flex h-8 w-full cursor-pointer select-none items-center border-border/50 border-r border-r-transparent border-b px-3 text-sm transition-all duration-200',
|
|
207
|
+
isSelected
|
|
208
|
+
? 'border-r-3 border-r-white bg-accent/50 text-foreground'
|
|
209
|
+
: 'text-muted-foreground hover:bg-accent/30 hover:text-foreground',
|
|
210
|
+
)}
|
|
211
|
+
key={lvl.id}
|
|
212
|
+
onClick={() => handleLevelClick(lvl.id)}
|
|
213
|
+
>
|
|
214
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
215
|
+
<span
|
|
216
|
+
className={cn(
|
|
217
|
+
'flex h-4 w-4 shrink-0 items-center justify-center transition-all duration-200',
|
|
218
|
+
!isSelected && 'opacity-60 grayscale',
|
|
219
|
+
)}
|
|
220
|
+
>
|
|
221
|
+
<Layers className="h-3.5 w-3.5" />
|
|
222
|
+
</span>
|
|
223
|
+
<div className="min-w-0 flex-1 truncate text-left">
|
|
224
|
+
{getLevelDisplayName(lvl)}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</button>
|
|
228
|
+
)
|
|
229
|
+
})}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
)
|
|
235
|
+
}
|