window.pageBoot = async (me) => {
let settings = [], cat = null;
const dirty = new Set();
async function load(){
const d = await GET("/settings/list");
settings = d.settings || [];
const cats = [...new Set(settings.map(s => s.category))];
if (!cat && cats.length) cat = cats[0];
$("#cats").innerHTML = cats.map(c =>
`
${esc(c)}
${settings.filter(s=>s.category===c).length}
`).join("");
renderRows();
}
function renderRows(){
$("#catTitle").textContent = cat || "—";
const rows = settings.filter(s => s.category === cat);
$("#rows").innerHTML = rows.map(s => {
const val = s.value === null || s.value === undefined ? "" : String(s.value);
const input = (s.type === "bool")
? ``
: ``;
return `
${esc(s.label)}
key: ${esc(s.key)} · default: ${esc(String(s.default))}${s.is_default ? " (using default)" : " (customized)"}
${input}
`;
}).join("");
dirty.clear();
}
$("#cats").addEventListener("click", (e) => {
const it = e.target.closest(".cat-item"); if (!it) return;
cat = it.dataset.cat;
load();
});
$("#rows").addEventListener("input", (e) => {
const inp = e.target.closest("[data-key]"); if (inp) dirty.add(inp.dataset.key);
});
$("#rows").addEventListener("change", (e) => {
const inp = e.target.closest("[data-key]"); if (inp) dirty.add(inp.dataset.key);
});
$("#btnSave").addEventListener("click", async () => {
if (!dirty.size) { toast("No changes to save"); return; }
const values = {};
for (const k of dirty) {
const inp = $(`#rows [data-key="${CSS.escape(k)}"]`);
values[k] = inp.value;
}
try {
const d = await POST("/settings/save-batch", { values });
const errs = d.errors || {};
const nApplied = Object.keys(d.applied || {}).length;
if (nApplied) toast(`Saved ${nApplied} setting(s)`, "ok");
if (Object.keys(errs).length) toast("Some values rejected: " + JSON.stringify(errs), "err", 8000);
await load();
} catch (err) { toast("Save failed: " + err.message, "err"); }
});
$("#rows").addEventListener("click", (e) => {
const b = e.target.closest("button"); if (!b || !b.dataset.reset) return;
confirmedAction(`Reset “${b.dataset.reset}” to its default value?`, async () => {
await POST("/settings/reset", { key: b.dataset.reset, value: "" });
toast("Reset to default", "ok"); await load();
});
});
$("#btnResetCat").addEventListener("click", () => {
const keys = settings.filter(s => s.category === cat && !s.is_default).map(s => s.key);
if (!keys.length) { toast("All settings in this category already use defaults"); return; }
confirmedAction(`Reset all customized settings in “${cat}”?`, async () => {
for (const k of keys) await POST("/settings/reset", { key: k, value: "" });
toast(`Reset ${keys.length} setting(s)`, "ok"); await load();
});
});
=== HTML controls ===
24:
25:
32:
33:
39:
42: —
43: