mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-11-15 03:02:17 -05:00
more queue related convergence for the future.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user