top-down in spectator

This commit is contained in:
legop3
2025-12-21 16:08:34 -05:00
parent 1b013ed91b
commit ed9bb3216c
6 changed files with 77 additions and 76 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title> <title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-C6EqAuKU.js"></script> <script type="module" crossorigin src="/assets/index-BBUSDixK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DkBejE8J.css"> <link rel="stylesheet" crossorigin href="/assets/index-DkBejE8J.css">
</head> </head>
<body> <body>
+39 -24
View File
@@ -52,6 +52,8 @@ export default function VideoTile({
hudVariant = 'default', hudVariant = 'default',
driverLabel = null, driverLabel = null,
songNote = null, songNote = null,
hudForceMap = false,
hudMapPosition = 'top-right',
}) { }) {
const videoRef = useRef(null); const videoRef = useRef(null);
const audioRef = useRef(null); const audioRef = useRef(null);
@@ -70,7 +72,7 @@ export default function VideoTile({
const desktopLayout = layoutFormat === 'desktop'; const desktopLayout = layoutFormat === 'desktop';
const mobileHud = !desktopLayout; const mobileHud = !desktopLayout;
const [showHudMapDesktop, setShowHudMapDesktop] = useHudMapSetting(); const [showHudMapDesktop, setShowHudMapDesktop] = useHudMapSetting();
const showHudMap = mobileHud ? true : showHudMapDesktop; const showHudMap = hudForceMap ? true : mobileHud ? true : showHudMapDesktop;
const batteryVisual = buildBatteryVisual(batteryCharge, batteryConfig); const batteryVisual = buildBatteryVisual(batteryCharge, batteryConfig);
// console.log('[BatteryBarDebug]', { // console.log('[BatteryBarDebug]', {
// frameSensors: sensors, // frameSensors: sensors,
@@ -284,6 +286,7 @@ export default function VideoTile({
songNote={songNote} songNote={songNote}
showTopDown={showHudMap} showTopDown={showHudMap}
mobileHud={mobileHud} mobileHud={mobileHud}
mapPosition={hudMapPosition}
/> />
<OvercurrentOverlay motors={overcurrentMotors} /> <OvercurrentOverlay motors={overcurrentMotors} />
<LowBatteryOverlay charge={batteryCharge} config={batteryConfig} /> <LowBatteryOverlay charge={batteryCharge} config={batteryConfig} />
@@ -348,6 +351,7 @@ function HudOverlay({
songNote = null, songNote = null,
showTopDown = false, showTopDown = false,
mobileHud = false, mobileHud = false,
mapPosition = 'top-right',
}) { }) {
const bumps = sensors?.bumpsAndWheelDrops || {}; const bumps = sensors?.bumpsAndWheelDrops || {};
const [now, setNow] = useState(() => Date.now()); const [now, setNow] = useState(() => Date.now());
@@ -358,6 +362,16 @@ function HudOverlay({
}, []); }, []);
const pulse = frame?.receivedAt ? now - frame.receivedAt < 200 : false; const pulse = frame?.receivedAt ? now - frame.receivedAt < 200 : false;
const mapStyle = {
width: '240px',
height: '240px',
opacity: 0.7,
transform: `scale(${mobileHud ? 0.55 : 0.7})`,
transformOrigin: mapPosition === 'bottom-left' ? 'bottom left' : 'top right',
...(mapPosition === 'bottom-left'
? { left: '0.25rem', bottom: '0.25rem' }
: { right: '0.25rem', top: '0.25rem' }),
};
if (variant === 'spectator') { if (variant === 'spectator') {
const telemetryEntries = [ const telemetryEntries = [
@@ -378,7 +392,8 @@ function HudOverlay({
</div> </div>
) : null} ) : null}
<div className="absolute left-1 top-1/2 flex -translate-y-1/2 flex-col gap-0.25 bg-black/70 px-1 py-0.75 text-[0.65rem] text-slate-100"> <div className="absolute left-1 top-1/2 flex -translate-y-1/2 flex-col gap-0.5 bg-black/70 px-1 py-0.75 text-[0.65rem] text-slate-100">
<div className="space-y-0.25">
<span className="text-[0.6rem] uppercase tracking-wide text-slate-400">Telemetry</span> <span className="text-[0.6rem] uppercase tracking-wide text-slate-400">Telemetry</span>
{telemetryEntries.map(([labelText, value]) => ( {telemetryEntries.map(([labelText, value]) => (
<span key={labelText} className="flex items-center justify-between gap-0.5"> <span key={labelText} className="flex items-center justify-between gap-0.5">
@@ -387,26 +402,28 @@ function HudOverlay({
</span> </span>
))} ))}
</div> </div>
{showTopDown ? (
<div className="flex items-center justify-center">
<div
className="pointer-events-none rounded"
style={{
width: '160px',
height: '160px',
opacity: 0.8,
transform: 'scale(0.7)',
transformOrigin: 'top left',
}}
>
<TopDownMap sensors={sensors} size={200} overlay />
</div>
</div>
) : null}
</div>
<div className="absolute bottom-0.5 left-1/2 flex -translate-x-1/2 items-center gap-1 bg-black/80 px-1 py-0.5 text-[0.8rem] text-slate-100"> <div className="absolute bottom-0.5 left-1/2 flex -translate-x-1/2 items-center gap-1 bg-black/80 px-1 py-0.5 text-[0.8rem] text-slate-100">
<span className="font-semibold text-white">{label || 'Unnamed Rover'}</span> <span className="font-semibold text-white">{label || 'Unnamed Rover'}</span>
{driverLabel ? <span className="text-slate-300"> {driverLabel}</span> : null} {driverLabel ? <span className="text-slate-300"> {driverLabel}</span> : null}
</div> </div>
{showTopDown ? (
<div
className="pointer-events-none absolute right-1 top-1"
style={{
width: '240px',
height: '240px',
opacity: 0.7,
transform: `scale(${mobileHud ? 0.55 : 0.7})`,
transformOrigin: 'top right',
}}
>
<TopDownMap sensors={sensors} size={240} overlay />
</div>
) : null}
</div> </div>
); );
} }
@@ -427,15 +444,13 @@ function HudOverlay({
{/* <span>{pulse ? 'Sensors active' : 'No recent sensors'}</span> */} {/* <span>{pulse ? 'Sensors active' : 'No recent sensors'}</span> */}
</div> </div>
{showTopDown ? ( {showTopDown && variant !== 'spectator' ? (
<div <div
className="pointer-events-none absolute right-1 top-1" className="pointer-events-none absolute rounded"
style={{ style={{
width: '240px', ...mapStyle,
height: '240px', padding: '0.1rem',
opacity: 0.7, background: 'rgba(0,0,0,0.6)',
transform: `scale(${mobileHud ? 0.55 : 0.7})`,
transformOrigin: 'top right',
}} }}
> >
<TopDownMap sensors={sensors} size={240} overlay /> <TopDownMap sensors={sensors} size={240} overlay />
+5 -23
View File
@@ -1,28 +1,10 @@
import { useEffect, useState } from 'react'; import { useSettingsNamespace } from '../settings/index.js';
export function useHudMapSetting() { export function useHudMapSetting() {
const [enabled, setEnabled] = useState(() => { const { value, save } = useSettingsNamespace('page', { hudMapDesktop: false });
if (typeof window === 'undefined') return false; const enabled = Boolean(value?.hudMapDesktop);
return window.localStorage.getItem('hudMapDesktop') === '1'; const setEnabled = (next) => {
}); save({ hudMapDesktop: Boolean(next) });
useEffect(() => {
if (typeof window === 'undefined') return;
window.localStorage.setItem('hudMapDesktop', enabled ? '1' : '0');
const event = new CustomEvent('hudMapDesktopChanged', { detail: enabled });
window.dispatchEvent(event);
}, [enabled]);
useEffect(() => {
if (typeof window === 'undefined') return undefined;
const handler = (event) => {
if (typeof event.detail === 'boolean') {
setEnabled(event.detail);
}
}; };
window.addEventListener('hudMapDesktopChanged', handler);
return () => window.removeEventListener('hudMapDesktopChanged', handler);
}, []);
return [enabled, setEnabled]; return [enabled, setEnabled];
} }
+4
View File
@@ -34,6 +34,8 @@ function RoverSpectatorCard({ rover, frame, videoInfo, audioInfo, session }) {
batteryConfig={rover.battery} batteryConfig={rover.battery}
hudVariant="spectator" hudVariant="spectator"
driverLabel={driverLabel} driverLabel={driverLabel}
hudForceMap
hudMapPosition="bottom-left"
/> />
</div> </div>
</article> </article>
@@ -54,6 +56,8 @@ function RoverRow({ roster, frames, videoSources, session }) {
videoInfo={videoSources[rover.id]} videoInfo={videoSources[rover.id]}
audioInfo={videoSources[`${rover.id}-audio`]} audioInfo={videoSources[`${rover.id}-audio`]}
session={session} session={session}
showHudMap
hudMapPosition="bottom-left"
/> />
))} ))}
</section> </section>