This commit is contained in:
legop3
2025-11-15 22:09:56 -05:00
parent d55726eb1f
commit 0caae506f4
9 changed files with 69 additions and 65 deletions
+19 -19
View File
@@ -12,9 +12,9 @@ import { useVideoRequests } from './hooks/useVideoRequests.js';
function RoomCameraPanel() {
return (
<div className="min-h-[8rem] rounded-sm bg-[#242a32] p-1 text-sm text-slate-200">
<p className="text-center text-xs text-slate-400">Room camera</p>
<p className="mt-1 text-center text-sm text-slate-200">Feed placeholder. Wire upcoming room cam here.</p>
<div className="min-h-[8rem] rounded-sm bg-[#242a32] p-2 text-base text-slate-100">
<p className="text-center text-sm text-slate-400">Room camera</p>
<p className="mt-1 text-center">Feed placeholder. Wire upcoming room cam here.</p>
</div>
);
}
@@ -52,12 +52,12 @@ function useLayoutMode() {
function LogPanel() {
const { logs } = useSession();
return (
<div className="rounded-sm bg-[#242a32] p-1 text-xs text-slate-200">
<div className="flex items-center justify-between text-[0.7rem] text-slate-400">
<div className="rounded-sm bg-[#242a32] p-2 text-base text-slate-100">
<div className="flex items-center justify-between text-sm text-slate-400">
<span>Server logs</span>
<span>{logs.length}</span>
</div>
<div className="mt-1 h-48 overflow-y-auto rounded-sm bg-black/40 p-1 font-mono text-[0.65rem] text-slate-300">
<div className="mt-2 h-48 overflow-y-auto rounded-sm bg-black/40 p-2 font-mono text-sm text-slate-300">
{logs.length === 0 ? (
<p>No logs yet.</p>
) : (
@@ -93,7 +93,7 @@ function DriverVideoPanel() {
return (
<section className="rounded-sm bg-[#242a32] p-1">
{roverId ? (
<div className="min-h-[55vh]">
<div className="min-h-[70vh]">
<VideoTile
sessionInfo={info}
label={roverId}
@@ -130,17 +130,17 @@ function AuthPanel() {
}
return (
<div className="rounded-sm bg-[#242a32] p-1 text-sm text-slate-200">
<p className="text-xs text-slate-400">Admin login</p>
<form className="mt-1 flex flex-col gap-1" onSubmit={handleLogin}>
<div className="rounded-sm bg-[#242a32] p-2 text-base text-slate-100">
<p className="text-sm text-slate-400">Admin login</p>
<form className="mt-2 flex flex-col gap-2" onSubmit={handleLogin}>
<input
className="rounded-sm bg-black/50 px-1 py-1 text-slate-100"
className="rounded-sm bg-black/50 px-2 py-1 text-slate-100"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
className="rounded-sm bg-black/50 px-1 py-1 text-slate-100"
className="rounded-sm bg-black/50 px-2 py-1 text-slate-100"
placeholder="Password"
type="password"
value={password}
@@ -149,23 +149,23 @@ function AuthPanel() {
<button
type="submit"
disabled={loading}
className="rounded-sm bg-slate-200 px-1 py-1 text-xs font-semibold text-black disabled:opacity-50"
className="rounded-sm bg-slate-200 px-2 py-1 text-sm font-semibold text-black disabled:opacity-50"
>
{loading ? 'Logging in…' : 'Login'}
</button>
</form>
<div className="mt-1 flex gap-1 text-xs">
<div className="mt-2 flex gap-2 text-sm">
<button
type="button"
onClick={() => setRole('user')}
className="flex-1 rounded-sm bg-black/40 px-1 py-1 text-slate-200"
className="flex-1 rounded-sm bg-black/40 px-2 py-1 text-slate-200"
>
Driver
</button>
<button
type="button"
onClick={() => setRole('spectator')}
className="flex-1 rounded-sm bg-black/40 px-1 py-1 text-slate-200"
className="flex-1 rounded-sm bg-black/40 px-2 py-1 text-slate-200"
>
Spectator
</button>
@@ -177,7 +177,7 @@ function AuthPanel() {
function DesktopLayout() {
return (
<div className="flex flex-col gap-1">
<section className="grid grid-cols-[minmax(0,0.9fr)_minmax(0,1.6fr)_minmax(0,0.9fr)] gap-1">
<section className="grid grid-cols-[minmax(0,0.6fr)_minmax(0,2.2fr)_minmax(0,0.6fr)] gap-1">
<TelemetryPanel />
<DriverVideoPanel />
<DrivePanel />
@@ -212,7 +212,7 @@ function MobilePortraitLayout() {
function MobileLandscapeLayout() {
return (
<div className="flex flex-col gap-1">
<section className="grid grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)_minmax(0,1fr)] gap-1">
<section className="grid grid-cols-[minmax(0,0.7fr)_minmax(0,2.1fr)_minmax(0,0.7fr)] gap-1">
<div className="flex flex-col gap-1">
<DrivePanel />
<AuxMotorControls />
@@ -244,7 +244,7 @@ function App() {
return (
<div className="min-h-screen bg-black text-slate-50">
<DriveControlProvider>
<main className="flex w-full flex-col gap-1 px-1 py-1">{renderedLayout}</main>
<main className="flex w-full flex-col gap-1 px-1 py-1 text-base">{renderedLayout}</main>
<AlertFeed />
</DriveControlProvider>
</div>
+9 -9
View File
@@ -57,13 +57,13 @@ export default function AdminPanel() {
if (!isAdmin) return null;
return (
<section className="rounded-sm bg-[#242a32] p-1 text-xs text-slate-200">
<div className="flex items-center justify-between gap-1">
<section className="rounded-sm bg-[#242a32] p-2 text-base text-slate-100">
<div className="flex items-center justify-between gap-2 text-sm">
<span>Admin controls</span>
<select
value={currentMode}
onChange={handleModeChange}
className="rounded-sm bg-black/40 px-1 py-0.5 text-[0.7rem] text-slate-200"
className="rounded-sm bg-black/40 px-2 py-1 text-sm text-slate-100"
>
{MODES.map((mode) => (
<option key={mode.key} value={mode.key}>
@@ -73,25 +73,25 @@ export default function AdminPanel() {
</select>
</div>
<div className="mt-1 space-y-1">
<div className="mt-2 space-y-2 text-sm">
{roster.map((rover) => (
<div key={rover.id} className="flex flex-wrap items-center justify-between gap-1 rounded-sm bg-black/30 px-1 py-1">
<div key={rover.id} className="flex flex-wrap items-center justify-between gap-2 rounded-sm bg-black/30 px-2 py-2">
<div>
<p className="text-slate-100">{rover.name}</p>
<p className="text-[0.6rem] text-slate-500">{lockMap[rover.id] ? 'locked' : 'unlocked'}</p>
<p className="text-xs text-slate-400">{lockMap[rover.id] ? 'locked' : 'unlocked'}</p>
</div>
<div className="flex flex-wrap gap-1 text-[0.6rem]">
<div className="flex flex-wrap gap-1 text-xs">
<button
type="button"
onClick={() => handleLockToggle(rover.id, !lockMap[rover.id])}
className="rounded-sm bg-black/50 px-1 py-0.5 text-slate-100"
className="rounded-sm bg-black/50 px-2 py-1 text-slate-100"
>
{lockMap[rover.id] ? 'Unlock' : 'Lock'}
</button>
<button
type="button"
onClick={() => handleForceControl(rover.id)}
className="rounded-sm bg-black/50 px-1 py-0.5 text-slate-100"
className="rounded-sm bg-black/50 px-2 py-1 text-slate-100"
>
Force
</button>
+17 -16
View File
@@ -19,17 +19,17 @@ export default function DrivePanel() {
const updated = frame?.receivedAt ? new Date(frame.receivedAt).toLocaleTimeString() : null;
return (
<section className="rounded-sm bg-[#242a32] p-1 text-sm text-slate-100">
<div className="flex items-center justify-between text-xs text-slate-300">
<section className="rounded-sm bg-[#242a32] p-2 text-base text-slate-100">
<div className="flex items-center justify-between text-sm text-slate-300">
<span>Drive control</span>
<span>{roverId ? `Rover ${roverId}` : 'unassigned'}</span>
</div>
<div className="mt-1 space-y-1">
<div className="mt-2 space-y-2">
<ActionCard
title="Start Driving"
description="Press to enable driving mode, then start moving. The headlamps should illuminate."
statuses={[{ label: drivingMode ? 'Ready!' : 'Not Ready!', active: drivingMode }]}
gradient="from-emerald-600 to-emerald-400"
tone="emerald"
onClick={runStartDockFull}
disabled={!roverId}
footnote={updated ? `Sensor ping ${updated}` : null}
@@ -41,13 +41,13 @@ export default function DrivePanel() {
{ label: docked ? 'Docked!' : 'Not Docked!', active: docked },
{ label: charging ? 'Charging!' : 'Not Charging!', active: charging },
]}
gradient="from-indigo-600 to-purple-500"
tone="indigo"
onClick={seekDock}
disabled={!roverId}
/>
<SpeedRow left={speeds.left} right={speeds.right} />
<div>
<p className="text-xs text-slate-400">Manual OI</p>
<p className="text-sm text-slate-300">Manual OI</p>
<div className="mt-1 flex flex-wrap gap-1">
{manualOiButtons.map((btn) => (
<button
@@ -55,7 +55,7 @@ export default function DrivePanel() {
type="button"
onClick={() => sendOiCommand(btn.key)}
disabled={!roverId}
className="rounded-sm bg-black/40 px-1 py-0.5 text-[0.65rem] text-slate-200 disabled:opacity-30"
className="rounded-sm bg-black/40 px-2 py-0.5 text-sm text-slate-200 disabled:opacity-30"
>
{btn.label}
</button>
@@ -67,27 +67,28 @@ export default function DrivePanel() {
type="button"
onClick={stopMotors}
disabled={!roverId}
className="flex-1 rounded-sm bg-red-600/60 px-1 py-1 text-xs text-white disabled:opacity-40"
className="flex-1 rounded-sm bg-red-600/70 px-2 py-1 text-sm text-white disabled:opacity-40"
>
Stop motors
</button>
</div>
</div>
<p className="mt-1 text-[0.65rem] text-slate-500">Sensor streaming auto-starts after each OI change.</p>
<p className="mt-2 text-sm text-slate-400">Sensor streaming auto-starts after each OI change.</p>
</section>
);
}
function ActionCard({ title, description, statuses, gradient, onClick, disabled, footnote }) {
function ActionCard({ title, description, statuses, tone, onClick, disabled, footnote }) {
const baseColor = tone === 'indigo' ? 'bg-indigo-600' : 'bg-emerald-600';
return (
<button
type="button"
onClick={onClick}
disabled={disabled}
className={`w-full rounded-lg bg-gradient-to-r ${gradient} px-2 py-2 text-left text-white disabled:opacity-40`}
className={`w-full rounded-lg ${baseColor} px-3 py-2 text-left text-white disabled:opacity-40`}
>
<p className="text-base font-semibold">{title}</p>
<p className="text-sm text-emerald-50/80">{description}</p>
<p className="text-sm text-white/90">{description}</p>
<div className="mt-2 flex flex-wrap gap-1">
{statuses.map((status) => (
<span
@@ -107,13 +108,13 @@ function ActionCard({ title, description, statuses, gradient, onClick, disabled,
function SpeedRow({ left, right }) {
return (
<div className="grid grid-cols-2 gap-1 rounded-sm bg-black/40 p-1 text-[0.7rem]">
<div className="grid grid-cols-2 gap-1 rounded-sm bg-black/40 p-2 text-base">
<div>
<p className="text-[0.6rem] text-slate-500">Left</p>
<p className="text-sm text-slate-300">Left</p>
<p className="font-semibold text-slate-100">{left}</p>
</div>
<div>
<p className="text-[0.6rem] text-slate-500">Right</p>
<p className="text-sm text-slate-300">Right</p>
<p className="font-semibold text-slate-100">{right}</p>
</div>
</div>
@@ -123,7 +124,7 @@ function SpeedRow({ left, right }) {
function StatusPill({ label, active }) {
return (
<span
className={`rounded-sm px-1 py-0.5 text-[0.6rem] ${
className={`rounded-sm px-2 py-0.5 text-xs ${
active ? 'bg-emerald-500/30 text-emerald-100' : 'bg-black/40 text-slate-500'
}`}
>
+10 -10
View File
@@ -38,8 +38,8 @@ export default function TelemetryPanel() {
}
return (
<section className="rounded-sm bg-[#242a32] p-1 text-sm text-slate-200">
<div className="text-[0.7rem] text-slate-400">
<section className="rounded-sm bg-[#242a32] p-2 text-base text-slate-100">
<div className="text-sm text-slate-400">
<span>{connected ? 'online' : 'offline'}</span>
<span> · role {session?.role || 'unknown'}</span>
<span> · mode {session?.mode || '--'}</span>
@@ -50,7 +50,7 @@ export default function TelemetryPanel() {
) : !frame ? (
<p className="mt-1 text-[0.75rem] text-slate-400">Waiting for sensor frames</p>
) : (
<div className="mt-1 space-y-1 text-[0.75rem]">
<div className="mt-2 space-y-1 text-base">
<Metric label="Charge" value={formatMetric(charge != null && capacity != null ? `${charge}/${capacity} mAh` : null)} />
<Metric label="Charging" value={formatMetric(sensors.chargingState?.label)} />
<Metric label="OI mode" value={formatMetric(sensors.oiMode?.label)} />
@@ -59,21 +59,21 @@ export default function TelemetryPanel() {
</div>
)}
{rawSnippet && (
<pre className="mt-1 overflow-hidden rounded-sm bg-black/60 p-1 text-[0.6rem] text-lime-300">
<pre className="mt-2 overflow-hidden rounded-sm bg-black/60 p-2 text-sm text-lime-300">
{rawSnippet}
</pre>
)}
<div className="mt-1">
<p className="text-xs text-slate-400">Rovers</p>
<div className="mt-2">
<p className="text-sm text-slate-400">Rovers</p>
{roster.length === 0 ? (
<p className="text-xs text-slate-500">No roster data.</p>
<p className="text-sm text-slate-500">No roster data.</p>
) : (
<ul className="mt-1 space-y-1 text-xs">
<ul className="mt-2 space-y-1 text-sm">
{roster.map((rover) => (
<li key={rover.id} className="flex items-center justify-between gap-1">
<div>
<p className="text-slate-200">{rover.name}</p>
<p className="text-[0.6rem] text-slate-500">
<p className="text-xs text-slate-500">
{rover.locked ? 'locked' : 'free'} · {rover.lastSeen ? 'seen' : 'unknown'}
</p>
</div>
@@ -82,7 +82,7 @@ export default function TelemetryPanel() {
type="button"
onClick={() => handleRequest(rover.id)}
disabled={pending[rover.id]}
className="rounded-sm bg-black/40 px-1 py-0.5 text-[0.6rem] text-slate-200 disabled:opacity-40"
className="rounded-sm bg-black/40 px-2 py-0.5 text-xs text-slate-200 disabled:opacity-40"
>
{pending[rover.id] ? '...' : 'request'}
</button>
+10 -7
View File
@@ -145,9 +145,12 @@ export default function VideoTile({ sessionInfo, label, forceMute = false, telem
function BatteryBar({ charge, capacity, config, label, status }) {
if (charge == null || !config?.full || config.warn == null) {
return (
<div className="flex items-center justify-between text-xs text-slate-400">
<span>{label}</span>
<span>{status}</span>
<div className="rounded-sm bg-[#1e1e1e] px-2 py-1 text-sm text-slate-200">
<div className="flex items-center justify-between text-xs text-slate-400">
<span>{label}</span>
<span>{status}</span>
</div>
<p className="mt-1 text-xs text-slate-400">Battery telemetry unavailable</p>
</div>
);
}
@@ -161,16 +164,16 @@ function BatteryBar({ charge, capacity, config, label, status }) {
const barClass = depleted ? 'bg-red-500 animate-pulse' : urgent ? 'bg-amber-400' : 'bg-emerald-500';
const capText = capacity ? `${charge}/${capacity}` : `${charge}`;
return (
<div className="space-y-1 rounded-sm bg-[#111] p-1 text-xs text-slate-200">
<div className="flex items-center justify-between text-[0.65rem] text-slate-400">
<div className="space-y-2 rounded-sm bg-[#1e1e1e] px-2 py-2 text-sm text-slate-200">
<div className="flex items-center justify-between text-xs text-slate-400">
<span>{label}</span>
<span>{status}</span>
</div>
<div className="flex items-center justify-between text-[0.7rem]">
<div className="flex items-center justify-between text-sm">
<span>Battery</span>
<span>{capText} mAh</span>
</div>
<div className="h-2 w-full rounded-full bg-slate-800">
<div className="h-4 w-full rounded-full bg-slate-800">
<div className={`h-full rounded-full transition-[width] ${barClass}`} style={{ width: `${percentDisplay}%` }} />
</div>
</div>