// "Run in this tab" (oxygenflow#80): each WASM job of the pipeline in a // worker of its own (wasi-worker.js), over the bookmark's files fetched once // as a zip. A job shows its output as it comes, its exit code and its time. "use strict"; (() => { const root = document.getElementById("tab-runner"); if (!root) return; const jobs = JSON.parse(root.dataset.jobs); const status = document.getElementById("tab-status"); const list = document.getElementById("tab-jobs"); let archive = null; const fetchArchive = () => { archive ??= fetch(root.dataset.archive, { credentials: "same-origin" }).then((r) => { if (!r.ok) throw new Error(`the files did not come (HTTP ${r.status})`); return r.arrayBuffer(); }); return archive; }; const el = (tag, props, ...kids) => { const e = Object.assign(document.createElement(tag), props); e.append(...kids); return e; }; const rows = jobs.map((job) => { const state = el("span", { className: "small mute", textContent: "not run" }); const out = el("pre", { className: "snippet", hidden: true }); const run = el("button", { type: "button", textContent: "Run" }); const stop = el("button", { type: "button", textContent: "Stop", hidden: true }); const box = el( "div", { className: "tab-job" }, el("h3", {}, job.name, " ", state), el("p", { className: "small mute" }, el("code", { textContent: job.exec.join(" ") })), el("p", {}, run, " ", stop), out, ); list.append(box); let worker = null; const line = (cls, text) => { out.hidden = false; out.append(el("span", { className: cls, textContent: text + "\n" })); out.scrollTop = out.scrollHeight; }; const go = () => new Promise((resolve) => { out.replaceChildren(); state.textContent = "fetching the files…"; state.className = "small mute"; run.disabled = true; stop.hidden = false; const finish = (text, cls, ok) => { state.textContent = text; state.className = "small " + cls; run.disabled = false; stop.hidden = true; worker?.terminate(); worker = null; resolve(ok); }; stop.onclick = () => finish("stopped", "bad", false); fetchArchive() .then((buf) => { state.textContent = "running…"; worker = new Worker(root.dataset.worker); worker.onmessage = ({ data: m }) => { if (m.kind === "done") finish(m.code === 0 ? `passed in ${m.ms} ms` : `failed, exit ${m.code}, in ${m.ms} ms`, m.code === 0 ? "ok" : "bad", m.code === 0); else if (m.kind === "failed") finish(`could not run: ${m.text}`, "bad", false); else line(m.kind === "out" ? "" : m.kind === "err" ? "bad" : "mute", m.text); }; worker.onerror = (e) => finish(`the worker stopped: ${e.message}`, "bad", false); worker.postMessage({ archive: buf.slice(0), exec: job.exec, env: job.env }); }) .catch((e) => finish(e.message, "bad", false)); }); run.onclick = () => go(); return go; }); document.getElementById("tab-run-all").onclick = async (e) => { e.target.disabled = true; let passed = 0; for (const go of rows) if (await go()) passed += 1; status.textContent = `${passed} of ${rows.length} passed`; e.target.disabled = false; }; })();