feat(StatusPage): integrate DirectorySlideshow component for displaying images from active directory

This commit is contained in:
Jaime Idolpx 2026-06-11 12:34:44 -04:00
parent 010cd68e3f
commit 290cdb8ae9
2 changed files with 66 additions and 2 deletions

View File

@ -0,0 +1,59 @@
import { useEffect, useState } from 'react';
import { listDirectory, getWebDAVBaseUrl, type EntryInfo } from '../webdav';
import { IMAGE_EXTS } from './MediaEntry';
interface Props {
path: string;
}
export default function DirectorySlideshow({ path }: Props) {
const [images, setImages] = useState<EntryInfo[]>([]);
const [idx, setIdx] = useState(0);
useEffect(() => {
if (!path) { setImages([]); return; }
listDirectory(path)
.then(entries => {
const imgs = entries.filter(e => {
if (e.type !== 'file') return false;
const ext = e.name.split('.').pop()?.toLowerCase() ?? '';
return IMAGE_EXTS.has(ext);
});
setImages(imgs);
setIdx(0);
})
.catch(() => setImages([]));
}, [path]);
useEffect(() => {
if (images.length <= 1) return;
const t = setInterval(() => setIdx(i => (i + 1) % images.length), 4000);
return () => clearInterval(t);
}, [images.length]);
if (images.length === 0) return null;
const current = images[idx];
return (
<div className="mb-3 rounded-lg overflow-hidden">
<img
key={current.path}
src={getWebDAVBaseUrl() + current.path}
alt={current.name}
className="w-full h-48 object-contain"
/>
{images.length > 1 && (
<div className="flex justify-center gap-1.5 py-1.5">
{images.map((_, i) => (
<button
key={i}
onClick={() => setIdx(i)}
className={`w-1.5 h-1.5 rounded-full transition-colors ${i === idx ? 'bg-white' : 'bg-white/40'}`}
/>
))}
</div>
)}
</div>
);
}

View File

@ -3,6 +3,7 @@ import { HardDrive, Activity, Wifi, Radio, Clock, RefreshCw, Loader2, Printer, P
import { listDirectory, deletePath, getFileContents, getWebDAVBaseUrl, humanFileSize, splitPath, type EntryInfo } from '../webdav'; import { listDirectory, deletePath, getFileContents, getWebDAVBaseUrl, humanFileSize, splitPath, type EntryInfo } from '../webdav';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { MediaEntry } from './MediaEntry'; import { MediaEntry } from './MediaEntry';
import DirectorySlideshow from './DirectorySlideshow';
import { useWs } from '../ws'; import { useWs } from '../ws';
import DeviceDetailOverlay from './DeviceDetailOverlay'; import DeviceDetailOverlay from './DeviceDetailOverlay';
import MediaSet from './MediaSet'; import MediaSet from './MediaSet';
@ -25,7 +26,6 @@ export default function StatusPage({ config, setConfig, onOpenFileManager }: Sta
psram: { total: 8192, free: 4096 }, psram: { total: 8192, free: 4096 },
}; };
// Overlay state for active device
const [showDeviceOverlay, setShowDeviceOverlay] = useState(false); const [showDeviceOverlay, setShowDeviceOverlay] = useState(false);
// Find the first enabled device as the active device // Find the first enabled device as the active device
const findActiveDevice = () => { const findActiveDevice = () => {
@ -41,6 +41,9 @@ export default function StatusPage({ config, setConfig, onOpenFileManager }: Sta
}; };
const activeDevice = findActiveDevice(); const activeDevice = findActiveDevice();
const activeDir = activeDevice
? (activeDevice.base_url || splitPath(activeDevice.url || '/').parent)
: null;
const mediaSetFiles: string[] | null = (() => { const mediaSetFiles: string[] | null = (() => {
if (!activeDevice?.url) return null; if (!activeDevice?.url) return null;
@ -172,6 +175,8 @@ export default function StatusPage({ config, setConfig, onOpenFileManager }: Sta
</div> </div>
</div> </div>
{activeDir && <DirectorySlideshow path={activeDir} />}
{mediaSetFiles && ( {mediaSetFiles && (
<div className="mt-2"> <div className="mt-2">
<MediaSet files={mediaSetFiles} activeUrl={activeDevice.url ?? ''} onSwitch={switchActiveMedia} /> <MediaSet files={mediaSetFiles} activeUrl={activeDevice.url ?? ''} onSwitch={switchActiveMedia} />