From 52d0e96961479ed3df127c5015d3b5c20609d520 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Mon, 8 Jun 2026 01:57:03 -0400 Subject: [PATCH] feat(FileManager, DeviceDetailOverlay): enhance file handling with .lst support and improve device mounting logic --- src/app/components/DeviceDetailOverlay.tsx | 62 ++++++++++++++++------ src/app/components/FileManager.tsx | 37 +++++++++++-- 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/src/app/components/DeviceDetailOverlay.tsx b/src/app/components/DeviceDetailOverlay.tsx index 517504a..9e1430d 100644 --- a/src/app/components/DeviceDetailOverlay.tsx +++ b/src/app/components/DeviceDetailOverlay.tsx @@ -1,6 +1,8 @@ import { useEffect, 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'; +import { getFileContents, joinPath } from '../webdav'; import FileBrowser from './FileBrowser'; interface Device { @@ -36,7 +38,6 @@ export default function DeviceDetailOverlay({ const [touchEnd, setTouchEnd] = useState(0); const [showFileBrowser, setShowFileBrowser] = useState(false); const [showCommandMenu, setShowCommandMenu] = useState(false); - const [selectedMediaIndex, setSelectedMediaIndex] = useState(0); const minSwipeDistance = 50; @@ -144,13 +145,48 @@ export default function DeviceDetailOverlay({ }; }; - const mediaSet = detectMediaSet(); + // Prefer an explicit .lst-derived mediaSet stored in config; fall back to pattern detection. + const mediaSetFiles: string[] | null = (() => { + if (Array.isArray(deviceData.mediaSet) && deviceData.mediaSet.length > 0) { + return deviceData.mediaSet as string[]; + } + const detected = detectMediaSet(); + return detected ? detected.files : null; + })(); - const switchMedia = (index: number) => { - if (!mediaSet) return; + const switchMedia = (file: string) => { const path = getDevicePath(); - updateDeviceSetting([...path, 'url'], mediaSet.files[index]); - setSelectedMediaIndex(index); + updateDeviceSetting([...path, 'url'], file); + }; + + const handleFileSelect = async (selectedPath: string) => { + const devicePath = getDevicePath(); + if (selectedPath.toLowerCase().endsWith('.lst')) { + try { + const text = await (await getFileContents(selectedPath)).text(); + const dir = selectedPath.split('/').slice(0, -1).join('/') || '/'; + const files = text.split('\n') + .map(l => l.trim()) + .filter(l => l.length > 0 && !l.startsWith('#')) + .map(l => l.startsWith('/') ? l : joinPath(dir, l)); + if (files.length === 0) { toast.error('Swap list is empty'); return; } + const newConfig = JSON.parse(JSON.stringify(config)); + let dev = newConfig; + for (const k of devicePath) dev = dev[k]; + dev.url = files[0]; + dev.mediaSet = files; + setConfig(newConfig); + } catch (e: any) { + toast.error(`Failed to read swap list: ${e?.message ?? e}`); + } + } else { + const newConfig = JSON.parse(JSON.stringify(config)); + let dev = newConfig; + for (const k of devicePath) dev = dev[k]; + dev.url = selectedPath; + delete dev.mediaSet; + setConfig(newConfig); + } }; const sendCommand = (command: string) => { @@ -322,20 +358,17 @@ export default function DeviceDetailOverlay({ - {mediaSet && ( + {mediaSetFiles && (
- {mediaSet.files.slice(0, 5).map((file, index) => { - // Attempt to extract a title from the filename, fallback to filename - // Example: /path/to/Game Disk.d64 or /path/to/disk1.d64 + {mediaSetFiles.map((file: string, index: number) => { const fileName = file.split('/').pop() || file; - // If you have a title mapping, replace this logic const title = fileName.replace(/\.[^.]+$/, ''); return (