mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
113 lines
4.1 KiB
React
113 lines
4.1 KiB
React
// Room Camera Panel
|
|
// Purpose: Defines the Room Camera Panel module and the local helpers/components used in this file.
|
|
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
|
|
import { useEffect, useState } from 'react';
|
|
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
|
import { useSettingsNamespace } from '../../settings/index.js';
|
|
import { useRoomCameraSnapshots } from '../../hooks/useRoomCameraSnapshots.js';
|
|
import RoomCameraFeed from '../RoomCameraFeed/index.jsx';
|
|
import CardFrame from '../CardFrame/index.jsx';
|
|
|
|
function EmptyState() {
|
|
return (
|
|
<div className="panel-section space-y-0.5 text-sm">
|
|
<p className="text-center text-slate-400">No room cameras configured.</p>
|
|
<p className="text-center text-slate-500">Add entries to server config to populate this list.</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const ORIENTATIONS = ['horizontal', 'vertical'];
|
|
|
|
function normalizeOrientation(value, fallback) {
|
|
if (ORIENTATIONS.includes(value)) {
|
|
return value;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
export default function RoomCameraPanel({
|
|
defaultOrientation = 'horizontal',
|
|
orientation: forcedOrientation,
|
|
hideLayoutToggle = false,
|
|
hideHeader = false,
|
|
panelId = null,
|
|
}) {
|
|
const cameras = useSessionSelector((state) => state.session?.roomCameras || []);
|
|
const feedMap = useRoomCameraSnapshots(cameras.map((camera) => ({ id: camera.id })));
|
|
const { value: orientationSettings, save: saveOrientationSettings } = useSettingsNamespace('roomCameraPanels', {});
|
|
const [orientation, setOrientation] = useState(() =>
|
|
normalizeOrientation(
|
|
panelId ? orientationSettings?.[panelId] : defaultOrientation,
|
|
'horizontal',
|
|
),
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (!panelId) return;
|
|
const stored = orientationSettings?.[panelId];
|
|
if (!stored) return;
|
|
setOrientation(normalizeOrientation(stored, 'horizontal'));
|
|
// only respond to changes for this panel id
|
|
}, [panelId, orientationSettings?.[panelId]]);
|
|
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 = !hideLayoutToggle && !forcedOrientation && cameras.length > 0;
|
|
const applyOrientation = (next) => {
|
|
setOrientation(next);
|
|
if (panelId) {
|
|
saveOrientationSettings((current) => ({ ...(current || {}), [panelId]: next }));
|
|
}
|
|
};
|
|
|
|
if (cameras.length === 0) {
|
|
return <EmptyState />;
|
|
}
|
|
|
|
const actions = showLayoutToggle ? (
|
|
<div className="flex items-center gap-0.5 text-[0.68rem] text-slate-400">
|
|
<span>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={() => applyOrientation(option)}
|
|
>
|
|
{option === 'vertical' ? 'Vertical' : 'Grid'}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : null;
|
|
|
|
return (
|
|
<CardFrame
|
|
title="Room cameras"
|
|
accent="#38bdf8"
|
|
actions={actions}
|
|
hideHeader={hideHeader}
|
|
bodyClassName="space-y-0.5 text-base"
|
|
>
|
|
<div className={containerClass}>
|
|
{cameras.map((camera) => {
|
|
const feed = feedMap[camera.id] || null;
|
|
return (
|
|
<article key={camera.id} className="w-full space-y-0.5 rounded bg-zinc-950 p-0.5 shadow-inner shadow-black/40">
|
|
{/* <header className="space-y-0.5">
|
|
<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>}
|
|
</header> */}
|
|
<RoomCameraFeed feed={feed} label={camera.name || camera.id} />
|
|
</article>
|
|
);
|
|
})}
|
|
</div>
|
|
</CardFrame>
|
|
);
|
|
}
|