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)} + /> + ))}
- ))} + )}
)}