mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
thing in HUD
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||
<title>Multi Roomba Rover</title>
|
||||
<script type="module" crossorigin src="/assets/index-bOy4wWKD.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BJwrnf9g.css">
|
||||
<script type="module" crossorigin src="/assets/index-C6EqAuKU.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DkBejE8J.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import KeymapSettings from './KeymapSettings.jsx';
|
||||
import GamepadMappingSettings from './GamepadMappingSettings.jsx';
|
||||
import Tabs, { Tab, TabList, TabPanel, TabPanels } from './Tabs.jsx';
|
||||
import SessionSnapshot from './SessionSnapshot.jsx';
|
||||
import { useHudMapSetting } from '../hooks/useHudMapSetting.js';
|
||||
|
||||
const manualTabs = [
|
||||
{ key: 'start', label: 'Start OI' },
|
||||
@@ -21,6 +22,7 @@ export default function SettingsPanel() {
|
||||
actions: { sendOiCommand, setSensorStream },
|
||||
} = useControlSystem();
|
||||
const canControl = Boolean(roverId);
|
||||
const [hudMapDesktop, setHudMapDesktop] = useHudMapSetting();
|
||||
|
||||
const sensorButtons = useMemo(
|
||||
() => [
|
||||
@@ -39,6 +41,7 @@ export default function SettingsPanel() {
|
||||
<TabList>
|
||||
<Tab id="keybindings">Keybindings</Tab>
|
||||
<Tab id="controller">Controller</Tab>
|
||||
<Tab id="page">Page settings</Tab>
|
||||
<Tab id="admin">Admin</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
@@ -52,6 +55,23 @@ export default function SettingsPanel() {
|
||||
<GamepadMappingSettings />
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel id="page">
|
||||
<div className="space-y-0.5">
|
||||
<section className="panel-section space-y-0.5 text-sm">
|
||||
<p className="text-slate-400">HUD</p>
|
||||
<label className="flex items-center gap-0.5 text-slate-200">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="accent-emerald-500"
|
||||
checked={hudMapDesktop}
|
||||
onChange={(e) => setHudMapDesktop(e.target.checked)}
|
||||
/>
|
||||
<span>Show top-down map in HUD (desktop)</span>
|
||||
</label>
|
||||
<p className="text-xs text-slate-500">Mobile HUD keeps the map on by default.</p>
|
||||
</section>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel id="admin">
|
||||
<div className="space-y-0.5">
|
||||
<section className="panel-section space-y-0.5 text-sm">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
function TopDownMap({ sensors = {}, variant = 'full', size: overrideSize }) {
|
||||
function TopDownMap({ sensors = {}, variant = 'full', size: overrideSize, overlay = false }) {
|
||||
const size = overrideSize || (variant === 'mini' ? 190 : 260);
|
||||
const center = size / 2;
|
||||
const innerCircle = center * 0.8; // keep proportions consistent as size changes
|
||||
@@ -50,9 +50,16 @@ function TopDownMap({ sensors = {}, variant = 'full', size: overrideSize }) {
|
||||
const maxLight = lightMaxSamples.length ? Math.max(...lightMaxSamples, 1200) : 1200;
|
||||
|
||||
return (
|
||||
<div className="surface relative p-1" style={{ height: '100%', width: '100%', aspectRatio: '1 / 1' }}>
|
||||
<div
|
||||
className={`${overlay ? 'relative' : 'surface relative p-1'}`}
|
||||
style={overlay ? { width: `${size}px`, height: `${size}px` } : { height: '100%', width: '100%', aspectRatio: '1 / 1' }}
|
||||
>
|
||||
{!overlay ? (
|
||||
<>
|
||||
<div className="absolute left-1 top-1 text-xs text-slate-400">Top-down</div>
|
||||
<div className="absolute right-1 top-1 text-[0.65rem] text-slate-500">0–{maxLight}</div>
|
||||
</>
|
||||
) : null}
|
||||
<svg width="100%" height="100%" viewBox={`0 0 ${size} ${size}`} preserveAspectRatio="xMidYMid meet" className="mx-auto block">
|
||||
<circle cx={center} cy={center} r={innerCircle} fill="#0f172a" stroke="#334155" strokeWidth="2" />
|
||||
<WheelVisual
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { WhepPlayer } from '../lib/whepPlayer.js';
|
||||
import TopDownMap from './TopDownMap.jsx';
|
||||
import { useHudMapSetting } from '../hooks/useHudMapSetting.js';
|
||||
|
||||
const RESTART_DELAY_MS = 2000;
|
||||
const UNMUTE_RETRY_MS = 3000;
|
||||
@@ -66,6 +68,9 @@ export default function VideoTile({
|
||||
const sensors = telemetryFrame?.sensors;
|
||||
const batteryCharge = sensors?.batteryChargeMah ?? null;
|
||||
const desktopLayout = layoutFormat === 'desktop';
|
||||
const mobileHud = !desktopLayout;
|
||||
const [showHudMapDesktop, setShowHudMapDesktop] = useHudMapSetting();
|
||||
const showHudMap = mobileHud ? true : showHudMapDesktop;
|
||||
const batteryVisual = buildBatteryVisual(batteryCharge, batteryConfig);
|
||||
// console.log('[BatteryBarDebug]', {
|
||||
// frameSensors: sensors,
|
||||
@@ -268,6 +273,7 @@ export default function VideoTile({
|
||||
<audio ref={audioRef} autoPlay hidden />
|
||||
<HudOverlay
|
||||
frame={telemetryFrame}
|
||||
sensors={sensors}
|
||||
label={label}
|
||||
status={renderedStatus}
|
||||
audioStatus={renderedAudioStatus}
|
||||
@@ -276,6 +282,8 @@ export default function VideoTile({
|
||||
driverLabel={driverLabel}
|
||||
battery={batteryVisual}
|
||||
songNote={songNote}
|
||||
showTopDown={showHudMap}
|
||||
mobileHud={mobileHud}
|
||||
/>
|
||||
<OvercurrentOverlay motors={overcurrentMotors} />
|
||||
<LowBatteryOverlay charge={batteryCharge} config={batteryConfig} />
|
||||
@@ -329,6 +337,7 @@ function BatteryBarVertical({ visual }) {
|
||||
|
||||
function HudOverlay({
|
||||
frame,
|
||||
sensors,
|
||||
label,
|
||||
status,
|
||||
audioStatus,
|
||||
@@ -337,8 +346,9 @@ function HudOverlay({
|
||||
driverLabel = null,
|
||||
battery,
|
||||
songNote = null,
|
||||
showTopDown = false,
|
||||
mobileHud = false,
|
||||
}) {
|
||||
const sensors = frame?.sensors;
|
||||
const bumps = sensors?.bumpsAndWheelDrops || {};
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
|
||||
@@ -383,12 +393,20 @@ function HudOverlay({
|
||||
{driverLabel ? <span className="text-slate-300">• {driverLabel}</span> : null}
|
||||
</div>
|
||||
|
||||
<div className="absolute top-0.5 left-1/2 flex -translate-x-1/2 gap-1 bg-black/70 text-[0.6rem] font-medium text-slate-200">
|
||||
<div className={`${desktopLayout ? 'px-1 py-0.5' : 'px-0.5 py-0 text-nowrap'} ${bumps.bumpLeft ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Left bump</div>
|
||||
<div className={`${desktopLayout ? 'px-1 py-0.5' : 'px-0.5 py-0 text-nowrap'} ${bumps.wheelDropLeft ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Left wheel drop</div>
|
||||
<div className={`${desktopLayout ? 'px-1 py-0.5' : 'px-0.5 py-0 text-nowrap'} ${bumps.wheelDropRight ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Right wheel drop</div>
|
||||
<div className={`${desktopLayout ? 'px-1 py-0.5' : 'px-0.5 py-0 text-nowrap'} ${bumps.bumpRight ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Right bump</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>
|
||||
);
|
||||
}
|
||||
@@ -400,7 +418,7 @@ function HudOverlay({
|
||||
{audioStatus ? <div>Audio: {audioStatus}</div> : null}
|
||||
</div>
|
||||
{songNote != null ? (
|
||||
<div className="absolute right-1 top-1 rounded bg-black/70 px-1 py-0.25 text-[0.65rem] font-semibold text-emerald-200">
|
||||
<div className="absolute bottom-1 right-1 rounded bg-black/70 px-1 py-0.25 text-[0.65rem] font-semibold text-emerald-200">
|
||||
Song {formatNoteLabel(songNote)} <span className="text-slate-400">({songNote})</span>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -409,14 +427,20 @@ function HudOverlay({
|
||||
{/* <span>{pulse ? 'Sensors active' : 'No recent sensors'}</span> */}
|
||||
</div>
|
||||
|
||||
|
||||
{/* bump and wheel drops bar */}
|
||||
<div className="absolute top-0.5 left-1/2 flex -translate-x-1/2 gap-1 bg-black/70 text-[0.6rem] font-medium text-slate-200">
|
||||
<div className={`${desktopLayout ? 'px-1 py-0.5':'px-0.5 py-0 text-nowrap'} ${bumps.bumpLeft ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Left bump</div>
|
||||
<div className={`${desktopLayout ? 'px-1 py-0.5':'px-0.5 py-0 text-nowrap'} ${bumps.wheelDropLeft ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Left wheel drop</div>
|
||||
<div className={`${desktopLayout ? 'px-1 py-0.5':'px-0.5 py-0 text-nowrap'} ${bumps.wheelDropRight ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Right wheel drop</div>
|
||||
<div className={`${desktopLayout ? 'px-1 py-0.5':'px-0.5 py-0 text-nowrap'} ${bumps.bumpRight ? 'bg-red-600 text-white animate-pulse' : 'text-slate-500'}`}>Right bump</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function useHudMapSetting() {
|
||||
const [enabled, setEnabled] = useState(() => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return window.localStorage.getItem('hudMapDesktop') === '1';
|
||||
});
|
||||
|
||||
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];
|
||||
}
|
||||
Reference in New Issue
Block a user