From 9a7d93b9a76a857e71173f44dcce20890d7263f1 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Thu, 11 Jun 2026 22:16:16 -0400 Subject: [PATCH] feat(DeviceDetailOverlay): improve device navigation handling and media set validation --- src/app/components/DeviceDetailOverlay.tsx | 27 ++++++++++++++-------- src/app/components/MediaManager.tsx | 4 ++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/app/components/DeviceDetailOverlay.tsx b/src/app/components/DeviceDetailOverlay.tsx index b231a17..1cbec90 100644 --- a/src/app/components/DeviceDetailOverlay.tsx +++ b/src/app/components/DeviceDetailOverlay.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { X, ChevronLeft, ChevronRight, Printer, HardDrive, Network, Box, FolderOpen, MoreVertical, Play, Pause, SkipForward, SkipBack, RotateCcw } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { toast } from 'sonner'; @@ -70,10 +70,11 @@ export default function DeviceDetailOverlay({ const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose(); - } else if (e.key === 'ArrowLeft' && hasPrev) { - onNavigate('prev'); - } else if (e.key === 'ArrowRight' && hasNext) { - onNavigate('next'); + } else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { + const tag = (document.activeElement as HTMLElement)?.tagName; + if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return; + if (e.key === 'ArrowLeft' && hasPrev) onNavigate('prev'); + if (e.key === 'ArrowRight' && hasNext) onNavigate('next'); } }; @@ -123,13 +124,19 @@ export default function DeviceDetailOverlay({ // Prefer an explicit .lst-derived mediaSet stored in config; fall back to pattern detection. const [mediaSetFiles, setMediaSetFiles] = useState(null); + const prevDeviceNumberRef = useRef(device.number); useEffect(() => { - if (Array.isArray(deviceData.media_set) && deviceData.media_set.length > 0) { + const deviceChanged = prevDeviceNumberRef.current !== device.number; + prevDeviceNumberRef.current = device.number; + + if (Array.isArray(deviceData.media_set) && deviceData.media_set.length > 1) { setMediaSetFiles(deviceData.media_set as MediaSetEntry[]); return; } - if (!deviceData.url) { setMediaSetFiles(null); return; } + // Don't run pattern detection when switching devices — only when the URL + // is actively being changed within the same device. + if (deviceChanged || !deviceData.url) { setMediaSetFiles(null); return; } const match = (deviceData.url as string).match(/^(.+?)(\d+)(\.[^.]+)$/); if (!match) { setMediaSetFiles(null); return; } const [, prefix, , ext] = match; @@ -137,10 +144,11 @@ export default function DeviceDetailOverlay({ for (let i = 1; i <= 10; i++) candidates.push(`${prefix}${i}${ext}`); let cancelled = false; Promise.all(candidates.map(f => fileExists(f).catch(() => false))).then(flags => { - if (!cancelled) setMediaSetFiles(candidates.filter((_, i) => flags[i])); + const found = candidates.filter((_, i) => flags[i]); + if (!cancelled) setMediaSetFiles(found.length > 1 ? found : null); }); return () => { cancelled = true; }; - }, [deviceData.url, deviceData.media_set]); + }, [device.number, deviceData.url, deviceData.media_set]); const switchMedia = (file: string) => { const path = getDevicePath(); @@ -185,6 +193,7 @@ export default function DeviceDetailOverlay({ const existsArr = await Promise.all(candidates.map(e => fileExists(mediaSetEntryUrl(e)).catch(() => false))); const files = candidates.filter((_, i) => existsArr[i]); if (files.length === 0) { toast.error('No files in swap list exist on device'); return; } + if (files.length < 2) { toast.error('A media set needs more than one item'); return; } if (files.length < candidates.length) { toast.warning(`${candidates.length - files.length} missing file(s) skipped from swap list`); } diff --git a/src/app/components/MediaManager.tsx b/src/app/components/MediaManager.tsx index ea87f3c..51f1328 100644 --- a/src/app/components/MediaManager.tsx +++ b/src/app/components/MediaManager.tsx @@ -668,6 +668,10 @@ export default function MediaManager({ initialPath, rootPath, title, config, set toast.error(`${mountEntry.name}: no files in swap list exist on device`); return; } + if (files.length < 2) { + toast.error(`${mountEntry.name}: a media set needs more than one item`); + return; + } if (files.length < candidates.length) { toast.warning(`${candidates.length - files.length} missing file(s) skipped from swap list`); }