more queue related convergence for the future.

This commit is contained in:
legop3
2026-09-27 17:19:56 -04:00
parent 7231809140
commit 34936ab05c
54 changed files with 219 additions and 368 deletions
+4 -1
View File
@@ -4,6 +4,7 @@
// Scope: Mounts input/identity providers; the page owns session entry and layouts.
import AlertFeed from '../components/AlertFeed/index.jsx';
import SocketConnectionPill from '../components/SocketConnectionPill/index.jsx';
import { LayoutProvider } from '../layouts/LayoutContext.jsx';
import PtzControllerPage from './PtzControllerPage.jsx';
import {
ControlSystemProvider,
@@ -26,7 +27,9 @@ function PtzRouteContent() {
<ControlSystemProvider>
<KeyboardInputManager />
<GamepadInputManager />
<PtzControllerPage layout={layout} />
<LayoutProvider layout={layout}>
<PtzControllerPage />
</LayoutProvider>
<AlertFeed />
<SocketConnectionPill />
</ControlSystemProvider>
+3 -1
View File
@@ -1,4 +1,5 @@
import { useCallback, useState } from 'react';
import { useLayout } from '../layouts/LayoutContext.jsx';
import { useNavigate } from 'react-router-dom';
import CardFrame from '../components/CardFrame/index.jsx';
import { useControlActions } from '../controls/index.js';
@@ -9,7 +10,8 @@ import { DEFAULT_PAGE_THEME_KEY, usePageThemeClass } from '../themes/index.js';
import usePtzSession from './usePtzSession.js';
import { PtzDesktopFullscreen, PtzMobileLandscape, PtzMobilePortrait } from './components/PtzLayouts.jsx';
export default function PtzControllerPage({ layout = 'desktop' }) {
export default function PtzControllerPage() {
const layout = useLayout();
const ptz = useSessionSelector((state) => state.session?.ptzCamera || null);
const featureEnabled = useSessionSelector((state) => isFeatureEnabled(state, 'ptzCamera'));
const { setOperatingMode, pushAlert } = useSessionActions();
+3 -3
View File
@@ -11,7 +11,7 @@ function PtzDesktopFullscreen({ ptz, releasePending }) {
<div className="grid h-full min-h-0 grid-rows-[minmax(0,1fr)_minmax(7rem,0.22fr)] gap-0.5 overflow-hidden p-0.5">
<div className="flex min-h-0 min-w-0 gap-0.5 overflow-hidden">
<main className="min-h-0 shrink-0 overflow-hidden bg-black" style={{ aspectRatio: '16 / 9' }}>
<PtzMediaPane ptz={ptz} open framed />
<PtzMediaPane ptz={ptz} open />
</main>
{/* Keep the sidebar itself transparent. Its child cards still own their dark surfaces,
while the shared PTZ page theme can show through the same compact gaps as the driver
@@ -65,7 +65,7 @@ function PtzMobileLandscape({ ptz, onClose, releasePending = false }) {
>
Close
</button>
<PtzMediaPane ptz={ptz} open compact framed={false} />
<PtzMediaPane ptz={ptz} open />
</main>
{/*
The right control column is naturally taller than the viewport.
@@ -116,7 +116,7 @@ function PtzMobilePortrait({ ptz, onClose, releasePending = false }) {
>
Close
</button>
<PtzMediaPane ptz={ptz} open compact framed={false} />
<PtzMediaPane ptz={ptz} open />
</main>
{/*
Portrait gives the video its full available width and places controls
+2 -4
View File
@@ -3,15 +3,13 @@ import { useControlActions, useControlSelector } from '../../controls/index.js';
import ControlHint from '../../components/ControlHint/index.jsx';
import PeripheralPod from '../../components/HudOverlays/newgen/CornerPods/PeripheralPod.jsx';
import RoundControl from '../../components/HudOverlays/newgen/CornerPods/RoundControl.jsx';
import usePodVisibility from '../../components/HudOverlays/newgen/CornerPods/usePodVisibility.js';
export default function PtzLightingPod() {
const [open, setOpen] = usePodVisibility('ptzLighting', true);
export default function PtzLightingPod({ open, onOpenChange }) {
const { setHeadlight, setLaser } = useControlActions();
const canControl = useControlSelector((control) => control.ptzControls.isActive);
const { spotlightOn, irMode, pending } = useControlSelector((control) => control.ptzControls.lighting);
return (
<PeripheralPod open={open} onOpenChange={setOpen} label="camera lights" className="h-24 w-44">
<PeripheralPod open={open} onOpenChange={onOpenChange} label="camera lights" className="h-24 w-44">
<RoundControl label="Spotlight" icon={FaLightbulb} keyLabel={<ControlHint actionId="headlightToggle" />} active={spotlightOn} disabled={!canControl || pending.spotlight} onClick={() => setHeadlight(!spotlightOn)} className="absolute left-5 top-1" />
<RoundControl label={`Infrared: ${irMode}`} icon={FaEye} value={irMode} keyLabel={<ControlHint actionId="laserToggle" />} active={irMode === 'On'} disabled={!canControl || pending.ir} onClick={() => setLaser()} className="absolute right-1 bottom-1" />
</PeripheralPod>
+10 -5
View File
@@ -1,3 +1,5 @@
import { useLayout } from '../../layouts/LayoutContext.jsx';
import usePodVisibility from '../../components/HudOverlays/newgen/CornerPods/usePodVisibility.js';
import PtzLightingPod from './PtzLightingPod.jsx';
import PtzLiveVideo, { PTZ_CAMERA_ID } from '../../components/PtzLiveVideo/index.jsx';
import ChatExpansion from '../../components/HudOverlays/newgen/CornerPods/ChatExpansion.jsx';
@@ -25,11 +27,14 @@ function PtzSnapshotPreview({ feed, label = 'PTZ Camera', className = 'h-full w-
);
}
function PtzMediaPane({ ptz, open, framed = true, compact = false }) {
function PtzMediaPane({ ptz, open }) {
const layout = useLayout();
const isDesktop = layout === 'desktop';
const [lightingOpen, setLightingOpen] = usePodVisibility('ptzLighting', true);
const shouldUseLiveVideo = ptz?.viewMode === 'live';
const snapshotFeeds = usePtzCameraSnapshots([PTZ_CAMERA_ID], { enabled: open && !shouldUseLiveVideo });
const snapshot = snapshotFeeds[PTZ_CAMERA_ID] || null;
const statusClassName = `pointer-events-none absolute left-1 ${compact ? 'bottom-1' : 'bottom-24'} z-20 text-[0.65rem] font-medium text-slate-100`;
const statusClassName = `pointer-events-none absolute left-1 ${isDesktop && lightingOpen ? 'bottom-24' : isDesktop ? 'bottom-10' : 'bottom-1'} z-20 text-[0.65rem] font-medium text-slate-100`;
const media = (
<>
{shouldUseLiveVideo ? (
@@ -37,14 +42,14 @@ function PtzMediaPane({ ptz, open, framed = true, compact = false }) {
) : (
<PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} statusClassName={statusClassName} />
)}
{ptz?.turn ? <TopLeftPod compact={compact} turns={ptz.turn} /> : null}
{ptz?.turn ? <TopLeftPod compact={!isDesktop} turns={ptz.turn} /> : null}
{/* Mobile already has lighting controls beside/below the video. */}
{!compact ? <PtzLightingPod /> : null}
{isDesktop ? <PtzLightingPod open={lightingOpen} onOpenChange={setLightingOpen} /> : null}
<ChatExpansion podOpen={false} />
</>
);
if (!framed) {
if (!isDesktop) {
return <div className="relative h-full min-h-0 w-full overflow-hidden bg-black">{media}</div>;
}
+3 -9
View File
@@ -3,11 +3,10 @@ import { useSessionActions, useSessionSelector } from '../../context/SessionCont
import CardFrame from '../../components/CardFrame/index.jsx';
import { QueueUserChips } from '../../components/QueueTargetRow/index.jsx';
import { PTZ_CAMERA_ID } from '../../components/PtzLiveVideo/index.jsx';
import useQueueUserLookup from '../../hooks/useQueueUserLookup.js';
function PtzQueueSummary({ ptz, title = 'PTZ queue' }) {
const { ptzRequestTurn, ptzRelease, pushAlert } = useSessionActions();
const [pending, setPending] = useState(false);
const participating = Boolean(ptz?.turn?.isActive || ptz?.turn?.turnsAhead);
const participating = Boolean(ptz?.turn?.ownsControl || ptz?.turn?.turnsAhead);
const changeTurn = async () => {
if (pending) return;
setPending(true);
@@ -21,21 +20,16 @@ function PtzQueueSummary({ ptz, title = 'PTZ queue' }) {
}
};
const selfId = useSessionSelector((state) => state.session?.socketId || null);
const lookupUser = useQueueUserLookup(ptz?.turn);
const { queue, currentId, nextId } = ptz?.turn || {};
return (
<CardFrame title={title} bodyClassName="space-y-0.5 p-1 text-sm">
<QueueUserChips
targetId={ptz?.id || PTZ_CAMERA_ID}
queue={queue}
currentId={currentId}
nextId={nextId}
turn={ptz?.turn}
selfId={selfId}
lookupUser={lookupUser}
/>
<button type="button" className="button-dark w-full text-xs" disabled={pending || !(participating ? ptz?.permissions?.canReleaseTurn : ptz?.permissions?.canRequestTurn)} onClick={changeTurn}>
{pending ? 'Please wait…' : ptz?.turn?.isActive ? 'Release turn' : ptz?.turn?.turnsAhead ? 'Leave queue' : 'Request turn'}
{pending ? 'Please wait…' : ptz?.turn?.ownsControl ? 'Release turn' : ptz?.turn?.turnsAhead ? 'Leave queue' : 'Request turn'}
</button>
</CardFrame>
);