mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
gawawa
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
File diff suppressed because one or more lines are too long
@@ -78,8 +78,8 @@
|
||||
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
||||
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
||||
<title>Roomba Rover</title>
|
||||
<script type="module" crossorigin src="/assets/index-CYuyI8Zg.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CoyqmROJ.css">
|
||||
<script type="module" crossorigin src="/assets/index-Dp_4HZ7O.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-LTXnNyH3.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
+1
-1
@@ -206,7 +206,7 @@ function MobileFeatureTabs({
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel id="vip" keepMounted>
|
||||
<VipPanel isActive={activeTab === 'vip'} />
|
||||
<VipPanel isActive={activeTab === 'vip'} layout={layout} />
|
||||
</TabPanel>
|
||||
<TabPanel id="roomcontrols">
|
||||
<div className={themeStackClass}>
|
||||
|
||||
@@ -425,7 +425,7 @@ export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
|
||||
|
||||
{/* VIP tab */}
|
||||
<TabPanel id="vip" keepMounted>
|
||||
<VipPanel isActive={activeTab === 'vip'} />
|
||||
<VipPanel isActive={activeTab === 'vip'} layout={layout} />
|
||||
</TabPanel>
|
||||
|
||||
{/* help tab */}
|
||||
|
||||
@@ -13,7 +13,7 @@ import VipPrivateRoverAccessCard from '../vip/VipPrivateRoverAccessCard.jsx';
|
||||
import VipProfileImageCard from '../vip/VipProfileImageCard.jsx';
|
||||
import VipPtzCameraCard from '../vip/VipPtzCameraCard.jsx';
|
||||
|
||||
export default function VipPanel() {
|
||||
export default function VipPanel({ layout = 'desktop' }) {
|
||||
const session = useSessionSelector((state) => state.session);
|
||||
const {
|
||||
identifySession,
|
||||
@@ -70,7 +70,7 @@ export default function VipPanel() {
|
||||
<div className="lg:col-span-2">
|
||||
{isVerified ? (
|
||||
<div className="space-y-2">
|
||||
<VipPtzCameraCard onMessage={setMessage} fullWidth />
|
||||
<VipPtzCameraCard onMessage={setMessage} fullWidth layout={layout} />
|
||||
<VipMidiBeeperCard />
|
||||
<VipAudioUploadCard
|
||||
ownRoverId={ownRoverId}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// Purpose: Provides the verified-user entry point and fullscreen controller for the single Reolink PTZ camera.
|
||||
// Scope: Owns PTZ UI state only; server-side PTZ ownership, rover handoff, and command authorization remain authoritative.
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import CardFrame from '../CardFrame/index.jsx';
|
||||
import GPIOToggleControl from '../GPIOToggleControl/index.jsx';
|
||||
import ReplaySourcesPanel from '../ReplaySourcesPanel/index.jsx';
|
||||
import KeyPill from './VipAudioUploadCard/KeyPill.jsx';
|
||||
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
import { useControlSelector } from '../../controls/index.js';
|
||||
import { formatKeyLabel } from '../../controls/keymapUtils.js';
|
||||
@@ -356,31 +358,34 @@ function keyLabelFor(keymap, actionId) {
|
||||
function PtzControlReference() {
|
||||
const keymap = useControlSelector((control) => control.state.keymap);
|
||||
const rows = [
|
||||
['Tilt up', keyLabelFor(keymap, 'driveForward')],
|
||||
['Tilt down', keyLabelFor(keymap, 'driveBackward')],
|
||||
['Pan left', keyLabelFor(keymap, 'driveLeft')],
|
||||
['Pan right', keyLabelFor(keymap, 'driveRight')],
|
||||
['Zoom in', keyLabelFor(keymap, 'cameraUp')],
|
||||
['Zoom out', keyLabelFor(keymap, 'cameraDown')],
|
||||
['Spotlight', keyLabelFor(keymap, 'headlightToggle')],
|
||||
['Infrared mode', keyLabelFor(keymap, 'laserToggle')],
|
||||
['Tilt up', 'driveForward'],
|
||||
['Tilt down', 'driveBackward'],
|
||||
['Pan left', 'driveLeft'],
|
||||
['Pan right', 'driveRight'],
|
||||
['Zoom in', 'cameraUp'],
|
||||
['Zoom out', 'cameraDown'],
|
||||
['Spotlight', 'headlightToggle'],
|
||||
['Infrared mode', 'laserToggle'],
|
||||
];
|
||||
|
||||
return (
|
||||
<CardFrame title="Controls" bodyClassName="space-y-0.5 p-1 text-xs">
|
||||
{rows.map(([label, value]) => (
|
||||
{rows.map(([label, actionId]) => (
|
||||
<div key={label} className="surface flex items-center justify-between gap-1">
|
||||
<span className="text-slate-400">{label}</span>
|
||||
<span className="truncate text-slate-100">{value}</span>
|
||||
{/* Use the same key display component as the rest of the UI so PTZ
|
||||
controls read as normal mapped controls instead of custom labels. */}
|
||||
<KeyPill label={keyLabelFor(keymap, actionId)} />
|
||||
</div>
|
||||
))}
|
||||
</CardFrame>
|
||||
);
|
||||
}
|
||||
|
||||
function PtzController({ open, onClose }) {
|
||||
function PtzController({ open, onClose, layout = 'desktop' }) {
|
||||
const ptz = useSessionSelector((state) => state.session?.ptzCamera || null);
|
||||
const isOperator = Boolean(ptz?.isOperator);
|
||||
const isMobile = layout === 'mobile-portrait' || layout === 'mobile-landscape';
|
||||
const { ptzRelease } = useSessionActions();
|
||||
const snapshot = usePtzCameraSnapshot({ enabled: open && !isOperator });
|
||||
const [releasePending, setReleasePending] = useState(false);
|
||||
@@ -397,61 +402,109 @@ function PtzController({ open, onClose }) {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
const desktopSidebar = (
|
||||
<>
|
||||
<div className="shrink-0">
|
||||
<PtzStatePanel
|
||||
ptz={ptz}
|
||||
onClose={onClose}
|
||||
onRelease={isOperator ? releaseAndClose : null}
|
||||
releaseDisabled={releasePending}
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<PtzQueueList queue={ptz?.queue} operatorLabel={ptz?.operatorLabel} />
|
||||
</div>
|
||||
{isOperator ? (
|
||||
<div className="shrink-0">
|
||||
<PtzLightingControls ptz={ptz} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="shrink-0">
|
||||
<CardFrame title="Controls" bodyClassName="p-1 text-xs text-slate-400">
|
||||
Live PTZ controls unlock when your camera turn is active.
|
||||
</CardFrame>
|
||||
</div>
|
||||
)}
|
||||
<div className="shrink-0">
|
||||
<PtzControlReference />
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<ReplaySourcesPanel panelId="ptz-controller-replay" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const mobileSidebar = (
|
||||
<>
|
||||
<div className="shrink-0">
|
||||
<PtzStatePanel
|
||||
ptz={ptz}
|
||||
onClose={onClose}
|
||||
onRelease={isOperator ? releaseAndClose : null}
|
||||
releaseDisabled={releasePending}
|
||||
/>
|
||||
</div>
|
||||
{isOperator ? (
|
||||
<>
|
||||
<div className="shrink-0">
|
||||
<PtzLightingControls ptz={ptz} />
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<PtzMobileZoomButtons />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="shrink-0">
|
||||
<CardFrame title="Controls" bodyClassName="p-1 text-xs text-slate-400">
|
||||
Live PTZ controls unlock when your camera turn is active.
|
||||
</CardFrame>
|
||||
</div>
|
||||
)}
|
||||
<div className="shrink-0">
|
||||
<PtzQueueList queue={ptz?.queue} operatorLabel={ptz?.operatorLabel} />
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<ReplaySourcesPanel panelId="ptz-controller-replay-mobile" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
const sidebarWidthClass = isMobile
|
||||
? 'grid-cols-[minmax(0,1fr)_12rem]'
|
||||
: 'grid-cols-[minmax(0,1fr)_20rem]';
|
||||
|
||||
const controller = (
|
||||
<div className="fixed inset-0 z-[110] h-[100dvh] w-[100vw] overflow-hidden bg-black text-slate-100">
|
||||
<CardFrame
|
||||
hideHeader
|
||||
fillHeight
|
||||
clipOverflow={false}
|
||||
className="h-[100dvh] w-[100vw] rounded-none border-0 !bg-black"
|
||||
bodyClassName="grid h-full min-h-0 overflow-hidden grid-cols-[minmax(0,1fr)_12rem] md:grid-cols-[minmax(0,1fr)_20rem]"
|
||||
bodyClassName={`grid h-full min-h-0 overflow-hidden ${sidebarWidthClass}`}
|
||||
>
|
||||
<main className="relative min-h-0 min-w-0 bg-black">
|
||||
{isOperator ? <PtzLiveVideo enabled /> : <PtzSnapshotPreview feed={snapshot} label={ptz?.name || 'PTZ Camera'} />}
|
||||
</main>
|
||||
<aside className="flex h-full min-h-0 items-stretch overflow-hidden border-l border-neutral-600 bg-neutral-950 text-sm">
|
||||
<div className="flex min-h-0 w-full flex-col gap-0.5 overflow-y-auto p-0.5">
|
||||
<div className="shrink-0">
|
||||
<PtzStatePanel
|
||||
ptz={ptz}
|
||||
onClose={onClose}
|
||||
onRelease={isOperator ? releaseAndClose : null}
|
||||
releaseDisabled={releasePending}
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<PtzQueueList queue={ptz?.queue} operatorLabel={ptz?.operatorLabel} />
|
||||
</div>
|
||||
{isOperator ? (
|
||||
<>
|
||||
<div className="shrink-0">
|
||||
<PtzLightingControls ptz={ptz} />
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<PtzMobileZoomButtons />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="shrink-0">
|
||||
<CardFrame title="Controls" bodyClassName="p-1 text-xs text-slate-400">
|
||||
Live PTZ controls unlock when your camera turn is active.
|
||||
</CardFrame>
|
||||
</div>
|
||||
)}
|
||||
<div className="shrink-0">
|
||||
<PtzControlReference />
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<ReplaySourcesPanel panelId="ptz-controller-replay" />
|
||||
</div>
|
||||
{isMobile ? mobileSidebar : desktopSidebar}
|
||||
</div>
|
||||
</aside>
|
||||
</CardFrame>
|
||||
</div>
|
||||
);
|
||||
|
||||
/*
|
||||
The controller is a true fullscreen surface, so mount it directly under
|
||||
document.body instead of inside the VIP tab/card tree. That keeps tab panel
|
||||
spacing, mobile banners, and parent overflow rules from creating visible
|
||||
gaps around a fixed-position camera interface.
|
||||
*/
|
||||
return createPortal(controller, document.body);
|
||||
}
|
||||
|
||||
export default function VipPtzCameraCard({ onMessage, fullWidth = false }) {
|
||||
export default function VipPtzCameraCard({ onMessage, fullWidth = false, layout = 'desktop' }) {
|
||||
const featureEnabled = useSessionSelector((state) => isFeatureEnabled(state, 'ptzCamera'));
|
||||
const ptz = useSessionSelector((state) => state.session?.ptzCamera || null);
|
||||
const isVerified = useSessionSelector((state) => Boolean(state.session?.isVerified));
|
||||
@@ -556,7 +609,7 @@ export default function VipPtzCameraCard({ onMessage, fullWidth = false }) {
|
||||
</div>
|
||||
</div>
|
||||
</CardFrame>
|
||||
<PtzController open={controllerOpen} onClose={() => setControllerOpen(false)} />
|
||||
<PtzController open={controllerOpen} onClose={() => setControllerOpen(false)} layout={layout} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user