import { useState } from 'react' import { Tooltip } from '@radix-ui/themes' import { ArrowCounterClockwise, ButterflyIcon, CheckSquareIcon, FileTextIcon, FolderOpenIcon, GlobeHemisphereWestIcon, ImageSquareIcon, ListIcon, PencilSimpleIcon, PersonIcon, QuestionMarkIcon, SpeakerHigh, SpeakerSlash, SquareIcon, TerminalWindowIcon } from '@phosphor-icons/react' import type { WorldEntry, WorldHoverPreview, WorldObjectAsset, WorldSceneProject } from '../types/world' import { type ControllerMode, useDebugStore } from '../store/debug' import { useAudioStore } from '../store/audio' import { ViewerQuality } from '../types/world' import { AppButton } from './AppButton' import { ChromeThumbnail, chrome } from './AppChrome' const CONTROLLER_MODES: readonly { mode: ControllerMode; label: string }[] = [ { mode: 'fly', label: 'Fly' }, { mode: 'fps', label: 'FPS' }, ] const QUALITY_MODES = [ { mode: ViewerQuality.Low, label: 'Low' }, { mode: ViewerQuality.High, label: 'High' }, ] as const function nextMode(items: readonly { mode: T }[], current: T) { const index = items.findIndex((item) => item.mode === current) return items[(index + 1) % items.length].mode } interface Props { worlds: WorldEntry[] activeSlug: string setActiveSlug: (slug: string) => void setEditing: (editing: boolean) => void compact?: boolean activeSceneProject?: WorldSceneProject activeSceneProjectEnabled: boolean onActiveSceneProjectToggle: () => void activeWorldVersionIndex?: number hoveredObjectAssetId?: string | null hoveredObjectInstanceId?: string | null onObjectHover?: (asset: WorldObjectAsset, hovering: boolean, instanceId?: string) => void onWorldHover?: (preview: WorldHoverPreview, hovering: boolean) => void onActiveWorldVersionChange: (index: number) => void } export function WorldSidebar({ worlds, activeSlug, setActiveSlug, setEditing, compact = false, activeSceneProject, activeSceneProjectEnabled, onActiveSceneProjectToggle, activeWorldVersionIndex, hoveredObjectAssetId, hoveredObjectInstanceId, onObjectHover, onWorldHover, onActiveWorldVersionChange, }: Props) { const [menuOpen, setMenuOpen] = useState(false) const butterfliesEnabled = useDebugStore((s) => s.butterfliesEnabled) const setButterfliesEnabled = useDebugStore((s) => s.setButterfliesEnabled) const canOpenLocalFolders = import.meta.env.DEV const selectWorld = (slug: string) => { setActiveSlug(slug) setMenuOpen(false) } const openClaudeTerminal = () => { fetch('/__open-claude-terminal').catch((error) => { console.warn('Could not open Claude terminal.', error) }) } const openWorldFolder = (slug: string) => { fetch(`/__open-world-folder?slug=${encodeURIComponent(slug)}`).catch((error) => { console.warn(`Could not open world folder for "${slug}".`, error) }) } const openAssetFolder = (slug: string, target: 'scene' | 'world-asset' | 'object-asset', asset?: string) => { const params = new URLSearchParams({ slug, target }) if (asset) params.set('asset', asset) fetch(`/__open-world-folder?${params.toString()}`).catch((error) => { console.warn(`Could not open ${target} folder for "${slug}".`, error) }) } return ( ) } function SidebarViewerControls() { const muted = useAudioStore((s) => s.muted) const toggleMuted = useAudioStore((s) => s.toggleMuted) const resetObjects = useDebugStore((s) => s.resetObjects) const controllerMode = useDebugStore((s) => s.controllerMode) const setControllerMode = useDebugStore((s) => s.setControllerMode) const viewerQuality = useDebugStore((s) => s.viewerQuality) const setViewerQuality = useDebugStore((s) => s.setViewerQuality) const currentControllerMode = CONTROLLER_MODES.find((item) => item.mode === controllerMode) ?? CONTROLLER_MODES[0] const currentQuality = QUALITY_MODES.find((item) => item.mode === viewerQuality) ?? QUALITY_MODES[0] return (
{muted ? : } setControllerMode(nextMode(CONTROLLER_MODES, controllerMode))} className="h-7 w-20 justify-center gap-1 flex border flex-1 border-white/20 text-white" aria-label="Change controller" title="Change controller" > {currentControllerMode.label} setViewerQuality(nextMode(QUALITY_MODES, viewerQuality))} className="h-7 w-16 justify-center gap-1 flex border flex-1 border-white/20 text-white" aria-label="Change quality" title="Change quality" > {currentQuality.label}
) } function FileExtensionBadge({ extension }: { extension: string }) { return ( {extension} ) } function LoadingSpinner() { return ( ) } function isLoadingStatus(status?: string) { if (!status) return true return ['queued', 'submitted', 'running', 'processing', 'in_progress'].includes(status.toLowerCase()) }