From f9103455ed4f9d711ddeffc91321e95077e37ed9 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Tue, 9 Jun 2026 15:34:07 -0400 Subject: [PATCH] feat(StatusPage): add print log functionality and enhance UI elements --- src/app/components/MediaManager.tsx | 10 ++- src/app/components/StatusPage.tsx | 135 ++++++++++++++++++++++++++-- 2 files changed, 138 insertions(+), 7 deletions(-) diff --git a/src/app/components/MediaManager.tsx b/src/app/components/MediaManager.tsx index 5dc642e..7219866 100644 --- a/src/app/components/MediaManager.tsx +++ b/src/app/components/MediaManager.tsx @@ -19,6 +19,7 @@ import { File, FilePlus, FileText, + FileType, Folder, FolderPlus, HardDrive, @@ -55,6 +56,7 @@ import { deletePath, fileExists, getFileContents, + getWebDAVBaseUrl, humanFileSize, joinPath, listDirectory, @@ -77,11 +79,12 @@ import { type SortKey = 'name' | 'size' | 'date'; type Clipboard = { op: 'copy' | 'move'; paths: string[] }; -type ViewMode = 'text' | 'markdown' | 'json' | 'xml' | 'hex' | 'image' | 'config' | 'code'; +type ViewMode = 'text' | 'markdown' | 'json' | 'xml' | 'hex' | 'image' | 'config' | 'code' | 'doc'; // ─── Extension sets ────────────────────────────────────────────────────────── const TEXT_EXTS = new Set(['txt', 'cfg', 'ini', 'seq', 'log', 'csv', 'lst']); +const DOC_EXTS = new Set(['doc', 'docx', 'odt', 'rtf', 'pdf', 'pages', 'tex', 'xls', 'xlsx', 'ods', 'ppt', 'pptx', 'odp']); const CODE_EXTS = new Set(['asm', 'bas', 's', 'js', 'ts', 'jsx', 'tsx', 'css', 'scss', 'py', 'c', 'cpp', 'h', 'hpp', 'lua', 'sh', 'bash', 'php', 'rb', 'rs', 'go', 'java', 'cs', 'kt', 'sql', 'pl']); const MD_EXTS = new Set(['md', 'markdown']); const JSON_EXTS = new Set(['json', 'webmanifest']); @@ -104,6 +107,7 @@ function defaultViewMode(entry: EntryInfo): ViewMode { if (JSON_EXTS.has(ext)) return 'json'; if (XML_EXTS.has(ext)) return 'xml'; if (CODE_EXTS.has(ext)) return 'code'; + if (DOC_EXTS.has(ext)) return 'doc'; if (TEXT_EXTS.has(ext)) return 'text'; return 'hex'; } @@ -117,6 +121,7 @@ function availableViewers(entry: EntryInfo): ViewMode[] { json: ['json', 'text', 'hex'], xml: ['xml', 'text', 'hex'], code: ['code', 'text', 'hex'], + doc: ['doc'], text: ['text', 'hex'], hex: ['hex', 'text'], }; @@ -124,7 +129,7 @@ function availableViewers(entry: EntryInfo): ViewMode[] { } const VIEWER_LABEL: Record = { - code: 'Code', text: 'Text', markdown: 'Markdown', json: 'JSON', xml: 'HTML/XML', hex: 'Hex', image: 'Image', config: 'Config', + code: 'Code', text: 'Text', markdown: 'Markdown', json: 'JSON', xml: 'HTML/XML', hex: 'Hex', image: 'Image', config: 'Config', doc: 'Document', }; const EXT_TO_LANG: Record = { @@ -276,6 +281,7 @@ function EntryIcon({ entry }: { entry: EntryInfo }) { if (JSON_EXTS.has(ext)) return ; if (XML_EXTS.has(ext)) return ; if (MD_EXTS.has(ext)) return ; + if (DOC_EXTS.has(ext)) return ; if (TEXT_EXTS.has(ext)) return ; return ; } diff --git a/src/app/components/StatusPage.tsx b/src/app/components/StatusPage.tsx index 5d60a1c..523bd08 100644 --- a/src/app/components/StatusPage.tsx +++ b/src/app/components/StatusPage.tsx @@ -1,10 +1,12 @@ -import { useState } from 'react'; -import { HardDrive, Activity, Wifi, Radio, Clock, RefreshCw, Loader2 } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { HardDrive, Activity, Wifi, Radio, Clock, RefreshCw, Loader2, Printer, FileText, Power, Computer, MoreVertical, Download, Trash2, Eye, File as FileIcon } from 'lucide-react'; +import { listDirectory, deletePath, getFileContents, getWebDAVBaseUrl, humanFileSize, type EntryInfo } from '../webdav'; +import { toast } from 'sonner'; import { useWs } from '../ws'; import DeviceDetailOverlay from './DeviceDetailOverlay'; import MediaSet from './MediaSet'; import { ImageWithFallback } from './figma/ImageWithFallback'; -import { Dialog, DialogContent, DialogTitle, DialogDescription } from './ui/dialog'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog'; interface StatusPageProps { config: any; @@ -80,12 +82,58 @@ export default function StatusPage({ config, setConfig }: StatusPageProps) { const [showResetModal, setShowResetModal] = useState(null); const [resetStatus, setResetStatus] = useState('idle'); // 'idle' | 'in-progress' | 'done' + // Print Log + const [printFiles, setPrintFiles] = useState([]); + const [printLoading, setPrintLoading] = useState(true); + const [printActionEntry, setPrintActionEntry] = useState(null); + + const loadPrintFiles = () => { + setPrintLoading(true); + listDirectory('/sd/.print') + .then(entries => setPrintFiles(entries.filter(e => e.type === 'file'))) + .catch(() => setPrintFiles([])) + .finally(() => setPrintLoading(false)); + }; + + useEffect(() => { loadPrintFiles(); }, []); + + const printFileUrl = (entry: EntryInfo) => getWebDAVBaseUrl() + entry.path; + + const downloadPrintFile = async (entry: EntryInfo) => { + try { + const blob = await getFileContents(entry.path); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; a.download = entry.name; a.click(); + setTimeout(() => URL.revokeObjectURL(url), 1000); + } catch (e: any) { toast.error(`Download failed: ${e?.message ?? e}`); } + }; + + const deletePrintFile = async (entry: EntryInfo) => { + if (!window.confirm(`Delete "${entry.name}"?`)) return; + try { + await deletePath(entry.path); + toast.success(`Deleted ${entry.name}`); + loadPrintFiles(); + } catch (e: any) { toast.error(`Delete failed: ${e?.message ?? e}`); } + }; + + const printFileIcon = (entry: EntryInfo) => { + const ext = entry.name.split('.').pop()?.toLowerCase() ?? ''; + if (['txt', 'log', 'prn', 'lst'].includes(ext)) + return ; + return ; + }; + return (
{activeDevice && ( <> -

Active Device

+

+ + Active Device +

)} + +

+ + Print Log +

+ +
+ {printLoading ? ( +
+ Loading… +
+ ) : printFiles.length === 0 ? ( +
No print files found in /sd/.print
+ ) : ( + printFiles.map(entry => ( +
+ + +
+ )) + )} +
+ + {/* Print Log action modal */} + !open && setPrintActionEntry(null)}> + + + {printActionEntry?.name} + {humanFileSize(printActionEntry?.size ?? 0)} + + {printActionEntry && ( +
+ + +
+ +
+ )} + +
+

Activity Log @@ -272,7 +394,10 @@ export default function StatusPage({ config, setConfig }: StatusPageProps) { ))}

-

System Status

+

+ + System Status +

{/* System Status Action Buttons at bottom */}