progress tracker for playlists
This commit is contained in:
parent
04acbf896e
commit
a89cc14448
|
|
@ -14,6 +14,10 @@
|
||||||
M.currentUser.permissions = data.permissions;
|
M.currentUser.permissions = data.permissions;
|
||||||
}
|
}
|
||||||
M.updateAuthUI();
|
M.updateAuthUI();
|
||||||
|
// Start slow queue polling if logged in
|
||||||
|
if (M.currentUser && !M.currentUser.isGuest && M.startSlowQueuePoll) {
|
||||||
|
M.startSlowQueuePoll();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
M.currentUser = null;
|
M.currentUser = null;
|
||||||
M.updateAuthUI();
|
M.updateAuthUI();
|
||||||
|
|
@ -108,6 +112,8 @@
|
||||||
const wasGuest = M.currentUser?.isGuest;
|
const wasGuest = M.currentUser?.isGuest;
|
||||||
await fetch("/api/auth/logout", { method: "POST" });
|
await fetch("/api/auth/logout", { method: "POST" });
|
||||||
M.currentUser = null;
|
M.currentUser = null;
|
||||||
|
// Stop slow queue polling on logout
|
||||||
|
if (M.stopSlowQueuePoll) M.stopSlowQueuePoll();
|
||||||
if (wasGuest) {
|
if (wasGuest) {
|
||||||
// Guest clicking "Sign In" - show login panel
|
// Guest clicking "Sign In" - show login panel
|
||||||
M.updateAuthUI();
|
M.updateAuthUI();
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,16 @@ h3 { font-size: 0.8rem; color: #666; margin-bottom: 0.3rem; text-transform: uppe
|
||||||
.task-item .task-progress { font-size: 0.7rem; color: #888; flex-shrink: 0; }
|
.task-item .task-progress { font-size: 0.7rem; color: #888; flex-shrink: 0; }
|
||||||
.task-item .task-bar { position: absolute; left: 0; bottom: 0; height: 2px; background: #ea4; transition: width 0.2s; }
|
.task-item .task-bar { position: absolute; left: 0; bottom: 0; height: 2px; background: #ea4; transition: width 0.2s; }
|
||||||
.task-item.complete .task-bar { background: #4e8; }
|
.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-title { font-size: 0.75rem; color: #888; font-weight: 500; }
|
||||||
|
.slow-queue-timer { font-size: 0.7rem; color: #6af; }
|
||||||
|
.slow-queue-list { display: flex; flex-direction: column; gap: 0.15rem; max-height: 150px; overflow-y: auto; }
|
||||||
|
.slow-queue-item { display: flex; align-items: center; gap: 0.4rem; padding: 0.25rem 0.4rem; background: #1a1a2a; border-radius: 3px; font-size: 0.75rem; color: #6af; }
|
||||||
|
.slow-queue-item.next { background: #1a2a2a; color: #4cf; }
|
||||||
|
.slow-queue-item-icon { flex-shrink: 0; font-size: 0.7rem; }
|
||||||
|
.slow-queue-item-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.scan-progress { font-size: 0.7rem; color: #ea4; padding: 0.2rem 0.4rem; background: #2a2a1a; border-radius: 3px; margin-bottom: 0.3rem; display: flex; align-items: center; gap: 0.4rem; flex-shrink: 0; }
|
.scan-progress { font-size: 0.7rem; color: #ea4; padding: 0.2rem 0.4rem; background: #2a2a1a; border-radius: 3px; margin-bottom: 0.3rem; display: flex; align-items: center; gap: 0.4rem; flex-shrink: 0; }
|
||||||
.scan-progress.hidden { display: none; }
|
.scan-progress.hidden { display: none; }
|
||||||
.scan-progress.complete { color: #4e8; background: #1a2a1a; }
|
.scan-progress.complete { color: #4e8; background: #1a2a1a; }
|
||||||
|
|
|
||||||
103
public/upload.js
103
public/upload.js
|
|
@ -12,6 +12,10 @@
|
||||||
const tasksList = M.$("#tasks-list");
|
const tasksList = M.$("#tasks-list");
|
||||||
const tasksEmpty = M.$("#tasks-empty");
|
const tasksEmpty = M.$("#tasks-empty");
|
||||||
|
|
||||||
|
// Slow queue section elements (created dynamically)
|
||||||
|
let slowQueueSection = null;
|
||||||
|
let slowQueuePollInterval = null;
|
||||||
|
|
||||||
if (!addBtn || !fileInput || !dropzone) return;
|
if (!addBtn || !fileInput || !dropzone) return;
|
||||||
|
|
||||||
function openPanel() {
|
function openPanel() {
|
||||||
|
|
@ -200,10 +204,101 @@
|
||||||
const fetchTasks = new Map(); // Map<id, taskHandle>
|
const fetchTasks = new Map(); // Map<id, taskHandle>
|
||||||
|
|
||||||
function updateTasksEmpty() {
|
function updateTasksEmpty() {
|
||||||
const hasTasks = tasksList.children.length > 0;
|
const hasActiveTasks = tasksList.children.length > 0;
|
||||||
tasksEmpty.classList.toggle("hidden", hasTasks);
|
const hasSlowQueue = slowQueueSection && !slowQueueSection.classList.contains("hidden");
|
||||||
|
tasksEmpty.classList.toggle("hidden", hasActiveTasks || hasSlowQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Slow queue display
|
||||||
|
function createSlowQueueSection() {
|
||||||
|
if (slowQueueSection) return slowQueueSection;
|
||||||
|
|
||||||
|
slowQueueSection = document.createElement("div");
|
||||||
|
slowQueueSection.className = "slow-queue-section hidden";
|
||||||
|
slowQueueSection.innerHTML = `
|
||||||
|
<div class="slow-queue-header">
|
||||||
|
<span class="slow-queue-title">Playlist Queue</span>
|
||||||
|
<span class="slow-queue-timer"></span>
|
||||||
|
</div>
|
||||||
|
<div class="slow-queue-list"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Insert before tasks-empty
|
||||||
|
tasksEmpty.parentNode.insertBefore(slowQueueSection, tasksEmpty);
|
||||||
|
return slowQueueSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (seconds <= 0) return "now";
|
||||||
|
const mins = Math.floor(seconds / 60);
|
||||||
|
const secs = seconds % 60;
|
||||||
|
if (mins > 0) return `${mins}m ${secs}s`;
|
||||||
|
return `${secs}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSlowQueueDisplay(slowQueue, slowQueueNextIn) {
|
||||||
|
const section = createSlowQueueSection();
|
||||||
|
const queuedItems = slowQueue.filter(i => i.status === "queued");
|
||||||
|
|
||||||
|
if (queuedItems.length === 0) {
|
||||||
|
section.classList.add("hidden");
|
||||||
|
updateTasksEmpty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.classList.remove("hidden");
|
||||||
|
|
||||||
|
// Update header with count and timer
|
||||||
|
const timerEl = section.querySelector(".slow-queue-timer");
|
||||||
|
timerEl.textContent = `${queuedItems.length} queued · next in ${formatTime(slowQueueNextIn)}`;
|
||||||
|
|
||||||
|
// Update list
|
||||||
|
const listEl = section.querySelector(".slow-queue-list");
|
||||||
|
listEl.innerHTML = queuedItems.map((item, i) => `
|
||||||
|
<div class="slow-queue-item${i === 0 ? ' next' : ''}">
|
||||||
|
<span class="slow-queue-item-icon">${i === 0 ? '⏳' : '·'}</span>
|
||||||
|
<span class="slow-queue-item-title">${item.title}</span>
|
||||||
|
</div>
|
||||||
|
`).join("");
|
||||||
|
|
||||||
|
updateTasksEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollSlowQueue() {
|
||||||
|
if (!M.currentUser || M.currentUser.isGuest) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/fetch");
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
updateSlowQueueDisplay(data.slowQueue || [], data.slowQueueNextIn || 0);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore poll errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSlowQueuePoll() {
|
||||||
|
if (slowQueuePollInterval) return;
|
||||||
|
pollSlowQueue();
|
||||||
|
slowQueuePollInterval = setInterval(pollSlowQueue, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopSlowQueuePoll() {
|
||||||
|
if (slowQueuePollInterval) {
|
||||||
|
clearInterval(slowQueuePollInterval);
|
||||||
|
slowQueuePollInterval = null;
|
||||||
|
}
|
||||||
|
if (slowQueueSection) {
|
||||||
|
slowQueueSection.classList.add("hidden");
|
||||||
|
updateTasksEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose start/stop for auth module to call
|
||||||
|
M.startSlowQueuePoll = startSlowQueuePoll;
|
||||||
|
M.stopSlowQueuePoll = stopSlowQueuePoll;
|
||||||
|
|
||||||
// Handle WebSocket fetch progress messages
|
// Handle WebSocket fetch progress messages
|
||||||
M.handleFetchProgress = function(data) {
|
M.handleFetchProgress = function(data) {
|
||||||
let task = fetchTasks.get(data.id);
|
let task = fetchTasks.get(data.id);
|
||||||
|
|
@ -220,9 +315,13 @@
|
||||||
} else if (data.status === "complete") {
|
} else if (data.status === "complete") {
|
||||||
task.setComplete();
|
task.setComplete();
|
||||||
fetchTasks.delete(data.id);
|
fetchTasks.delete(data.id);
|
||||||
|
// Refresh slow queue on completion
|
||||||
|
pollSlowQueue();
|
||||||
} else if (data.status === "error") {
|
} else if (data.status === "error") {
|
||||||
task.setError(data.error || "Failed");
|
task.setError(data.error || "Failed");
|
||||||
fetchTasks.delete(data.id);
|
fetchTasks.delete(data.id);
|
||||||
|
// Refresh slow queue on error
|
||||||
|
pollSlowQueue();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue