'use client' import { type AnyNode, type AnyNodeId, type BuildingNode, type LevelNode, useScene, type ZoneNode, } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { ArrowLeft, Camera, ChevronRight, Diamond, Layers, Layers2, Moon, Sun, } from 'lucide-react' import Link from 'next/link' import { motion } from 'framer-motion' import { cn } from '@/lib/utils' import type { ProjectOwner } from '@/features/community/lib/projects/types' import { ActionButton } from '@/components/ui/action-menu/action-button' import { TooltipProvider } from '@/components/ui/primitives/tooltip' import { emitter } from '@pascal-app/core' import { CollectionsPanel } from './collections-panel' const levelModeLabels: Record<'stacked' | 'exploded' | 'solo', string> = { stacked: 'Stacked', exploded: 'Exploded', solo: 'Solo', } const wallModeConfig = { up: { icon: (props: any) => ( Full Height ), label: 'Full Height', }, cutaway: { icon: (props: any) => ( Cutaway ), label: 'Cutaway', }, down: { icon: (props: any) => Low, label: 'Low', }, } const getNodeName = (node: AnyNode): string => { if ('name' in node && node.name) return node.name if (node.type === 'wall') return 'Wall' if (node.type === 'item') return (node as { asset: { name: string } }).asset?.name || 'Item' if (node.type === 'slab') return 'Slab' if (node.type === 'ceiling') return 'Ceiling' if (node.type === 'roof') return 'Roof' return node.type } interface ViewerOverlayProps { projectName?: string | null owner?: ProjectOwner | null canShowScans?: boolean canShowGuides?: boolean onBack?: () => void hideCollections?: boolean } export const ViewerOverlay = ({ projectName, owner, canShowScans = true, canShowGuides = true, onBack, hideCollections, }: ViewerOverlayProps) => { const selection = useViewer((s) => s.selection) const nodes = useScene((s) => s.nodes) const showScans = useViewer((s) => s.showScans) const showGuides = useViewer((s) => s.showGuides) const cameraMode = useViewer((s) => s.cameraMode) const levelMode = useViewer((s) => s.levelMode) const wallMode = useViewer((s) => s.wallMode) const theme = useViewer((s) => s.theme) const building = selection.buildingId ? (nodes[selection.buildingId] as BuildingNode | undefined) : null const level = selection.levelId ? (nodes[selection.levelId] as LevelNode | undefined) : null const zone = selection.zoneId ? (nodes[selection.zoneId] as ZoneNode | undefined) : null // Get the first selected item (if any) const selectedNode = selection.selectedIds.length > 0 ? (nodes[selection.selectedIds[0] as AnyNodeId] as AnyNode | undefined) : null // Get all levels for the selected building const levels = building?.children .map((id) => nodes[id as AnyNodeId] as LevelNode | undefined) .filter((n): n is LevelNode => n?.type === 'level') .sort((a, b) => a.level - b.level) ?? [] const handleLevelClick = (levelId: LevelNode['id']) => { // When switching levels, deselect zone and items useViewer.getState().setSelection({ levelId }) } const handleBreadcrumbClick = (depth: 'root' | 'building' | 'level' | 'zone') => { switch (depth) { case 'root': useViewer.getState().resetSelection() break case 'building': useViewer.getState().setSelection({ levelId: null }) break case 'level': useViewer.getState().setSelection({ zoneId: null }) break } } return ( <> {/* Unified top-left card */}
{/* Project info + back */}
{onBack ? ( ) : ( )}
{projectName || 'Untitled'}
{owner?.username && ( @{owner.username} )}
{/* Breadcrumb — only shown when navigated into a building */} {building && (
{building && ( <> )} {level && ( <> )} {zone && ( <> {zone.name} )} {selectedNode && zone && ( <> {getNodeName(selectedNode)} )}
)}
{/* Level List (only when building is selected) */} {building && levels.length > 0 && (
Levels
{levels.map((lvl) => { const isSelected = lvl.id === selection.levelId; return ( ); })}
)}
{/* Collections Panel - Top Right */} {!hideCollections && (
)} {/* Controls Panel - Bottom Center */}
{/* Theme Toggle */}
{/* Scans and Guides Visibility */} {canShowScans && ( useViewer.getState().setShowScans(!showScans)} size="icon" variant="ghost" > Scans )} {canShowGuides && ( useViewer.getState().setShowGuides(!showGuides)} size="icon" variant="ghost" > Guides )} {(canShowScans || canShowGuides) &&
} {/* Camera Mode */} useViewer.getState().setCameraMode(cameraMode === 'perspective' ? 'orthographic' : 'perspective')} size="icon" variant="ghost" > {/* Level Mode */} { if (levelMode === 'manual') return useViewer.getState().setLevelMode('stacked') const modes: ('stacked' | 'exploded' | 'solo')[] = ['stacked', 'exploded', 'solo'] const nextIndex = (modes.indexOf(levelMode as any) + 1) % modes.length useViewer.getState().setLevelMode(modes[nextIndex] ?? 'stacked') }} size="icon" variant="ghost" > {levelMode === 'solo' && } {levelMode === 'exploded' && } {(levelMode === 'stacked' || levelMode === 'manual') && } {/* Wall Mode */} { const modes: ('cutaway' | 'up' | 'down')[] = ['cutaway', 'up', 'down'] const nextIndex = (modes.indexOf(wallMode as any) + 1) % modes.length useViewer.getState().setWallMode(modes[nextIndex] ?? 'cutaway') }} size="icon" variant="ghost" > {(() => { const Icon = wallModeConfig[wallMode as keyof typeof wallModeConfig].icon return })()}
{/* Camera Actions */} emitter.emit('camera-controls:orbit-ccw')} size="icon" variant="ghost" > Orbit Left emitter.emit('camera-controls:orbit-cw')} size="icon" variant="ghost" > Orbit Right emitter.emit('camera-controls:top-view')} size="icon" variant="ghost" > Top View
) }