Initial commit: Brass & Sigil monorepo
Self-hosted Minecraft modpack distribution + administration system.
- launcher/ Avalonia 12 desktop client; single-file win-x64 publish.
Microsoft auth via XboxAuthNet, manifest+SHA-1 mod sync,
portable install path, sidecar settings.
- server/ brass-sigil-server daemon (.NET 8, linux-x64). Wraps the
MC subprocess, embedded Kestrel admin panel with cookie
auth + rate limiting, RCON bridge, scheduled backups,
BlueMap CLI integration with player markers + skin proxy,
friend-side whitelist request flow, world wipe with seed
selection (keep current / random / custom).
- pack/ pack.lock.json (Modrinth + manual CurseForge entries),
data-only tweak source under tweaks/, build outputs in
overrides/ (gitignored).
- scripts/ Build-Pack / Build-Tweaks / Update-Pack / Check-Updates
plus Deploy-Brass.ps1 unified one-shot deploy with
version-bump pre-flight and daemon-state detection.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user