import { state, notify } from "../store.js"; import { t } from "../i18n.js"; import { iconMarkup } from "../icons.js"; import { formatSize, taskStateKind, taskStateDetail, progressPct } from "../format.js"; import * as ipc from "../ipc.js"; import { escapeHtml } from "../dom.js"; const STATE_LABEL_KEY = { queued: "queue.stateQueued", completed: "queue.stateComplete", failed: "queue.stateFailed", paused: "queue.statePaused", cancelled: "queue.stateCancelled", retrying: "queue.stateRetrying", }; function stateBadge(task) { const kind = taskStateKind(task.state); if (kind === "running") { if (task.speed) return { text: `${formatSize(task.speed)}/s`, cls: "text-green" }; const verb = task.kind === "upload" ? "queue.stateUploading" : "queue.stateDownloading"; return { text: t(verb), cls: "text-accent" }; } const cls = { completed: "text-green", failed: "text-red", paused: "text-yellow" }[kind] ?? "text-hint"; return { text: t(STATE_LABEL_KEY[kind] ?? "queue.stateQueued"), cls }; } function taskRow(task) { const row = document.createElement("div"); row.className = "queue-task"; const kind = taskStateKind(task.state); const pct = progressPct(task); const badge = stateBadge(task); const err = kind === "failed" ? taskStateDetail(task.state) : ""; row.innerHTML = ` ${iconMarkup(task.kind === "upload" ? "upload" : "download", 15)} ${escapeHtml(task.fileName)}
${pct.toFixed(0)}% ${badge.text}
`; const actions = row.querySelector('[data-role="actions"]'); if (kind === "running") { actions.appendChild(iconActionBtn("pauseCircle", () => ipc.pauseTask(task.id))); actions.appendChild(iconActionBtn("closeCircle", () => ipc.cancelTask(task.id))); } else if (kind === "paused" || kind === "queued") { actions.appendChild(iconActionBtn("playCircle", () => ipc.resumeTask(task.id))); actions.appendChild(iconActionBtn("closeCircle", () => ipc.cancelTask(task.id))); } else { actions.appendChild(iconActionBtn("closeCircle", () => ipc.removeTask(task.id))); } return row; } function iconActionBtn(icon, onClick) { const btn = document.createElement("button"); btn.type = "button"; btn.className = "btn icon-btn btn-ghost"; btn.style.width = "22px"; btn.style.height = "22px"; btn.innerHTML = iconMarkup(icon, 13); btn.addEventListener("click", onClick); return btn; } let lastSig = null; export function renderQueuePanel(container) { const tasks = state.queueTasks; const pending = tasks.filter((tk) => ["running", "queued"].includes(taskStateKind(tk.state))); const n = pending.length > 0 ? pending.length : tasks.length; const expanded = state.showQueue && tasks.length > 0; container.classList.toggle("expanded", expanded); const aggSpeed = tasks .filter((tk) => taskStateKind(tk.state) === "running") .reduce((sum, tk) => sum + (tk.speed ?? 0), 0); const done = tasks.filter((tk) => taskStateKind(tk.state) === "completed").length; const failed = tasks.filter((tk) => taskStateKind(tk.state) === "failed").length; let rightHtml = ""; if (aggSpeed > 0) { rightHtml = `${formatSize(aggSpeed)}/s`; } else if (tasks.length > 0) { rightHtml = `${t("queue.doneSuffix", { done, total: tasks.length })}`; if (failed > 0) rightHtml += `${t("queue.failedSuffix", { n: failed })}`; } container.innerHTML = `
${iconMarkup(state.showQueue ? "altArrowDown" : "altArrowUp", 13)} ${t("queue.title", { n })}
${rightHtml}
`; container.querySelector('[data-role="header"]').addEventListener("click", () => { state.showQueue = !state.showQueue; notify(); }); const list = container.querySelector('[data-role="list"]'); if (tasks.length === 0) { list.innerHTML = `
${t("queue.empty")}
`; } else { for (const task of tasks) list.appendChild(taskRow(task)); } lastSig = computeSig(); } function computeSig() { return JSON.stringify([ state.showQueue, state.queueTasks.map((tk) => [tk.id, tk.transferredBytes, tk.speed, JSON.stringify(tk.state)]), ]); } export function updateQueuePanelIfNeeded(container) { if (computeSig() === lastSig) return; renderQueuePanel(container); } const escapeAttr = escapeHtml;