feat: jump to a channel by name, and queue the whole channel
A video's channel name under the thumbnail is now a link — click it in either list or tile view and the feed narrows to that channel. It clears the search box on the way, so arriving at a channel shows the channel rather than whatever you had been searching for. On a channel page, an icon button to the right of Local queues every video listed there. The backend already runs two downloads at a time and parks the rest, so the whole channel goes into the queue at once and comes down in order. The button is only there when there is something left to fetch, and a video that fails reports it on its own row rather than raising a dialog per failure. Cancelling a queued download now actually cancels it. Before, a video waiting for a slot ignored the cancel and started anyway once its turn came — barely reachable with one-at-a-time downloading, unmissable when a whole channel is queued. A download re-reads its own state after claiming a slot and stands down if it is no longer wanted.
This commit is contained in:
+26
@@ -242,6 +242,27 @@ export default function App() {
|
||||
return () => clearInterval(id);
|
||||
}, [online, refreshing, playingIndex, doRefresh]);
|
||||
|
||||
// Everything listed that is not already here or on its way.
|
||||
const pendingDownloads = useMemo(
|
||||
() =>
|
||||
items.filter((i) => {
|
||||
const state = live[i.id]?.state ?? i.state;
|
||||
return state !== "done" && state !== "queued" && state !== "running";
|
||||
}),
|
||||
[items, live],
|
||||
);
|
||||
|
||||
// Queues the lot in one go. The backend runs two at a time and the rest wait
|
||||
// their turn, so this is a queue rather than a stampede; a video that fails
|
||||
// reports it on its own row instead of throwing a dialog for each one.
|
||||
const downloadAll = useCallback(() => {
|
||||
if (pendingDownloads.length === 0) return;
|
||||
say(`Queued ${pendingDownloads.length} video${pendingDownloads.length === 1 ? "" : "s"}`);
|
||||
for (const i of pendingDownloads) {
|
||||
downloadVideo(i.id, quality, subLangArg).catch(() => {});
|
||||
}
|
||||
}, [pendingDownloads, quality, subLangArg, say]);
|
||||
|
||||
// Ids on screen still lacking a length, newest first. Joined into a string
|
||||
// so the effect below only re-runs when the set actually changes.
|
||||
const missingDurations = useMemo(
|
||||
@@ -375,6 +396,10 @@ export default function App() {
|
||||
resultCount={items.length}
|
||||
view={view} onView={setView}
|
||||
onDeleteAll={totals.downloaded > 0 ? () => setConfirmWipe(true) : undefined}
|
||||
onDownloadAll={
|
||||
channelId && online && pendingDownloads.length > 0 ? downloadAll : undefined
|
||||
}
|
||||
downloadAllCount={pendingDownloads.length}
|
||||
sidebarHidden={sidebarHidden}
|
||||
onShowSidebar={() => { setSidebarHidden(false); setSidebarPeek(false); }}
|
||||
titleBarInset={titleBarInset}
|
||||
@@ -422,6 +447,7 @@ export default function App() {
|
||||
live: live[item.id],
|
||||
online,
|
||||
onOpen: () => openIndex(idx),
|
||||
onOpenChannel: () => { setChannelId(item.channel_id); setSearch(""); },
|
||||
onDownload: () =>
|
||||
downloadVideo(item.id, quality, subLangArg).catch((e) => setFailure(String(e))),
|
||||
onCancel: () =>
|
||||
|
||||
Reference in New Issue
Block a user