mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-22 17:16:19 -07:00
- Add backend selection, setup gating, Anki integration, and external host support - Add launcher flags, documentation, packaging, and focused tests - Open on-demand overlay modals on the first attempt
116 lines
4.2 KiB
JavaScript
116 lines
4.2 KiB
JavaScript
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
export function formatBytes(bytes) {
|
|
return bytes < 1_048_576
|
|
? `${Math.round(bytes / 1024)} KB`
|
|
: `${(bytes / 1_048_576).toFixed(1)} MB`;
|
|
}
|
|
|
|
export function formatSeconds(seconds) {
|
|
return seconds < 10 ? `${seconds.toFixed(1)} seconds` : `${Math.round(seconds)} seconds`;
|
|
}
|
|
|
|
export function installEntryState(entry) {
|
|
switch (entry.phase) {
|
|
case "downloading": {
|
|
const received = formatBytes(entry.receivedBytes);
|
|
if (entry.totalBytes === null) return { text: `Downloading… ${received}`, progress: { value: null } };
|
|
const fraction = Math.min(1, entry.receivedBytes / entry.totalBytes);
|
|
return { text: `Downloading… ${received} of ${formatBytes(entry.totalBytes)} (${Math.floor(fraction * 100)}%)`,
|
|
progress: { value: fraction } };
|
|
}
|
|
case "installing": return { text: "Installing…", progress: { value: null } };
|
|
case "installed": return { text: `Installed in ${formatSeconds(entry.seconds)}`, tone: "ok" };
|
|
case "already-installed": return { text: "Already installed", tone: "ok" };
|
|
case "failed": return { text: `Failed: ${entry.error}`, tone: "error" };
|
|
default: return { text: "Waiting" };
|
|
}
|
|
}
|
|
|
|
export function createDictionaryProgressList({
|
|
document,
|
|
ariaLabel,
|
|
idPrefix = "dictionary-progress",
|
|
}) {
|
|
const list = document.createElement("ul");
|
|
list.className = "setup-dictionary-list";
|
|
list.setAttribute("aria-label", ariaLabel);
|
|
list.hidden = true;
|
|
const rows = new Map();
|
|
|
|
function setEntries(entries) {
|
|
rows.clear();
|
|
list.replaceChildren();
|
|
entries.forEach((entry, index) => {
|
|
const key = String(entry.id);
|
|
const row = document.createElement("li");
|
|
row.className = "setup-dictionary";
|
|
row.dataset.sourceId = key;
|
|
|
|
const name = document.createElement("span");
|
|
name.className = "setup-dictionary-name";
|
|
name.id = entry.labelId ?? `${idPrefix}-${index}`;
|
|
name.textContent = entry.name;
|
|
|
|
const purpose = document.createElement("span");
|
|
purpose.className = "setup-dictionary-purpose";
|
|
purpose.textContent = entry.purpose ?? "";
|
|
purpose.hidden = purpose.textContent === "";
|
|
|
|
const status = document.createElement("span");
|
|
status.className = "setup-dictionary-status";
|
|
status.id = `${idPrefix}-status-${index}`;
|
|
status.textContent = entry.status ?? "Waiting";
|
|
|
|
const track = document.createElement("div");
|
|
track.className = "track setup-track";
|
|
track.hidden = true;
|
|
track.setAttribute("role", "progressbar");
|
|
track.setAttribute("aria-labelledby", name.id);
|
|
track.setAttribute("aria-describedby", status.id);
|
|
track.appendChild(document.createElement("div")).className = "track-fill";
|
|
|
|
row.append(name, purpose, status, track);
|
|
list.appendChild(row);
|
|
rows.set(key, { row, status, track });
|
|
});
|
|
list.hidden = entries.length === 0;
|
|
}
|
|
|
|
function update(id, state) {
|
|
const current = rows.get(String(id));
|
|
if (!current) return;
|
|
const { row, status, track } = current;
|
|
if (status.textContent !== state.text) status.textContent = state.text;
|
|
status.classList.toggle("is-ok", state.tone === "ok");
|
|
status.classList.toggle("is-error", state.tone === "error");
|
|
row.classList.toggle("is-active", Boolean(state.progress));
|
|
track.hidden = !state.progress;
|
|
if (!state.progress) return;
|
|
|
|
const value = state.progress.value;
|
|
const determinate = typeof value === "number";
|
|
track.classList.toggle("is-determinate", determinate);
|
|
if (determinate) {
|
|
const percent = Math.floor(Math.min(1, Math.max(0, value)) * 100);
|
|
track.removeAttribute("aria-valuetext");
|
|
track.setAttribute("aria-valuemin", "0");
|
|
track.setAttribute("aria-valuemax", "100");
|
|
track.setAttribute("aria-valuenow", String(percent));
|
|
track.style.setProperty("--progress", `${percent}%`);
|
|
} else {
|
|
track.removeAttribute("aria-valuenow");
|
|
track.removeAttribute("aria-valuemin");
|
|
track.removeAttribute("aria-valuemax");
|
|
track.style.removeProperty("--progress");
|
|
track.setAttribute("aria-valuetext", state.text);
|
|
}
|
|
}
|
|
|
|
function clear() {
|
|
setEntries([]);
|
|
}
|
|
|
|
return { element: list, setEntries, update, clear };
|
|
}
|