feat(DeviceDetailOverlay): improve device navigation handling and media set validation
This commit is contained in:
parent
d05eaaa84d
commit
9a7d93b9a7
|
|
@ -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 { X, ChevronLeft, ChevronRight, Printer, HardDrive, Network, Box, FolderOpen, MoreVertical, Play, Pause, SkipForward, SkipBack, RotateCcw } from 'lucide-react';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
@ -70,10 +70,11 @@ export default function DeviceDetailOverlay({
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
onClose();
|
onClose();
|
||||||
} else if (e.key === 'ArrowLeft' && hasPrev) {
|
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
||||||
onNavigate('prev');
|
const tag = (document.activeElement as HTMLElement)?.tagName;
|
||||||
} else if (e.key === 'ArrowRight' && hasNext) {
|
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
|
||||||
onNavigate('next');
|
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.
|
// Prefer an explicit .lst-derived mediaSet stored in config; fall back to pattern detection.
|
||||||
const [mediaSetFiles, setMediaSetFiles] = useState<MediaSetEntry[] | null>(null);
|
const [mediaSetFiles, setMediaSetFiles] = useState<MediaSetEntry[] | null>(null);
|
||||||
|
const prevDeviceNumberRef = useRef(device.number);
|
||||||
|
|
||||||
useEffect(() => {
|
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[]);
|
setMediaSetFiles(deviceData.media_set as MediaSetEntry[]);
|
||||||
return;
|
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+)(\.[^.]+)$/);
|
const match = (deviceData.url as string).match(/^(.+?)(\d+)(\.[^.]+)$/);
|
||||||
if (!match) { setMediaSetFiles(null); return; }
|
if (!match) { setMediaSetFiles(null); return; }
|
||||||
const [, prefix, , ext] = match;
|
const [, prefix, , ext] = match;
|
||||||
|
|
@ -137,10 +144,11 @@ export default function DeviceDetailOverlay({
|
||||||
for (let i = 1; i <= 10; i++) candidates.push(`${prefix}${i}${ext}`);
|
for (let i = 1; i <= 10; i++) candidates.push(`${prefix}${i}${ext}`);
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
Promise.all(candidates.map(f => fileExists(f).catch(() => false))).then(flags => {
|
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; };
|
return () => { cancelled = true; };
|
||||||
}, [deviceData.url, deviceData.media_set]);
|
}, [device.number, deviceData.url, deviceData.media_set]);
|
||||||
|
|
||||||
const switchMedia = (file: string) => {
|
const switchMedia = (file: string) => {
|
||||||
const path = getDevicePath();
|
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 existsArr = await Promise.all(candidates.map(e => fileExists(mediaSetEntryUrl(e)).catch(() => false)));
|
||||||
const files = candidates.filter((_, i) => existsArr[i]);
|
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 === 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) {
|
if (files.length < candidates.length) {
|
||||||
toast.warning(`${candidates.length - files.length} missing file(s) skipped from swap list`);
|
toast.warning(`${candidates.length - files.length} missing file(s) skipped from swap list`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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`);
|
toast.error(`${mountEntry.name}: no files in swap list exist on device`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (files.length < 2) {
|
||||||
|
toast.error(`${mountEntry.name}: a media set needs more than one item`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (files.length < candidates.length) {
|
if (files.length < candidates.length) {
|
||||||
toast.warning(`${candidates.length - files.length} missing file(s) skipped from swap list`);
|
toast.warning(`${candidates.length - files.length} missing file(s) skipped from swap list`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user