import { useCallback, useEffect, useMemo, useState } from 'react';
import TelemetryPanel from './components/TelemetryPanel.jsx';
import ReplaySourcesPanel from './components/ReplaySourcesPanel.jsx';
import AlertFeed from './components/AlertFeed.jsx';
import MobileControls, {
MobileActionsColumn,
MobileDriveColumn,
} from './components/MobileControls.jsx';
import {
ControlSystemProvider,
KeyboardInputManager,
GamepadInputManager,
useControlSystem,
} from './controls/index.js';
import RoomCameraPanel from './components/RoomCameraPanel.jsx';
import LogPanel from './components/LogPanel.jsx';
import DriverVideoPanel from './components/DriverVideoPanel.jsx';
import RightPaneTabs from './components/RightPaneTabs.jsx';
import ModeGateOverlay from './components/ModeGateOverlay.jsx';
import HomeAssistantControls from './components/HomeAssistantControls.jsx';
import TurnAlertListener from './components/TurnAlertListener.jsx';
import RawUserPilePanel from './components/RawUserPilePanel.jsx';
import ChatPanel from './components/ChatPanel.jsx';
import FullscreenPrompt from './components/FullscreenPrompt.jsx';
import FloatingFullscreenButton from './components/FloatingFullscreenButton.jsx';
import { useFullscreenPrompt } from './hooks/useFullscreenPrompt.js';
import { useSettingsNamespace } from './settings/index.js';
import HelpOverlay from './components/HelpOverlay.jsx';
import HelpPanel from './components/HelpPanel.jsx';
import SettingsPanel from './components/SettingsPanel.jsx';
import Tabs, { Tab, TabList, TabPanel, TabPanels } from './components/Tabs.jsx';
import useDefaultNickname from './hooks/useDefaultNickname.js';
import useUserIdentitySync from './hooks/useUserIdentitySync.js';
import CommunityGoalBanner from './components/CommunityGoalBanner.jsx';
import RoverQueuesPanel from './components/RoverQueuesPanel.jsx';
import VipPanel from './components/VipPanel.jsx';
import { useSessionSelector } from './context/SessionContext.jsx';
import ButtonBoxPanel from './components/ButtonBoxPanel.jsx';
import RewardRunOverlay from './components/RewardRunOverlay.jsx';
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,
showTelemetry = true,
}) {
const session = useSessionSelector((state) => state.session);
const { state: controlState } = useControlSystem();
const { value: vipAudio } = useSettingsNamespace('vipAudio', { openMicEnabled: false, pttMode: 'live' });
const vipDotClass = session?.isVerified ? 'bg-emerald-400' : 'bg-amber-400';
const ownRoverId = String(session?.assignment?.roverId || '').trim();
const ownAudioForward = ownRoverId ? session?.audioForward?.[ownRoverId] : null;
const pttActive = Boolean(controlState?.mic?.pttActive);
const openMicEnabled = Boolean(vipAudio?.openMicEnabled);
const pttMode = vipAudio?.pttMode === 'clip' ? 'clip' : 'live';
const vipMicActive = Boolean(
ownRoverId &&
session?.isVerified &&
(pttMode === 'clip' ? pttActive : (openMicEnabled || pttActive)),
);
const vipClipPlaying = Boolean(
ownRoverId &&
session?.isVerified &&
pttMode === 'clip' &&
ownAudioForward?.source === 'upload' &&
ownAudioForward?.state === 'playing',
);
return (
Chat
VIP
Room Controls
Help
Settings
{/* {showTelemetry ? : null} */}
);
}
function MobilePortraitLayout({ onOpenHelpOverlay, swapMobileControlColumns = false }) {
return (
);
}
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();
const {
visible: fullscreenVisible,
mode: fullscreenMode,
isIOS: fullscreenIsIOS,
nativeSupported: fullscreenNativeSupported,
enterFullscreen,
dismiss,
showPrompt,
} = fullscreen;
const {
value: helpSettings,
status: helpStatus,
save: saveHelpSettings,
} = useSettingsNamespace('help', { 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);
useEffect(() => {
if (helpStatus === 'ready') {
setHelpVisible(helpSettings?.showOnLoad !== false);
}
}, [helpStatus, helpSettings?.showOnLoad]);
const openHelp = useCallback(() => setHelpVisible(true), []);
const closeHelp = useCallback(() => setHelpVisible(false), []);
const handleFloatingFullscreen = useCallback(async () => {
if (fullscreenIsIOS) {
showPrompt();
return;
}
const entered = await enterFullscreen();
if (!entered) {
showPrompt();
}
}, [enterFullscreen, fullscreenIsIOS, showPrompt]);
const setShowOnLoad = useCallback(
(enabled) => {
const next = Boolean(enabled);
saveHelpSettings((current) => ({ ...(current ?? {}), showOnLoad: next }));
if (!next) {
setHelpVisible(false);
}
},
[saveHelpSettings],
);
const renderedLayout = useMemo(
() =>
isDesktop
?
: layout === 'mobile-landscape'
?
: ,
[isDesktop, layout, openHelp, swapMobileControlColumns],
);
return (
{!isDesktop ? : null}
{renderedLayout}
{showFloatingFullscreenButton ? (
) : null}
);
}
export default App;