dev/playlists #13
|
|
@ -14,6 +14,10 @@
|
|||
M.currentUser.permissions = data.permissions;
|
||||
}
|
||||
M.updateAuthUI();
|
||||
// Start slow queue polling if logged in
|
||||
if (M.currentUser && !M.currentUser.isGuest && M.startSlowQueuePoll) {
|
||||
M.startSlowQueuePoll();
|
||||
}
|
||||
} catch (e) {
|
||||
M.currentUser = null;
|
||||
M.updateAuthUI();
|
||||
|
|
@ -108,6 +112,8 @@
|
|||
const wasGuest = M.currentUser?.isGuest;
|
||||
await fetch("/api/auth/logout", { method: "POST" });
|
||||
M.currentUser = null;
|
||||
// Stop slow queue polling on logout
|
||||
if (M.stopSlowQueuePoll) M.stopSlowQueuePoll();
|
||||
if (wasGuest) {
|
||||
// Guest clicking "Sign In" - show login panel
|
||||
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-bar { position: absolute; left: 0; bottom: 0; height: 2px; background: #ea4; transition: width 0.2s; }
|
||||
.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.hidden { display: none; }
|
||||
.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 tasksEmpty = M.$("#tasks-empty");
|
||||
|
||||
// Slow queue section elements (created dynamically)
|
||||
let slowQueueSection = null;
|
||||
let slowQueuePollInterval = null;
|
||||
|
||||
if (!addBtn || !fileInput || !dropzone) return;
|
||||
|
||||
function openPanel() {
|
||||
|
|
@ -200,10 +204,101 @@
|
|||
const fetchTasks = new Map(); // Map<id, taskHandle>
|
||||
|
||||
function updateTasksEmpty() {
|
||||
const hasTasks = tasksList.children.length > 0;
|
||||
tasksEmpty.classList.toggle("hidden", hasTasks);
|
||||
const hasActiveTasks = tasksList.children.length > 0;
|
||||
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
|
||||
M.handleFetchProgress = function(data) {
|
||||
let task = fetchTasks.get(data.id);
|
||||
|
|
@ -220,9 +315,13 @@
|
|||
} else if (data.status === "complete") {
|
||||
task.setComplete();
|
||||
fetchTasks.delete(data.id);
|
||||
// Refresh slow queue on completion
|
||||
pollSlowQueue();
|
||||
} else if (data.status === "error") {
|
||||
task.setError(data.error || "Failed");
|
||||
fetchTasks.delete(data.id);
|
||||
// Refresh slow queue on error
|
||||
pollSlowQueue();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue