session restore added
This commit is contained in:
parent
71a428ee48
commit
4f01b3f1af
1 changed files with 109 additions and 15 deletions
100
index.html
100
index.html
|
|
@ -1755,6 +1755,9 @@
|
|||
}
|
||||
|
||||
// --- seed with the example problem ---
|
||||
// Only used for a fresh tab; a tab reload/restore refills the form from
|
||||
// sessionStorage instead (see restoreSession below).
|
||||
function seedDefault() {
|
||||
addCity({name: "0", type: "hub", renown: 2});
|
||||
addCity({name: "1", type: "foundry", renown: 2, vat_steel: 1, vat_brass: 1, vat_electrum: 1});
|
||||
addCity({name: "2", type: "hub", renown: 2});
|
||||
|
|
@ -1767,7 +1770,8 @@
|
|||
"steel": 2,
|
||||
"brass": 1,
|
||||
"electrum": 2
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// --- deep links ---
|
||||
// /?solve=<id> loads one solve; /?solves=<id1>,<id2>,… loads an arbitrary
|
||||
|
|
@ -1785,13 +1789,103 @@
|
|||
document.getElementById("output").prepend(card);
|
||||
pending.set(token, {token, card, n, confirmed: true});
|
||||
}
|
||||
|
||||
// --- session persistence -------------------------------------------
|
||||
// The browser natively restores plain form controls (the Game inputs,
|
||||
// start-resource numbers, tradeable checkboxes) on a reload / "reopen
|
||||
// closed tab", but not the SPA-built cards (cities, agents, terms, …) nor
|
||||
// the queue of solve cards under #output — those are recreated by script
|
||||
// each load. We mirror both into sessionStorage (per-tab, and preserved
|
||||
// across tab restore) so the whole page comes back as it was left.
|
||||
const SESSION_KEY = "dws_session";
|
||||
|
||||
// Tokens of every solve card currently on the page, top-to-bottom.
|
||||
function currentTokens() {
|
||||
return [...document.querySelectorAll("#output [data-token]")]
|
||||
.map(e => e.dataset.token);
|
||||
}
|
||||
|
||||
function saveSession() {
|
||||
// buildProblem() samples log terms and can throw on a half-typed
|
||||
// expression; if so, keep whatever problem we last stored so a
|
||||
// transiently-invalid form doesn't wipe the saved inputs.
|
||||
let problem = null, maxTime;
|
||||
try {
|
||||
problem = buildProblem();
|
||||
maxTime = +document.getElementById("time").value;
|
||||
} catch (e) {/* keep previously-saved problem */}
|
||||
let prev = {};
|
||||
try {prev = JSON.parse(sessionStorage.getItem(SESSION_KEY) || "{}");}
|
||||
catch (e) {/* ignore corrupt/absent state */}
|
||||
const data = {
|
||||
problem: problem || prev.problem || null,
|
||||
maxTime: problem ? maxTime : prev.maxTime,
|
||||
tokens: currentTokens(),
|
||||
solutionCount,
|
||||
};
|
||||
try {sessionStorage.setItem(SESSION_KEY, JSON.stringify(data));}
|
||||
catch (e) {/* storage may be full/unavailable; state just won't persist */}
|
||||
}
|
||||
|
||||
// Debounced: coalesce bursts of edits (and card add/remove mutations)
|
||||
// into a single write.
|
||||
let saveTimer = null;
|
||||
function scheduleSave() {
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(saveSession, 300);
|
||||
}
|
||||
|
||||
// Refill the form and re-open the solve queue from a prior session.
|
||||
// Returns true if there was a session to restore.
|
||||
function restoreSession() {
|
||||
let data;
|
||||
try {data = JSON.parse(sessionStorage.getItem(SESSION_KEY) || "null");}
|
||||
catch (e) {return false;}
|
||||
if (!data) return false;
|
||||
if (data.problem) applyProblem(data.problem, data.maxTime);
|
||||
const tokens = data.tokens || [];
|
||||
// Reserve a card per token and let the shared /job_status stream
|
||||
// resolve each (rendering a finished solve fetched from the server,
|
||||
// or tracking one still queued/running). Load in reverse so the
|
||||
// first token ends up on top, mirroring how they were prepended.
|
||||
for (const token of tokens.slice().reverse()) loadSharedSolve(token);
|
||||
if (tokens.length) syncStream();
|
||||
// Keep future solve numbering above anything from the old session.
|
||||
if (typeof data.solutionCount === "number")
|
||||
solutionCount = Math.max(solutionCount, data.solutionCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- startup ---
|
||||
const restored = restoreSession();
|
||||
if (!restored) seedDefault();
|
||||
|
||||
// Deep links still work on top of a restored session; skip any token the
|
||||
// restored queue already holds so it isn't duplicated.
|
||||
const params = new URLSearchParams(location.search);
|
||||
const sharedTokens = (params.get("solves") || params.get("solve") || "")
|
||||
.split(",").map(s => s.trim()).filter(Boolean);
|
||||
const present = new Set(currentTokens());
|
||||
const newShared = sharedTokens.filter(t => !present.has(t));
|
||||
// Load in reverse so the first id ends up on top (each load prepends).
|
||||
for (const token of sharedTokens.slice().reverse()) loadSharedSolve(token);
|
||||
if (sharedTokens.length) syncStream();
|
||||
for (const token of newShared.slice().reverse()) loadSharedSolve(token);
|
||||
if (newShared.length) syncStream();
|
||||
updateNotifyUi();
|
||||
|
||||
// Wire autosave only after the initial load has settled, so the seed /
|
||||
// restore / deep-link work above doesn't churn through it. Edits to any
|
||||
// control save; card add/remove and queue changes save via observers.
|
||||
document.addEventListener("input", scheduleSave);
|
||||
document.addEventListener("change", scheduleSave);
|
||||
const cardObserver = new MutationObserver(scheduleSave);
|
||||
for (const id of ["cities", "agents", "terms", "constraints",
|
||||
"conversions", "optional_conversions"])
|
||||
cardObserver.observe(document.getElementById(id), {childList: true});
|
||||
// The solve queue: card add/remove plus the data-token that appears when a
|
||||
// pending card is replaced by its rendered solution.
|
||||
new MutationObserver(scheduleSave).observe(document.getElementById("output"),
|
||||
{childList: true, subtree: true, attributes: true, attributeFilter: ["data-token"]});
|
||||
saveSession();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue