lets see . . .

This commit is contained in:
legop3
2026-01-13 17:21:32 -05:00
parent 4ab6bad1b8
commit f3e13f6ab7
25 changed files with 841 additions and 175 deletions
+2 -84
View File
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { useSession } from '../context/SessionContext.jsx';
import { useSettingsNamespace } from '../settings/index.js';
import { useRoomCameraSnapshots } from '../hooks/useRoomCameraSnapshots.js';
@@ -29,21 +29,16 @@ export default function RoomCameraPanel({
hideHeader = false,
panelId = null,
}) {
const { session, triggerReplay } = useSession();
const { session } = useSession();
const cameras = session?.roomCameras || [];
const feedMap = useRoomCameraSnapshots(cameras.map((camera) => ({ id: camera.id })));
const { value: orientationSettings, save: saveOrientationSettings } = useSettingsNamespace('roomCameraPanels', {});
const { value: replaySettings, save: saveReplaySettings } = useSettingsNamespace('roomCameraReplay', {});
const [orientation, setOrientation] = useState(() =>
normalizeOrientation(
panelId ? orientationSettings?.[panelId] : defaultOrientation,
'horizontal',
),
);
const [selectedReplayCamera, setSelectedReplayCamera] = useState(() => {
if (!panelId) return 'all';
return replaySettings?.[panelId] || 'all';
});
useEffect(() => {
if (!panelId) return;
@@ -52,62 +47,12 @@ export default function RoomCameraPanel({
setOrientation(normalizeOrientation(stored, 'horizontal'));
// only respond to changes for this panel id
}, [panelId, orientationSettings?.[panelId]]);
useEffect(() => {
if (!panelId) return;
const stored = replaySettings?.[panelId];
if (!stored) return;
setSelectedReplayCamera(stored);
}, [panelId, replaySettings?.[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 [replayBusy, setReplayBusy] = useState(false);
const [replayError, setReplayError] = useState(null);
const replayState = session?.replay || null;
const replayRemainingMs = useMemo(() => {
if (!replayState?.lastTriggeredAt || !replayState?.cooldownMs) return 0;
const remaining = replayState.lastTriggeredAt + replayState.cooldownMs - Date.now();
return Math.max(0, remaining);
}, [replayState?.lastTriggeredAt, replayState?.cooldownMs, replayState?.remainingMs]);
const [remainingMs, setRemainingMs] = useState(replayRemainingMs);
const replayDisabled =
replayBusy || session?.mode === 'lockdown' || !cameras.length || remainingMs > 0;
useEffect(() => {
setRemainingMs(replayRemainingMs);
if (!replayState?.lastTriggeredAt || !replayState?.cooldownMs) {
return undefined;
}
const interval = setInterval(() => {
const next = replayState.lastTriggeredAt + replayState.cooldownMs - Date.now();
setRemainingMs(Math.max(0, next));
}, 250);
return () => clearInterval(interval);
}, [replayState?.lastTriggeredAt, replayState?.cooldownMs, replayRemainingMs]);
const handleReplay = async () => {
if (replayDisabled) return;
setReplayError(null);
setReplayBusy(true);
try {
const cameraId = selectedReplayCamera === 'all' ? null : selectedReplayCamera;
await triggerReplay(cameraId);
} catch (err) {
setReplayError(err.message);
} finally {
setReplayBusy(false);
}
};
const handleReplayCameraChange = (event) => {
const value = event.target.value;
setSelectedReplayCamera(value);
if (panelId) {
saveReplaySettings((current) => ({ ...(current || {}), [panelId]: value }));
}
};
const applyOrientation = (next) => {
setOrientation(next);
if (panelId) {
@@ -128,33 +73,6 @@ export default function RoomCameraPanel({
<span className="text-xs text-slate-500">{cameras.length}</span>
</div>
<div className="flex flex-wrap items-center gap-1 text-xs">
<select
className="rounded border border-slate-700 bg-black/40 px-1 py-0.5 text-slate-200"
value={selectedReplayCamera}
onChange={handleReplayCameraChange}
aria-label="Replay camera"
>
<option value="all">All cameras</option>
{cameras.map((camera) => (
<option key={camera.id} value={camera.id}>
{camera.name || camera.id}
</option>
))}
</select>
<button
type="button"
className={`rounded border px-1.5 py-0.5 ${
replayDisabled
? 'border-slate-700 text-slate-500'
: 'border-slate-500 text-slate-200 hover:border-slate-300 hover:text-white'
}`}
onClick={handleReplay}
disabled={replayDisabled}
title={session?.mode === 'lockdown' ? 'Replay disabled in lockdown' : 'Send replay to Discord'}
>
{remainingMs > 0 ? `Replay (${Math.ceil(remainingMs / 1000)}s)` : replayBusy ? 'Replay…' : 'Replay'}
</button>
{replayError && <span className="text-amber-400">{replayError}</span>}
{showLayoutToggle && (
<div className="flex items-center gap-0.5">
<span className="text-slate-500">Layout:</span>