mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
vertical room cameras
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
@@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>webui</title>
|
<title>webui</title>
|
||||||
<script type="module" crossorigin src="/assets/index-BGeippT_.js"></script>
|
<script type="module" crossorigin src="/assets/index-DBM9rsb_.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-ZQklSSJR.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BlCqEIWr.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default function RightPaneTabs({ layout }) {
|
|||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="room">
|
<TabPanel id="room">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<RoomCameraPanel />
|
<RoomCameraPanel defaultOrientation="vertical" />
|
||||||
<RoomControlsPlaceholder />
|
<RoomControlsPlaceholder />
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
import { useSession } from '../context/SessionContext.jsx';
|
import { useSession } from '../context/SessionContext.jsx';
|
||||||
import { useVideoRequests } from '../hooks/useVideoRequests.js';
|
import { useVideoRequests } from '../hooks/useVideoRequests.js';
|
||||||
import RoomCameraFeed from './RoomCameraFeed.jsx';
|
import RoomCameraFeed from './RoomCameraFeed.jsx';
|
||||||
@@ -11,11 +12,29 @@ function EmptyState() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RoomCameraPanel() {
|
const ORIENTATIONS = ['horizontal', 'vertical'];
|
||||||
|
|
||||||
|
function normalizeOrientation(value, fallback) {
|
||||||
|
if (ORIENTATIONS.includes(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RoomCameraPanel({ defaultOrientation = 'horizontal', orientation: forcedOrientation }) {
|
||||||
const { session } = useSession();
|
const { session } = useSession();
|
||||||
const cameras = session?.roomCameras || [];
|
const cameras = session?.roomCameras || [];
|
||||||
const sourceDescriptors = cameras.map((camera) => ({ type: 'room', id: camera.id, key: `room:${camera.id}` }));
|
const sourceDescriptors = cameras.map((camera) => ({ type: 'room', id: camera.id, key: `room:${camera.id}` }));
|
||||||
const videoSources = useVideoRequests(sourceDescriptors);
|
const videoSources = useVideoRequests(sourceDescriptors);
|
||||||
|
const [orientation, setOrientation] = useState(() =>
|
||||||
|
normalizeOrientation(defaultOrientation, 'horizontal'),
|
||||||
|
);
|
||||||
|
const effectiveOrientation = forcedOrientation
|
||||||
|
? normalizeOrientation(forcedOrientation, 'horizontal')
|
||||||
|
: orientation;
|
||||||
|
const containerClass =
|
||||||
|
effectiveOrientation === 'vertical' ? 'flex flex-col gap-0.5' : 'grid gap-0.5 md:grid-cols-2';
|
||||||
|
const showLayoutToggle = !forcedOrientation && cameras.length > 0;
|
||||||
|
|
||||||
if (cameras.length === 0) {
|
if (cameras.length === 0) {
|
||||||
return <EmptyState />;
|
return <EmptyState />;
|
||||||
@@ -23,16 +42,35 @@ export default function RoomCameraPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="panel-section space-y-0.5 text-base">
|
<section className="panel-section space-y-0.5 text-base">
|
||||||
<header className="flex items-center justify-between text-sm text-slate-400">
|
<header className="flex flex-wrap items-center justify-between gap-0.5 text-sm text-slate-400">
|
||||||
<p>Room cameras</p>
|
<div className="flex items-center gap-1">
|
||||||
<span>{cameras.length}</span>
|
<p>Room cameras</p>
|
||||||
|
<span className="text-xs text-slate-500">{cameras.length}</span>
|
||||||
|
</div>
|
||||||
|
{showLayoutToggle && (
|
||||||
|
<div className="flex items-center gap-0.5 text-xs">
|
||||||
|
<span className="text-slate-500">Layout:</span>
|
||||||
|
<div className="inline-flex overflow-hidden rounded border border-slate-700">
|
||||||
|
{ORIENTATIONS.map((option) => (
|
||||||
|
<button
|
||||||
|
key={option}
|
||||||
|
type="button"
|
||||||
|
className={`px-1 py-0.5 ${effectiveOrientation === option ? 'bg-slate-600 text-white' : 'bg-transparent text-slate-400 hover:text-white'}`}
|
||||||
|
onClick={() => setOrientation(option)}
|
||||||
|
>
|
||||||
|
{option === 'vertical' ? 'Vertical' : 'Grid'}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
<div className="grid gap-0.5 sm:grid-cols-2">
|
<div className={containerClass}>
|
||||||
{cameras.map((camera) => {
|
{cameras.map((camera) => {
|
||||||
const key = `room:${camera.id}`;
|
const key = `room:${camera.id}`;
|
||||||
const sessionInfo = videoSources[key];
|
const sessionInfo = videoSources[key];
|
||||||
return (
|
return (
|
||||||
<article key={camera.id} className="space-y-0.5 rounded border border-slate-800 bg-zinc-950 p-0.5">
|
<article key={camera.id} className="w-full space-y-0.5 rounded border border-slate-800 bg-zinc-950 p-0.5">
|
||||||
<header className="space-y-0.5">
|
<header className="space-y-0.5">
|
||||||
<p className="text-lg font-semibold text-white">{camera.name || camera.id}</p>
|
<p className="text-lg font-semibold text-white">{camera.name || camera.id}</p>
|
||||||
{camera.description && <p className="text-xs text-slate-500">{camera.description}</p>}
|
{camera.description && <p className="text-xs text-slate-500">{camera.description}</p>}
|
||||||
|
|||||||
Reference in New Issue
Block a user