Applying a new version at the start of a board was right about when and wrong about what it costs. I wrote that the reload costs a player the wait and nothing else; a screenshot of it says otherwise. They tap a puzzle, the app reloads, and they are looking at the title window. The tap is gone, and what they actually got for pressing the button was a place at the back of the queue. So the reload carries their intent. The family they were opening goes into session storage on the way out and is read back once on the way in, and the app opens it rather than starting at the title. Session storage and not local, so it cannot outlive the tab and drop somebody onto a board they never picked; read once and cleared, so any launch that is not that reload begins where it always did. This is the half of the pattern PROXX has that I had left out, and the reason it has it.
- commit
- a2c47c76a2f0f82a41ea2d60c0689d6b9aa7d1b3
- parent
- 65a78924d32ed61869df110711f0e2e9373cd0bf
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 26 | +++++++++++++++++++++----- |
| M | src/ | 15 | ++++++++++++--- |
| M | src/ | 25 | ++++++++++++++++++++++++- |
@@ -291,8 +291,7 @@ fn init(raw: String) -> #(Model, Effect(Message)) {
291291 probing: [],292292 walking: False,293293 )294- #(295- model,294+ let boot =296295 effect.batch([297296 browser.watch_frame(Ticked),298297 browser.watch_viewport(ViewportChanged),@@ -316,8 +315,21 @@ fn init(raw: String) -> #(Model, Effect(Message)) {
316315 browser.load_fonts(FontsReady),317316 browser.register_service_worker(),318317 browser.request_persistence(),319- ]),320- )318+ ])319+ // A reload that was taken to install a new version hands back what the player320+ // had just asked for, so the update costs them the wait and genuinely nothing321+ // else. Any other launch starts at the title window, which is the only thing322+ // an empty resume can mean.323+ let resume =324+ families.all()325+ |> list.find(fn(family) { family.id == browser.take_resume() })326+ case resume {327+ Ok(family) -> {328+ let #(model, opened) = open_family(model, family)329+ #(model, effect.batch([boot, opened]))330+ }331+ Error(_) -> #(model, boot)332+ }321333 }322334 323335 // ------------------------------------------------------------------ update@@ -879,7 +891,11 @@ fn begin_play(model: Model) -> #(Model, Effect(Message)) {
879891 // last board is finished with, and a reload costs a player the wait and880892 // nothing else. Almost every time there is nothing waiting and this does881893 // nothing at all.882- #(model, effect.batch([browser.apply_update(), page(model), paint(model)]))894+ #(model, effect.batch([895+ browser.apply_update(model.family.id),896+ page(model),897+ paint(model),898+ ]))883899 }884900 }885901 }@@ -153,14 +153,23 @@ pub fn register_service_worker() -> Effect(message) {
153153 }154154 155155 @external(javascript, "./browser_ffi.mjs", "applyUpdate")156-fn apply_update_ffi() -> Nil156+fn apply_update_ffi(resume: String) -> Nil157+158+@external(javascript, "./browser_ffi.mjs", "takeResume")159+fn take_resume_ffi() -> String160+161+/// The family the app was opening when it reloaded to take an update, if it was.162+/// Reading it clears it.163+pub fn take_resume() -> String {164+ take_resume_ffi()165+}157166 158167 /// Take a waiting version, if there is one. Nothing happens when there is not.159168 ///160169 /// Called at the seam between one board and the next, which is the only moment161170 /// in this app where a reload costs a player nothing.162-pub fn apply_update() -> Effect(message) {163- effect.from(fn(_dispatch) { apply_update_ffi() })171+pub fn apply_update(resume: String) -> Effect(message) {172+ effect.from(fn(_dispatch) { apply_update_ffi(resume) })164173 }165174 166175 @external(javascript, "./browser_ffi.mjs", "loadFonts")@@ -281,13 +281,24 @@ export function registerServiceWorker() {
281281 /// until the response arrives, so the old worker is still controlling and the282282 /// waiting one stays waiting. It has to be told to skip, and the reload has to283283 /// wait until it has actually taken over.284-export function applyUpdate() {284+const RESUME = "klotski.resume";285+286+export function applyUpdate(resume) {285287 if (!("serviceWorker" in navigator)) return;286288 navigator.serviceWorker287289 .getRegistration()288290 .then((registration) => {289291 const worker = registration && registration.waiting;290292 if (!worker) return;293+ // What the player was in the middle of asking for, kept across the reload294+ // so it can be handed straight back to them. Without this the reload lands295+ // on the title window and the tap that triggered it is simply gone —296+ // which is not "the wait and nothing else", it is being sent to the back297+ // of the queue for pressing the button. Session storage, so it cannot298+ // outlive the tab and strand somebody on a board they did not pick.299+ try {300+ sessionStorage.setItem(RESUME, resume);301+ } catch (_) {}291302 navigator.serviceWorker.addEventListener(292303 "controllerchange",293304 () => window.location.reload(),@@ -298,6 +309,18 @@ export function applyUpdate() {
298309 .catch(() => {});299310 }300311 312+/// What the app was opening when it reloaded to take an update, once. Reading it313+/// clears it, so a later reload starts from the title window like any other.314+export function takeResume() {315+ try {316+ const value = sessionStorage.getItem(RESUME);317+ sessionStorage.removeItem(RESUME);318+ return value ?? "";319+ } catch (_) {320+ return "";321+ }322+}323+301324 // ------------------------------------------------------------- the surveyor302325 303326 // The build substitutes the worker's fingerprinted URL for `__SOLVER_WORKER__`