Initial DDC release
This commit is contained in:
commit
434f6441da
57 files changed
+4606
No files matched your search
Executable
+95
@@ -0,0 +1,95 @@
|
||||
import { button, element } from "./dom.js";
|
||||
import { formatBytes, percentOf, statusLabels } from "./format.js";
|
||||
|
||||
const activeStatuses = new Set(["queued", "downloading", "extracting", "moving"]);
|
||||
const speedSamples = new Map();
|
||||
|
||||
export function isActive(job) {
|
||||
return activeStatuses.has(job.status);
|
||||
}
|
||||
|
||||
export function updateSpeeds(jobs) {
|
||||
const now = performance.now();
|
||||
const seenIds = new Set();
|
||||
|
||||
for (const job of jobs) {
|
||||
seenIds.add(job.id);
|
||||
const previous = speedSamples.get(job.id);
|
||||
let speed = 0;
|
||||
if (previous && job.bytesDone >= previous.bytes && now > previous.time) {
|
||||
speed = ((job.bytesDone - previous.bytes) * 1000) / (now - previous.time);
|
||||
}
|
||||
speedSamples.set(job.id, { bytes: job.bytesDone, time: now, speed });
|
||||
}
|
||||
|
||||
for (const id of speedSamples.keys()) {
|
||||
if (!seenIds.has(id)) speedSamples.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderJobs(container, jobs, selectedId, handlers) {
|
||||
container.replaceChildren(...jobs.map((job) => renderJob(job, job.id === selectedId, handlers)));
|
||||
}
|
||||
|
||||
function renderJob(job, selected, handlers) {
|
||||
const item = element("li", selected ? "job selected" : "job");
|
||||
|
||||
const header = element("div", "job-header");
|
||||
header.append(
|
||||
element("span", "job-name", job.fileName || job.sourceUrl),
|
||||
element("span", `badge badge-${job.status}`, statusLabels[job.status] || job.status),
|
||||
);
|
||||
item.append(header);
|
||||
|
||||
if (job.sourceUrl) item.append(element("p", "job-source muted", job.sourceUrl));
|
||||
if (job.status !== "completed") item.append(renderProgress(job));
|
||||
if (job.message) {
|
||||
const messageClass = job.status === "failed" ? "job-message error" : "job-message muted";
|
||||
item.append(element("p", messageClass, job.message));
|
||||
}
|
||||
item.append(renderActions(job, handlers));
|
||||
return item;
|
||||
}
|
||||
|
||||
function renderProgress(job) {
|
||||
const wrapper = element("div");
|
||||
const track = element("div", "progress");
|
||||
const bar = element("div", "progress-bar");
|
||||
const percent = percentOf(job.bytesDone, job.bytesTotal);
|
||||
|
||||
if (percent === null && isActive(job)) {
|
||||
bar.classList.add("indeterminate");
|
||||
} else {
|
||||
const width = percent === null ? (job.status === "ready" ? 100 : 0) : percent;
|
||||
bar.style.width = `${width}%`;
|
||||
}
|
||||
|
||||
track.append(bar);
|
||||
wrapper.append(track, element("div", "progress-text muted", progressText(job, percent)));
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
function progressText(job, percent) {
|
||||
const parts = [];
|
||||
if (job.bytesTotal > 0) {
|
||||
parts.push(`${formatBytes(job.bytesDone)} of ${formatBytes(job.bytesTotal)}`);
|
||||
} else if (job.bytesDone > 0) {
|
||||
parts.push(formatBytes(job.bytesDone));
|
||||
}
|
||||
if (percent !== null) parts.push(`${percent.toFixed(1)}%`);
|
||||
|
||||
const sample = speedSamples.get(job.id);
|
||||
if (isActive(job) && sample && sample.speed > 0) parts.push(`${formatBytes(sample.speed)}/s`);
|
||||
return parts.join(" | ");
|
||||
}
|
||||
|
||||
function renderActions(job, handlers) {
|
||||
const actions = element("div", "job-actions");
|
||||
if (job.status === "ready") {
|
||||
actions.append(button("Browse files", "secondary small", () => handlers.onBrowse(job)));
|
||||
}
|
||||
if (job.status !== "moving") {
|
||||
actions.append(button(isActive(job) ? "Cancel" : "Remove", "danger small", () => handlers.onDelete(job)));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
Reference in new issue
Block a user