dev/playlists #13

Merged
peterino merged 19 commits from dev/playlists into integration 2026-02-09 18:58:41 +00:00
5 changed files with 67 additions and 2 deletions
Showing only changes of commit 424873e7b0 - Show all commits

View File

@ -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; }

View File

@ -222,10 +222,27 @@
<div class="slow-queue-header">
<span class="slow-queue-title">Playlist Queue</span>
<span class="slow-queue-timer"></span>
<button class="slow-queue-cancel-all" title="Cancel all">Cancel All</button>
</div>
<div class="slow-queue-list"></div>
`;
// 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;

View File

@ -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 });
}

View File

@ -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]);

View File

@ -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) {