pass 1 (broken)

This commit is contained in:
legop3
2026-05-02 15:39:38 -04:00
parent 53cf39e7fb
commit ba4b6cfc78
17 changed files with 247 additions and 182 deletions
@@ -2,14 +2,14 @@
// Purpose: Defines the Global Objective Banner module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useEffect, useMemo, useRef, useState } from 'react';
import { useSession } from '../../context/SessionContext.jsx';
import { useSessionSelector } from '../../context/SessionContext.jsx';
const MOBILE_DISMISS_MS = 10000;
const MAX_FONT_PX = 28;
const MIN_FONT_PX = 14;
export default function GlobalObjectiveBanner({ layout = 'desktop', className = '', dismissable = true }) {
const { session } = useSession();
const session = useSessionSelector((state) => state.session);
const goalText = session?.globalObjective?.text ? String(session.globalObjective.text).trim() : '';
const isMobile = layout === 'mobile-portrait' || layout === 'mobile-landscape' || layout === 'mobile';
const [visible, setVisible] = useState(false);
+3 -4
View File
@@ -2,9 +2,11 @@
// Purpose: Defines the Log Panel module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useSessionSelector } from '../../context/SessionContext.jsx';
import { useMemo } from 'react';
export default function LogPanel() {
const logs = useSessionSelector((state) => state.logs);
const rendered = useMemo(() => logs.slice().reverse(), [logs]);
return (
<div className="panel-section space-y-0.5 text-base">
<div className="flex items-center justify-between text-sm text-slate-400">
@@ -15,10 +17,7 @@ export default function LogPanel() {
{logs.length === 0 ? (
<p>No logs yet.</p>
) : (
logs
.slice()
.reverse()
.map((entry) => (
rendered.map((entry) => (
<div key={entry.id} className="surface">
<span className="text-amber-400">{entry.timestamp}</span>{' '}
<span className="text-lime-400">[{entry.level}]</span>{' '}
@@ -2,7 +2,7 @@
// Purpose: Defines the Raw User Pile Panel module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useMemo } from 'react';
import { useSession } from '../../context/SessionContext.jsx';
import { useSessionSelector } from '../../context/SessionContext.jsx';
import NicknameForm from '../NicknameForm/index.jsx';
import SocialButtonsGrid from '../SocialButtonsGrid/index.jsx';
@@ -35,7 +35,7 @@ export default function RawUserPilePanel({
fillHeight = false,
compact = false,
}) {
const { session } = useSession();
const session = useSessionSelector((state) => state.session);
const canSetNickname = session?.role !== 'spectator';
const users = session?.users ?? [];
const selfId = session?.socketId || null;
+4 -2
View File
@@ -23,6 +23,7 @@ import VipPanel from '../VipPanel/index.jsx';
import { useSessionSelector } from '../../context/SessionContext.jsx';
import { useSettingsNamespace } from '../../settings/index.js';
import ButtonBoxPanel from '../ButtonBoxPanel/index.jsx';
import { useState } from 'react';
function TopDownMapPanel() {
const {
@@ -115,6 +116,7 @@ function DriveDockPanel() {
}
export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
const [activeTab, setActiveTab] = useState('telemetry');
const session = useSessionSelector((state) => state.session);
const { state: controlState } = useControlSystem();
const { value: vipAudio } = useSettingsNamespace('vipAudio', { openMicEnabled: false, pttMode: 'live' });
@@ -138,7 +140,7 @@ export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
);
return (
<section className="panel text-base">
<Tabs defaultTab="telemetry">
<Tabs defaultTab="telemetry" currentTab={activeTab} onTabChange={setActiveTab}>
<TabList>
<Tab id="telemetry">Controls</Tab>
<Tab id="vip" highlight={vipClipPlaying ? 'green' : vipMicActive ? 'pink' : 'none'}>
@@ -179,7 +181,7 @@ export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
</div>
</TabPanel>
<TabPanel id="vip" keepMounted>
<VipPanel />
<VipPanel isActive={activeTab === 'vip'} />
</TabPanel>
<TabPanel id="help">
<HelpPanel layout={layout} onOpenOverlay={onOpenHelpOverlay} />
@@ -2,7 +2,7 @@
// Purpose: Defines the Room Camera Panel module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useEffect, useState } from 'react';
import { useSession } from '../../context/SessionContext.jsx';
import { useSessionSelector } from '../../context/SessionContext.jsx';
import { useSettingsNamespace } from '../../settings/index.js';
import { useRoomCameraSnapshots } from '../../hooks/useRoomCameraSnapshots.js';
import RoomCameraFeed from '../RoomCameraFeed/index.jsx';
@@ -32,7 +32,7 @@ export default function RoomCameraPanel({
hideHeader = false,
panelId = null,
}) {
const { session } = useSession();
const session = useSessionSelector((state) => state.session);
const cameras = session?.roomCameras || [];
const feedMap = useRoomCameraSnapshots(cameras.map((camera) => ({ id: camera.id })));
const { value: orientationSettings, save: saveOrientationSettings } = useSettingsNamespace('roomCameraPanels', {});
@@ -13,7 +13,7 @@ import {
cliffColor,
} from './visuals.jsx';
export default function TopDownMapContent({ sensors = {}, variant = 'full', size: overrideSize, overlay = false }) {
function TopDownMapContent({ sensors = {}, variant = 'full', size: overrideSize, overlay = false }) {
const size = overrideSize || (variant === 'mini' ? 190 : 260);
const center = size / 2;
const offsetY = size * 0.07;
@@ -147,3 +147,5 @@ export default function TopDownMapContent({ sensors = {}, variant = 'full', size
</div>
);
}
export default React.memo(TopDownMapContent);
@@ -5,7 +5,7 @@ import React from 'react';
import TopDownMap from '../TopDownMap/index.jsx';
import { roverNameChromeStyle } from '../../lib/roverColor.js';
export default function HudOverlay({
function HudOverlay({
sensors,
label,
roverColor = null,
@@ -174,3 +174,5 @@ export default function HudOverlay({
</div>
);
}
export default React.memo(HudOverlay);
@@ -3,7 +3,7 @@
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import React from 'react';
export default function LightBumpBars({ sensors }) {
function LightBumpBars({ sensors }) {
const values = [
sensors?.lightBumpLeftSignal,
sensors?.lightBumpFrontLeftSignal,
@@ -47,3 +47,5 @@ export default function LightBumpBars({ sensors }) {
</div>
);
}
export default React.memo(LightBumpBars);
+12 -4
View File
@@ -100,10 +100,18 @@ export default function VideoTile({
.map(([key]) => key);
const limiterCaps = overcurrentLimiter?.caps || null;
const limiterGroups = overcurrentLimiter?.overcurrent?.groups || null;
const debugAudio =
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('debugAudio');
const debugHud =
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('debugHud');
const debugFlags = useMemo(() => {
if (typeof window === 'undefined') {
return { debugAudio: false, debugHud: false };
}
const params = new URLSearchParams(window.location.search);
return {
debugAudio: params.has('debugAudio'),
debugHud: params.has('debugHud'),
};
}, []);
const debugAudio = debugFlags.debugAudio;
const debugHud = debugFlags.debugHud;
const limiterFill = useMemo(() => {
if (!limiterCaps) return null;
const driveCap = Number.isFinite(limiterCaps?.drive?.cap) ? limiterCaps.drive.cap : 1;
+6 -5
View File
@@ -2,7 +2,7 @@
// Purpose: Defines the Vip Panel module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useMemo, useState } from 'react';
import { useSession } from '../../context/SessionContext.jsx';
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
import { useSettingsNamespace } from '../../settings/index.js';
import { COOKIE_KEY_REGEX, flowWrapClass } from '../vip/constants.js';
import VipAudioUploadCard from '../vip/VipAudioUploadCard/index.jsx';
@@ -12,10 +12,10 @@ import VipPrivateRoverAccessCard from '../vip/VipPrivateRoverAccessCard.jsx';
import VipNeatoCard from '../vip/VipNeatoCard.jsx';
import VipLiftCard from '../vip/VipLiftCard.jsx';
export default function VipPanel() {
export default function VipPanel({ isActive = true }) {
const session = useSessionSelector((state) => state.session);
const neatoLidar = useSessionSelector((state) => state.neatoLidar);
const {
session,
neatoLidar,
identifySession,
requestVerification,
requestPrivateRoverAccess,
@@ -31,7 +31,7 @@ export default function VipPanel() {
neatoPowerCycle,
liftUp,
liftDown,
} = useSession();
} = useSessionActions();
const { value: identity, save: saveIdentity } = useSettingsNamespace('identity', { cookieUserId: '' });
const { value: profile } = useSettingsNamespace('profile', { nickname: '' });
@@ -80,6 +80,7 @@ export default function VipPanel() {
<VipNeatoCard
neato={session?.neato || null}
lidar={neatoLidar}
lidarActive={isActive}
onStart={neatoStart}
onSendHome={neatoSendHome}
onLocate={neatoLocate}
+8 -3
View File
@@ -1,7 +1,7 @@
// Vip Neato Card
// Purpose: Defines the Vip Neato Card module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
function normalizeState(value) {
return String(value || '').trim();
@@ -114,6 +114,7 @@ function buildLidarRenderPoints(points = []) {
export default function VipNeatoCard({
neato,
lidar,
lidarActive = true,
onStart,
onSendHome,
onLocate,
@@ -140,7 +141,10 @@ export default function VipNeatoCard({
const robotError = normalizeState(neato?.telemetry?.robotError) || '--';
const robotAlert = normalizeState(neato?.telemetry?.robotAlert) || '--';
const lidarPoints = Array.isArray(lidar?.points) ? lidar.points : [];
const lidarRenderPoints = buildLidarRenderPoints(lidarPoints);
const lidarRenderPoints = useMemo(
() => (lidarActive ? buildLidarRenderPoints(lidarPoints) : []),
[lidarActive, lidarPoints],
);
const lidarStatus = normalizeState(lidar?.status) || '--';
const lidarDebug = lidar?.debug && typeof lidar.debug === 'object' ? lidar.debug : null;
const lidarReason = normalizeState(lidarDebug?.reason) || '--';
@@ -180,13 +184,14 @@ export default function VipNeatoCard({
const primaryState = docked ? 'Docked' : uiStateLabel !== '--' ? uiStateLabel : 'Away from dock';
useEffect(() => {
if (!lidarActive) return undefined;
if (!lidar || !Array.isArray(lidar.points)) return undefined;
setLidarFlash(true);
const timer = setTimeout(() => {
setLidarFlash(false);
}, 120);
return () => clearTimeout(timer);
}, [lidar]);
}, [lidar, lidarActive]);
return (
<section className={`surface text-sm text-slate-200 ${wrapClass}`}>