vertical room cameras

This commit is contained in:
legop3
2025-11-20 16:21:18 -05:00
parent d816345560
commit 220535e72a
5 changed files with 54 additions and 16 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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>
+44 -6
View File
@@ -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>}