// Main Application Shell // Purpose: Composes the primary rover control interface and page-level layout. Scope: Orchestrates high-level panels, overlays, and feature modules for the default route. import DriverAnalytics from './components/DriverAnalytics/index.jsx'; import { useCallback, useEffect, useMemo, useState } from 'react'; import TelemetryPanel from './components/TelemetryPanel/index.jsx'; import PiHostStatsCard from './components/PiHostStatsCard/index.jsx'; import ReplaySourcesPanel from './components/ReplaySourcesPanel/index.jsx'; import AlertFeed from './components/AlertFeed/index.jsx'; import AuxColumn from './components/MobileControls/AuxColumn.jsx'; import MovementColumn from './components/MobileControls/MovementColumn.jsx'; import { ControlSystemProvider, KeyboardInputManager, GamepadInputManager, useControlSelector, } from './controls/index.js'; import RoomCameraPanel from './components/RoomCameraPanel/index.jsx'; import KinectPanel from './components/KinectPanel/index.jsx'; import DriverVideo from './components/DriverVideo/index.jsx'; import RightPaneTabs from './components/RightPaneTabs/index.jsx'; import ModeGateOverlay from './components/ModeGateOverlay/index.jsx'; import HomeAssistantControls from './components/HomeAssistantControls/index.jsx'; import TurnAlertListener from './components/TurnAlertListener/index.jsx'; import RawUserPilePanel from './components/RawUserPilePanel/index.jsx'; import OverseerPreferencePanel from './components/OverseerPreferencePanel/index.jsx'; import SocialButtonsGrid from './components/SocialButtonsGrid/index.jsx'; import ChatPanel from './components/ChatPanel/index.jsx'; import FullscreenPrompt from './components/FullscreenPrompt/index.jsx'; import FloatingFullscreenButton from './components/FloatingFullscreenButton/index.jsx'; import { useFullscreenPrompt } from './hooks/useFullscreenPrompt.js'; import { useSettingsNamespace } from './settings/index.js'; import HelpOverlay from './components/HelpOverlay/index.jsx'; import QuickstartOverlay from './components/QuickstartOverlay/index.jsx'; import HelpPanel from './components/HelpPanel/index.jsx'; import SettingsPanel from './components/SettingsPanel/index.jsx'; import Tabs, { Tab, TabList, TabPanel, TabPanels } from './components/Tabs/index.jsx'; import useDefaultNickname from './hooks/useDefaultNickname.js'; import useUserIdentitySync from './hooks/useUserIdentitySync.js'; import GlobalObjectiveBanner from './components/GlobalObjectiveBanner/index.jsx'; import RoverQueuesPanel from './components/RoverQueuesPanel/index.jsx'; import VipPanel from './components/VipPanel/index.jsx'; import { useSessionSelector } from './context/SessionContext.jsx'; import { useTelemetryVisualPolicy } from './context/TelemetryContext.jsx'; import ButtonBoxPanel from './components/ButtonBoxPanel/index.jsx'; import RewardRunOverlay from './components/RewardRunOverlay/index.jsx'; import SocketConnectionPill from './components/SocketConnectionPill/index.jsx'; import { pageBackgroundClass, themeGapClass, themeStackClass } from './themeFlags.js'; function useLayoutMode() { const [mode, setMode] = useState(() => { if (typeof window === 'undefined') return 'desktop'; return window.innerWidth >= 1024 ? 'desktop' : window.innerWidth > window.innerHeight ? 'mobile-landscape' : 'mobile-portrait'; }); useEffect(() => { function updateMode() { if (typeof window === 'undefined') return; const { innerWidth, innerHeight } = window; if (innerWidth >= 1024) { setMode('desktop'); } else if (innerWidth > innerHeight) { setMode('mobile-landscape'); } else { setMode('mobile-portrait'); } } updateMode(); window.addEventListener('resize', updateMode); return () => window.removeEventListener('resize', updateMode); }, []); return mode; } function DesktopLayout({ layout, onOpenHelpOverlay }) { return (
{/* */}
); } function MobileFeatureTabs({ layout, onOpenHelpOverlay, roomPanelId, }) { const [activeTab, setActiveTab] = useState('chat'); const isVerified = useSessionSelector((state) => Boolean(state.session?.isVerified)); const ownRoverId = useSessionSelector((state) => String(state.session?.assignment?.roverId || '').trim()); const ownAudioForward = useSessionSelector((state) => { const roverId = String(state.session?.assignment?.roverId || '').trim(); return roverId ? state.session?.audioForward?.[roverId] || null : null; }); const pttActive = useControlSelector((control) => Boolean(control.state.mic?.pttActive)); const { value: vipAudio } = useSettingsNamespace('vipAudio', { openMicEnabled: false, pttMode: 'live' }); const vipDotClass = isVerified ? 'bg-emerald-400' : 'bg-amber-400'; const openMicEnabled = Boolean(vipAudio?.openMicEnabled); const pttMode = vipAudio?.pttMode === 'clip' ? 'clip' : 'live'; const vipMicActive = Boolean( ownRoverId && isVerified && (pttMode === 'clip' ? pttActive : (openMicEnabled || pttActive)), ); const vipClipPlaying = Boolean( ownRoverId && isVerified && pttMode === 'clip' && ownAudioForward?.source === 'upload' && ownAudioForward?.state === 'playing', ); return (
Chat Activities VIP Room Controls Help Settings
{/* activities tab */}
{/* {showTelemetry ? : null} */}
); } function MobilePortraitLayout({ onOpenHelpOverlay, swapMobileControlColumns = false }) { const columnHeight = 'h-[min(60svh,24rem)]'; const firstColumn = swapMobileControlColumns ? : ; const secondColumn = swapMobileControlColumns ? : ; return (
{/* Portrait owns the two-column placement because the same reusable mobile columns sit in different grid contexts in portrait and landscape. */}
{firstColumn} {secondColumn}
{/* */}
); } function MobileLandscapeLayout({ onOpenHelpOverlay, swapMobileControlColumns = false }) { const columnClass = 'self-start h-[min(100svh,32rem)]'; const firstColumn = swapMobileControlColumns ? : ; const secondColumn = swapMobileControlColumns ? : ; return (
{firstColumn}
{/* */}
{secondColumn}
); } function App() { const layout = useLayoutMode(); const isDesktop = layout === 'desktop'; const fullscreen = useFullscreenPrompt(layout); return (
); } function AppWithProviders({ layout, isDesktop, fullscreen }) { useDefaultNickname(); useUserIdentitySync(); useTelemetryVisualPolicy({ mobile: !isDesktop }); const { visible: fullscreenVisible, mode: fullscreenMode, isIOS: fullscreenIsIOS, nativeSupported: fullscreenNativeSupported, enterFullscreen, dismiss, showPrompt, } = fullscreen; const { value: helpSettings, save: saveHelpSettings, } = useSettingsNamespace('help', { showOnLoad: true }); const { value: quickstartSettings, status: quickstartStatus, save: saveQuickstartSettings, } = useSettingsNamespace('quickstart', { showOnLoad: true }); const { value: pageSettings } = useSettingsNamespace('page', { swapMobileControlColumns: false, }); const swapMobileControlColumns = Boolean(pageSettings?.swapMobileControlColumns); const fullscreenButtonSide = swapMobileControlColumns ? 'left' : 'right'; const showFloatingFullscreenButton = !isDesktop && (fullscreenIsIOS || fullscreenNativeSupported); const [helpVisible, setHelpVisible] = useState(false); const [quickstartVisible, setQuickstartVisible] = useState(false); useEffect(() => { if (quickstartStatus === 'ready') { setQuickstartVisible(quickstartSettings?.showOnLoad !== false); } }, [quickstartStatus, quickstartSettings?.showOnLoad]); const openHelp = useCallback(() => setHelpVisible(true), []); const closeHelp = useCallback(() => setHelpVisible(false), []); const closeQuickstart = useCallback(() => setQuickstartVisible(false), []); const handleFloatingFullscreen = useCallback(async () => { if (fullscreenIsIOS) { showPrompt(); return; } const entered = await enterFullscreen(); if (!entered) { showPrompt(); } }, [enterFullscreen, fullscreenIsIOS, showPrompt]); const setQuickstartShowOnLoad = useCallback( (enabled) => { const next = Boolean(enabled); saveQuickstartSettings((current) => ({ ...(current ?? {}), showOnLoad: next })); if (!next) { setQuickstartVisible(false); } }, [saveQuickstartSettings], ); const openHelpFromQuickstart = useCallback(() => { setQuickstartVisible(false); setHelpVisible(true); }, []); const renderedLayout = useMemo( () => isDesktop ? : layout === 'mobile-landscape' ? : , [isDesktop, layout, openHelp, swapMobileControlColumns], ); return (
{!isDesktop ? : null} {renderedLayout}
saveHelpSettings((current) => ({ ...(current ?? {}), showOnLoad: Boolean(enabled) }))} /> {showFloatingFullscreenButton ? ( ) : null}
); } export default App;