frontend tweaks

This commit is contained in:
Pagwin 2026-06-17 16:02:44 -04:00
parent 2e50f61e57
commit 9866af07d3

View file

@ -185,6 +185,26 @@
opacity: .8; opacity: .8;
border-bottom: 1px solid #8886; border-bottom: 1px solid #8886;
} }
/* Solution cards: each solve gets its own collapsible block so older
solutions stay viewable, newest on top. */
details.solution {
border: 1px solid #8884;
border-radius: 8px;
padding: .3rem .8rem;
margin-top: .8rem;
}
details.solution>summary {
font-weight: 600;
cursor: pointer;
padding: .3rem 0;
}
.pending {
opacity: .75;
margin-top: .8rem;
}
</style> </style>
</head> </head>
@ -445,8 +465,20 @@
[upBoxes[u], " " + u])); [upBoxes[u], " " + u]));
} }
} }
type.onchange = renderUpgrades; // Vat amounts only matter for Foundries; hide them otherwise and keep
// them as the card's last fields (before the actions).
const vatFields = [
field("Vat steel", vs),
field("Vat brass", vb),
field("Vat electrum", ve),
];
function renderVats() {
const show = type.value === "foundry";
for (const f of vatFields) f.style.display = show ? "" : "none";
}
type.onchange = () => {renderUpgrades(); renderVats();};
renderUpgrades(); renderUpgrades();
renderVats();
card._get = () => { card._get = () => {
const o = {name: name.value, type: type.value}; const o = {name: name.value, type: type.value};
@ -469,14 +501,12 @@
field("Name", name), field("Name", name),
field("Type", type), field("Type", type),
field("Renown", renown), field("Renown", renown),
field("Vat steel", vs),
field("Vat brass", vb),
field("Vat electrum", ve),
field("Upgrades (already installed)", upWrap), field("Upgrades (already installed)", upWrap),
checkField("Can renovate", reno), checkField("Can renovate", reno),
field("Adjacent cities (csv of names)", adjacent), field("Adjacent cities (csv of names)", adjacent),
field("Forced actions (turn:action, csv)", forced), field("Forced actions (turn:action, csv)", forced),
field("Avail turns (csv, blank=all)", avail), field("Avail turns (csv, blank=all)", avail),
...vatFields,
el("div", {class: "card-actions"}, removeBtn(card))); el("div", {class: "card-actions"}, removeBtn(card)));
document.getElementById("cities").append(card); document.getElementById("cities").append(card);
} }
@ -497,11 +527,13 @@
const forced = el("input", {value: "", placeholder: "0:Aridias"}); const forced = el("input", {value: "", placeholder: "0:Aridias"});
const avail = el("input", {value: ""}); const avail = el("input", {value: ""});
const bastionsField = field("Bastions (Baron only)", bastions);
let nameEdited = !!a.name; let nameEdited = !!a.name;
name.oninput = () => {nameEdited = true;}; name.oninput = () => {nameEdited = true;};
function syncType() { function syncType() {
if (!nameEdited) name.value = type.value; if (!nameEdited) name.value = type.value;
bastions.disabled = (type.value !== "Baron"); bastionsField.style.display = (type.value === "Baron") ? "" : "none";
desc.textContent = AGENT_DESC[type.value] || ""; desc.textContent = AGENT_DESC[type.value] || "";
} }
type.onchange = syncType; type.onchange = syncType;
@ -519,9 +551,9 @@
card.append( card.append(
field("Agent", type), field("Agent", type),
field("Effect", desc), field("Effect", desc),
field("Bastions (Baron only)", bastions),
field("Forced city (turn:city, csv)", forced), field("Forced city (turn:city, csv)", forced),
field("Avail turns (csv, blank=all)", avail), field("Avail turns (csv, blank=all)", avail),
bastionsField,
el("div", {class: "card-actions"}, removeBtn(card))); el("div", {class: "card-actions"}, removeBtn(card)));
document.getElementById("agents").append(card); document.getElementById("agents").append(card);
} }
@ -645,16 +677,24 @@
}; };
} }
// Each solve produces its own collapsible <details> card; older ones are
// kept (collapsed) so previous solutions stay viewable. solutionCount labels
// them in request order.
let solutionCount = 0;
async function run() { async function run() {
const errBox = document.getElementById("error"); const errBox = document.getElementById("error");
const out = document.getElementById("output"); const out = document.getElementById("output");
errBox.textContent = ""; out.innerHTML = ""; errBox.textContent = "";
// A placeholder card for this request, prepended so the newest is on top.
// It's replaced by the solution on success, or removed on error.
const pending = el("p", {class: "pending"}, "Solving…");
out.prepend(pending);
let problem, time; let problem, time;
try { try {
problem = buildProblem(); problem = buildProblem();
time = +document.getElementById("time").value; time = +document.getElementById("time").value;
} catch (e) {errBox.textContent = e.message; return;} } catch (e) {errBox.textContent = e.message; pending.remove(); return;}
out.textContent = "Solving…";
try { try {
const resp = await fetch("/solve", { const resp = await fetch("/solve", {
method: "POST", method: "POST",
@ -662,15 +702,20 @@
body: JSON.stringify({problem, max_time_seconds: time}), body: JSON.stringify({problem, max_time_seconds: time}),
}); });
const data = await resp.json(); const data = await resp.json();
if (!resp.ok) {out.innerHTML = ""; errBox.textContent = data.error || "Server error"; return;} if (!resp.ok) {pending.remove(); errBox.textContent = data.error || "Server error"; return;}
renderSolution(data); renderSolution(data, pending);
} catch (e) {out.innerHTML = ""; errBox.textContent = e.message;} } catch (e) {pending.remove(); errBox.textContent = e.message;}
} }
function renderSolution(s) { function renderSolution(s, placeholder) {
const out = document.getElementById("output"); // Collapse any previously-shown solutions so the new one is the focus.
out.innerHTML = ""; for (const d of document.querySelectorAll("#output details.solution")) d.open = false;
out.append(el("h2", {}, "Solution"));
const n = ++solutionCount;
const details = el("details", {class: "solution", open: ""});
details.append(el("summary", {},
`Solution ${n} — ${s.status}, objective ${s.objective_value ?? "—"}`));
const out = details;
out.append(el("p", { out.append(el("p", {
html: html:
`<b>Status:</b> ${s.status} &nbsp; <b>Objective:</b> ${s.objective_value ?? "—"} &nbsp; ` + `<b>Status:</b> ${s.status} &nbsp; <b>Objective:</b> ${s.objective_value ?? "—"} &nbsp; ` +
@ -709,6 +754,10 @@
["Turn", "Converted into", "Amount"], ["Turn", "Converted into", "Amount"],
s.trade_conversions.map(c => [c.turn, c.resource, fmtNum(c.amount)]))); s.trade_conversions.map(c => [c.turn, c.resource, fmtNum(c.amount)])));
} }
// Swap the finished card in for this request's placeholder.
if (placeholder) placeholder.replaceWith(details);
else document.getElementById("output").prepend(details);
} }
// Trim trailing ".0" so whole numbers read cleanly. // Trim trailing ".0" so whole numbers read cleanly.