diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/VideoDownloadPanel.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/VideoDownloadPanel.tsx
index b91661e9..45d913d2 100644
--- a/src/workspaces/officerdev/src/apps/FileBrowser/VideoDownloadPanel.tsx
+++ b/src/workspaces/officerdev/src/apps/FileBrowser/VideoDownloadPanel.tsx
@@ -36,6 +36,116 @@ const fmtDuration = (sec?: number): string => {
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
const sanitizeFolder = (name: string) => name.replace(/[/\\]/g, '').replace(/^\.+/, '').trim();
+// The thumbnail contents for a card (loading skeleton spinner / error / cover art / audio icon).
+const Thumb = ({ e, audioOnly, size }: { e: Entry; audioOnly: boolean; size: number }) => {
+ if (e.status === 'loading') return ;
+ if (e.status === 'error') return ;
+ if (e.thumbnail && !audioOnly) return
;
+ return ;
+};
+
+// Download control, in two flavors: `overlay` (a chip pinned in a grid thumbnail) or full-width (single).
+const CardAction = ({ e, onDownload, overlay }: { e: Entry; onDownload: () => void; overlay?: boolean }) => {
+ if (e.dl === 'done')
+ return overlay ? (
+
+ Saved
+
+ ) : (
+
+ Saved
+
+ );
+ if (e.dl === 'downloading' || e.dl === 'saving') {
+ const label = e.dl === 'saving' ? 'Saving…' : 'Downloading…';
+ return overlay ? (
+
+ {label}
+
+ ) : (
+
+ {label}
+
+ );
+ }
+ const label = e.dl === 'error' ? 'Retry' : 'Download';
+ return overlay ? (
+
+ ) : (
+
+ );
+};
+
+const sub = (e: Entry) => [e.uploader, fmtDuration(e.duration)].filter(Boolean).join(' · ');
+
+// Compact grid cell (playlist), with a position number badge.
+const GridCard = ({
+ e,
+ number,
+ audioOnly,
+ onDownload,
+}: {
+ e: Entry;
+ number: number;
+ audioOnly: boolean;
+ onDownload: () => void;
+}) => (
+
+
+
+
+ {number}
+
+ {e.status === 'ready' && }
+
+ {e.status !== 'loading' && (
+ <>
+
+ {e.status === 'error' ? 'Could not fetch' : e.title || e.url}
+
+
{e.status === 'error' ? e.error || '' : sub(e)}
+ >
+ )}
+
+);
+
+// Large single-item card.
+const SingleCard = ({ e, audioOnly, onDownload }: { e: Entry; audioOnly: boolean; onDownload: () => void }) => (
+
+
+
+
+ {e.status === 'error' ? (
+
+
Could not fetch
+
{e.error || e.url}
+
+ ) : e.status === 'ready' ? (
+ <>
+
+
+ {e.title || e.url}
+
+ {sub(e) &&
{sub(e)}
}
+
+
+ >
+ ) : null}
+
+);
+
export const VideoDownloadPanelHeader = () => (
<>
@@ -145,9 +255,6 @@ export const VideoDownloadPanel = () => {
setDownloadingAll(false);
};
- const dlLabel = (e: Entry) =>
- e.dl === 'downloading' ? 'Downloading…' : e.dl === 'saving' ? 'Saving…' : e.dl === 'done' ? 'Saved' : '';
-
return (
@@ -219,72 +326,26 @@ export const VideoDownloadPanel = () => {
)}
-
- {entries.map((e, i) => (
-
-
- {e.status === 'loading' ? (
-
- ) : e.status === 'error' ? (
-
- ) : e.thumbnail && !audioOnly ? (
-

- ) : (
-
- )}
-
-
-
- {e.status === 'loading' ? (
-
- ) : e.status === 'error' ? (
- <>
-
Could not fetch
-
{e.error || e.url}
- >
- ) : (
- <>
-
- {e.title || e.url}
-
-
- {[e.uploader, fmtDuration(e.duration)].filter(Boolean).join(' · ')}
-
- {e.dl === 'error' &&
{e.dlError}
}
- >
- )}
-
-
- {e.status === 'ready' && (
-
- {e.dl === 'done' ? (
-
- Saved
-
- ) : e.dl === 'downloading' || e.dl === 'saving' ? (
-
- {dlLabel(e)}
-
- ) : (
-
- )}
-
- )}
+
+ {entries.length === 1 ? (
+
void downloadEntry(0, entries[0]!.url)}
+ />
+ ) : (
+
+ {entries.map((e, i) => (
+ void downloadEntry(i, e.url)}
+ />
+ ))}
- ))}
+ )}
)}