/** Live-UI: Verbindungsbanner, Poller, gemeinsame Zustands-Labels. */ const RUN_STATES = { completed: "fertig", cancelled: "abgebrochen", error: "Fehler", in_progress: "druckt", printing: "druckt", paused: "pausiert", interrupted: "unterbrochen", standby: "bereit", heating: "heizt", complete: "fertig", offline: "offline", waiting: "wartet", running: "läuft", }; let connBannerOk = true; let connBannerTimer = 0; function setConnectionBanner(ok, text) { let el = document.getElementById("conn-banner"); if (!el) { el = document.createElement("div"); el.id = "conn-banner"; el.className = "conn-banner"; el.setAttribute("role", "status"); el.hidden = true; document.body.prepend(el); } clearTimeout(connBannerTimer); if (ok) { const wasBad = !connBannerOk; connBannerOk = true; if (wasBad && text) { el.textContent = text; el.className = "conn-banner ok"; el.hidden = false; connBannerTimer = setTimeout(() => { el.hidden = true; el.textContent = ""; }, 2800); return; } el.hidden = true; el.textContent = ""; el.className = "conn-banner"; return; } connBannerOk = false; el.textContent = text || "Keine Verbindung zum Server."; el.className = "conn-banner bad"; el.hidden = false; } /** * @param {() => void | Promise} fn * @param {number} intervalMs * @returns {{ start: () => void, stop: () => void }} */ function createPoller(fn, intervalMs) { const base = Math.max(1000, Number(intervalMs) || 5000); const maxMs = 60000; let timer = 0; let busy = false; let delay = base; let fails = 0; let running = false; function schedule() { if (!running) return; clearTimeout(timer); timer = setTimeout(tick, delay); } async function tick() { if (!running || document.hidden || busy) { schedule(); return; } busy = true; try { await fn(); delay = base; fails = 0; if (!connBannerOk) setConnectionBanner(true, "Wieder verbunden."); else setConnectionBanner(true, ""); } catch (error) { fails += 1; delay = Math.min(maxMs, Math.max(base, delay * 2)); if (fails >= 2) { setConnectionBanner(false, (error && error.message) || "Daten konnten nicht geladen werden."); } } finally { busy = false; schedule(); } } function onVis() { if (!document.hidden && running) tick(); } return { start() { if (running) return; running = true; document.addEventListener("visibilitychange", onVis); tick(); }, stop() { running = false; document.removeEventListener("visibilitychange", onVis); clearTimeout(timer); timer = 0; }, }; } const VERDICT_REASON_LABELS = { haftung: "Haftung", warping: "Warping", spaghetti: "Spaghetti", layer_shift: "Layer-Shift", verstopfung: "Verstopfung", farbe: "Farbe", sonstiges: "Sonstiges", }; /** * Dialog vor „Platte geräumt“: Urteil, optional Grund und Stückzahlen. * @returns {Promise} Payload für /abraeumen oder null bei Abbruch */ function promptAbraeumen(hostLabel) { return new Promise((resolve) => { const existing = document.getElementById("abraeumen-dlg"); if (existing) existing.remove(); const dlg = document.createElement("dialog"); dlg.id = "abraeumen-dlg"; dlg.className = "abraeumen-dlg"; const reasons = Object.entries(VERDICT_REASON_LABELS) .map(([k, label]) => ``) .join(""); dlg.innerHTML = `

Platte geräumt

${escapeHtml(hostLabel || "Drucker")}

`; document.body.appendChild(dlg); const verdict = dlg.querySelector("#ab-verdict"); const reasonWrap = dlg.querySelector("#ab-reason-wrap"); const pieces = dlg.querySelector("#ab-pieces"); function sync() { const scrap = verdict.value === "ausschuss"; reasonWrap.hidden = !scrap; pieces.hidden = !scrap; } verdict.addEventListener("change", sync); sync(); dlg.querySelector("#ab-cancel").addEventListener("click", () => { dlg.close(); dlg.remove(); resolve(null); }); dlg.addEventListener("close", () => { if (dlg.returnValue !== "ok") { dlg.remove(); resolve(null); return; } const payload = { confirm: true, verdict: verdict.value, verdict_reason: verdict.value === "ausschuss" ? dlg.querySelector("#ab-reason").value : "", pieces_ok: Number(dlg.querySelector("#ab-ok").value) || 0, pieces_bad: Number(dlg.querySelector("#ab-bad").value) || 0, }; dlg.remove(); resolve(payload); }); dlg.showModal(); }); } window.RUN_STATES = RUN_STATES; window.setConnectionBanner = setConnectionBanner; window.createPoller = createPoller; window.promptAbraeumen = promptAbraeumen; window.VERDICT_REASON_LABELS = VERDICT_REASON_LABELS;