// Modpack update controls. // // The card hides itself when there's no update available and reveals when the // manifest reports a newer pack version. Polls /api/update/status (every 5 s // when idle, every 1 s when an update is in-flight) to keep state fresh. "use strict"; import { api, apiJson } from "./api.js"; const els = {}; let pollTimer = null; let pollInterval = 5000; function setPolling(intervalMs) { if (intervalMs === pollInterval && pollTimer) return; if (pollTimer) clearInterval(pollTimer); pollInterval = intervalMs; pollTimer = setInterval(tick, intervalMs); } async function tick() { let s; try { s = await api("/api/update/status"); } catch { return; } els.current.textContent = s.current ?? "--"; els.available.textContent = s.available ?? "--"; const card = els.card; if (s.needsUpdate || s.inProgress) { card.hidden = false; card.classList.toggle("has-update", s.needsUpdate && !s.inProgress); } else { card.hidden = true; } if (s.inProgress) { els.progress.hidden = false; els.start.disabled = true; els.delay.disabled = true; els.phase.textContent = phaseLabel(s.phase); const showCancel = s.phase === "countdown"; els.cancel.hidden = !showCancel; if (s.phase === "countdown" && s.countdownTotal > 0) { const elapsed = s.countdownTotal - s.countdownRemaining; const pct = (elapsed / s.countdownTotal) * 100; els.fill.style.width = `${pct}%`; els.status.textContent = `Restarting in ${formatSeconds(s.countdownRemaining)}`; } else { // Indeterminate during sync / loader install / start phases -- // just show 100% and a phase-specific status string. els.fill.style.width = "100%"; els.status.textContent = phaseStatus(s.phase); } setPolling(1000); } else { els.progress.hidden = true; els.start.disabled = !s.needsUpdate; els.delay.disabled = false; if (s.phase === "failed" && s.error) { els.progress.hidden = false; els.phase.textContent = "FAILED"; els.status.textContent = s.error; els.fill.style.width = "0%"; } setPolling(5000); } } function phaseLabel(phase) { switch (phase) { case "countdown": return "COUNTDOWN"; case "stopping": return "STOPPING"; case "syncing": return "SYNCING MODS"; case "installing_loader": return "INSTALLING LOADER"; case "starting": return "STARTING"; case "complete": return "COMPLETE"; case "failed": return "FAILED"; case "cancelled": return "CANCELLED"; default: return "WORKING"; } } function phaseStatus(phase) { switch (phase) { case "stopping": return "Stopping Minecraft cleanly..."; case "syncing": return "Syncing mods from manifest..."; case "installing_loader": return "Re-running NeoForge installer..."; case "starting": return "Starting Minecraft..."; case "complete": return "Update complete."; default: return ""; } } function formatSeconds(s) { if (s < 60) return `${s}s`; const m = Math.floor(s / 60), r = s % 60; return `${m}m ${String(r).padStart(2, "0")}s`; } export function setupUpdate() { els.card = document.getElementById("updateCard"); els.current = document.getElementById("updCurrent"); els.available = document.getElementById("updAvailable"); els.delay = document.getElementById("updDelay"); els.start = document.getElementById("updStart"); els.progress = document.getElementById("updProgress"); els.phase = document.getElementById("updPhaseLabel"); els.fill = document.getElementById("updProgressFill"); els.status = document.getElementById("updStatusText"); els.cancel = document.getElementById("updCancel"); els.start.addEventListener("click", async () => { const delay = parseInt(els.delay.value, 10); if (!Number.isFinite(delay) || delay < 0) { alert("Enter a non-negative warning duration."); return; } if (!confirm(`Update modpack? Players get a ${delay}s warning, then the server restarts.`)) return; try { await apiJson("/api/update/start", { delaySeconds: delay }); await tick(); } catch (e) { alert(e.message); } }); els.cancel.addEventListener("click", async () => { if (!confirm("Cancel the countdown? Update will be aborted; server stays running.")) return; try { await apiJson("/api/update/cancel", {}); await tick(); } catch (e) { alert(e.message); } }); tick(); setPolling(5000); }