// World map (BlueMap) controls. // // Render runs out-of-process via BlueMap CLI. Status polled every 3 s while // the modal is open OR while a render is in progress. The "Open map" button // only opens the new tab if there's actually rendered output -- otherwise we // pop a friendly message saying "render first". "use strict"; import { api } from "./api.js"; const els = {}; let pollTimer = null; let modalOpen = false; let lastHasOutput = false; function setPolling(intervalMs) { if (pollTimer) clearInterval(pollTimer); pollTimer = setInterval(tick, intervalMs); } async function tick() { let s; try { s = await api("/api/map/status"); } catch { return; } lastHasOutput = !!s.hasOutput; document.getElementById("mapBadge").hidden = !s.inProgress; if (s.inProgress) document.getElementById("mapBadge").textContent = "rendering"; if (!modalOpen) return; // don't bother updating modal DOM if hidden els.phase.textContent = phaseLabel(s.phase); els.lastLog.textContent = s.lastLogLine ?? "--"; els.render.disabled = s.inProgress; els.render.textContent = s.inProgress ? "Rendering…" : "Render now"; els.cancel.hidden = !s.inProgress; if (s.phase === "complete" || s.phase === "failed" || s.phase === "cancelled") { if (s.phase === "failed" && s.error) showMsg("Failed: " + s.error); else if (s.phase === "cancelled") showMsg("Cancelled. Next render resumes from this point."); else if (s.phase === "complete") showMsg("Render complete.", true); } } function phaseLabel(phase) { switch (phase) { case "downloading": return "Downloading CLI"; case "extracting": return "Extracting CLI"; case "configuring": return "Configuring"; case "rendering": return "Rendering"; case "complete": return "Complete"; case "failed": return "Failed"; case "cancelled": return "Cancelled"; default: return "Idle"; } } function showMsg(text, ok = false) { els.msg.className = ok ? "acct-msg ok" : "acct-msg"; els.msg.textContent = text; } export function setupMap() { els.phase = document.getElementById("mapPhase"); els.lastLog = document.getElementById("mapLastLog"); els.render = document.getElementById("mapRender"); els.cancel = document.getElementById("mapCancel"); els.open = document.getElementById("mapOpen"); els.msg = document.getElementById("mapMsg"); if (!els.render) return; els.cancel.addEventListener("click", async () => { if (!confirm("Cancel the render? It's resumable -- next time you click Render, BlueMap continues from where it stopped.")) return; try { const res = await fetch("/api/map/cancel", { method: "POST" }); const body = await res.json().catch(() => ({})); if (!res.ok || body.ok === false) showMsg(body.error || `Error ${res.status}`); else { showMsg("Cancelling…"); tick(); } } catch (e) { showMsg(e.message); } }); // Track modal open/close so we can poll faster when the user is watching. const modal = document.getElementById("modalMap"); new MutationObserver(() => { modalOpen = !modal.hidden; if (modalOpen) tick(); }).observe(modal, { attributes: true, attributeFilter: ["hidden"] }); els.render.addEventListener("click", async () => { showMsg("Starting render…"); els.render.disabled = true; try { const res = await fetch("/api/map/render", { method: "POST" }); const body = await res.json().catch(() => ({})); if (!res.ok || body.ok === false) { showMsg(body.error || `Error ${res.status}`); els.render.disabled = false; return; } tick(); } catch (e) { showMsg(e.message); els.render.disabled = false; } }); els.open.addEventListener("click", () => { if (!lastHasOutput) { showMsg("No map output yet -- click Render now first."); return; } window.open("/map/", "_blank", "noopener"); }); tick(); setPolling(3000); // light poll keeps the badge fresh + catches background renders }