From 424873e7b0782cfbe9bbca2e64dedccd29a9ab55 Mon Sep 17 00:00:00 2001 From: peterino2 Date: Sat, 7 Feb 2026 00:14:05 -0800 Subject: [PATCH] saving --- public/styles.css | 6 ++++-- public/upload.js | 17 +++++++++++++++++ routes/fetch.ts | 12 ++++++++++++ routes/index.ts | 4 ++++ ytdlp.ts | 30 ++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) diff --git a/public/styles.css b/public/styles.css index 61b1c62..4422778 100644 --- a/public/styles.css +++ b/public/styles.css @@ -72,9 +72,11 @@ h3 { font-size: 0.8rem; color: #666; margin-bottom: 0.3rem; text-transform: uppe .task-item.complete .task-bar { background: #4e8; } .slow-queue-section { margin-top: 0.5rem; border-top: 1px solid #333; padding-top: 0.5rem; } .slow-queue-section.hidden { display: none; } -.slow-queue-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.3rem; padding: 0 0.2rem; } +.slow-queue-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.3rem; padding: 0 0.2rem; gap: 0.5rem; } .slow-queue-title { font-size: 0.75rem; color: #888; font-weight: 500; } -.slow-queue-timer { font-size: 0.7rem; color: #6af; } +.slow-queue-timer { font-size: 0.7rem; color: #6af; flex: 1; } +.slow-queue-cancel-all { background: none; border: 1px solid #633; color: #a66; font-size: 0.65rem; padding: 0.15rem 0.4rem; border-radius: 3px; cursor: pointer; transition: all 0.15s; } +.slow-queue-cancel-all:hover { background: #422; border-color: #844; color: #e88; } .slow-queue-list { display: flex; flex-direction: column; gap: 0.15rem; max-height: 200px; overflow-y: auto; } .slow-queue-playlist-header { font-size: 0.7rem; color: #888; padding: 0.3rem 0.2rem 0.15rem; margin-top: 0.2rem; border-top: 1px solid #2a2a2a; } .slow-queue-playlist-header:first-child { border-top: none; margin-top: 0; } diff --git a/public/upload.js b/public/upload.js index 45d4854..0743d7a 100644 --- a/public/upload.js +++ b/public/upload.js @@ -222,10 +222,27 @@
Playlist Queue +
`; + // Wire up cancel all button + slowQueueSection.querySelector(".slow-queue-cancel-all").onclick = async () => { + try { + const res = await fetch("/api/fetch", { method: "DELETE" }); + if (res.ok) { + const data = await res.json(); + M.showToast(data.message); + pollSlowQueue(); + } else { + M.showToast("Failed to cancel", "error"); + } + } catch (e) { + M.showToast("Failed to cancel", "error"); + } + }; + // Insert before tasks-empty tasksEmpty.parentNode.insertBefore(slowQueueSection, tasksEmpty); return slowQueueSection; diff --git a/routes/fetch.ts b/routes/fetch.ts index afd323e..f79b0bb 100644 --- a/routes/fetch.ts +++ b/routes/fetch.ts @@ -6,6 +6,7 @@ import { addToSlowQueue, getQueues, cancelSlowQueueItem, + cancelAllSlowQueueItems, } from "../ytdlp"; import { getOrCreateUser } from "./helpers"; import { createPlaylist, generateUniquePlaylistName } from "../db"; @@ -133,3 +134,14 @@ export function handleCancelFetchItem(req: Request, server: any, itemId: string) return Response.json({ error: "Cannot cancel item (not found, not owned, or already downloading)" }, { status: 400, headers }); } } + +// DELETE /api/fetch - cancel all slow queue items for user +export function handleCancelAllFetchItems(req: Request, server: any): Response { + const { user, headers } = getOrCreateUser(req, server); + if (!user) { + return Response.json({ error: "Authentication required" }, { status: 401 }); + } + + const cancelled = cancelAllSlowQueueItems(user.id); + return Response.json({ message: `Cancelled ${cancelled} items`, cancelled }, { headers }); +} diff --git a/routes/index.ts b/routes/index.ts index 08a0cf8..351e57d 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -41,6 +41,7 @@ import { handleFetchConfirm, handleGetFetchQueue, handleCancelFetchItem, + handleCancelAllFetchItems, } from "./fetch"; // Playlist routes @@ -150,6 +151,9 @@ export function createRouter() { if (path === "/api/fetch" && req.method === "GET") { return handleGetFetchQueue(req, server); } + if (path === "/api/fetch" && req.method === "DELETE") { + return handleCancelAllFetchItems(req, server); + } const fetchCancelMatch = path.match(/^\/api\/fetch\/([^/]+)$/); if (fetchCancelMatch && req.method === "DELETE") { return handleCancelFetchItem(req, server, fetchCancelMatch[1]); diff --git a/ytdlp.ts b/ytdlp.ts index c428556..836729c 100644 --- a/ytdlp.ts +++ b/ytdlp.ts @@ -518,6 +518,36 @@ export function cancelSlowQueueItem(id: string, userId: number): boolean { return true; } +// Cancel all queued items in slow queue for a user +export function cancelAllSlowQueueItems(userId: number): number { + const items = slowQueue.filter(i => i.userId === userId && i.status === "queued"); + let cancelled = 0; + + for (const item of items) { + item.status = "cancelled"; + item.completedAt = Date.now(); + + updateSlowQueueItem(item.id, { + status: "cancelled", + completedAt: Math.floor(item.completedAt / 1000) + }); + + notifyProgress(item); + cancelled++; + } + + // Remove all cancelled items after brief delay + setTimeout(() => { + for (let i = slowQueue.length - 1; i >= 0; i--) { + if (slowQueue[i].status === "cancelled") { + slowQueue.splice(i, 1); + } + } + }, 1000); + + return cancelled; +} + // Notify progress callback function notifyProgress(item: QueueItem): void { if (onProgress) {