From de0fc8c917ab5980a052127af99e09ba12efb713 Mon Sep 17 00:00:00 2001 From: Adrian Wannenmacher Date: Sun, 1 Mar 2026 02:14:21 +0100 Subject: [PATCH] enable history navigation The previous implementation only looked at the URL (and state) during initialization. This allowed users to open a specific session via URL (e.g. when reloading the page). History navigation, however, was completely ignored. I.e. if the user pressed the back button the URL would change, but the content would remain the same. This has now been corrected. --- index.js | 8 ++++++-- ui/base_view.js | 42 ++++++++++++++++++++++++------------------ ui/session.js | 4 ++-- ui/session_list.js | 31 +++++++++++++++++++++++-------- 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index 7e6cead..52a3411 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,12 @@ import BaseView from "/ui/base_view.js"; m.route.prefix = ""; m.route(document.body, "/", { "/": { - render: function() { - return m(Layout, m(BaseView)); + render: function(vnode) { + let newSession = vnode.attrs.newSession ?? false; + let session = newSession ? null : parseInt(vnode.attrs.session); + session = isNaN(session) ? null : session; + + return m(Layout, m(BaseView, { newSession, session })); }, }, }); diff --git a/ui/base_view.js b/ui/base_view.js index 066013e..95ee6c5 100644 --- a/ui/base_view.js +++ b/ui/base_view.js @@ -9,27 +9,22 @@ export default class BaseView { #model = new BaseViewModel(); oninit() { - let id = m.route.param("session"); - id = parseInt(id); - if (Number.isNaN(id)) - id = null; - - this.#model.current = id; this.#model.loadAllSessions(); } - view() { + view(vnode) { + if (vnode.attrs.newSession) + this.#model.newSession(); + else + this.#model.current = vnode.attrs.session; + if (this.#model.current !== null) - return m(SessionView, { - model: this.#model.current, - onDeselect: () => this.#model.current = null, - }); + return m(SessionView, { model: this.#model.current }); if (this.#model.sessions !== null) return m(SessionList, { models: this.#model.sessions, onSelect: (session) => this.#model.current = session, - onNew: () => this.#model.newSession(), }); return m("p", "Wart kurz, i lad grad die Spiele…"); @@ -48,30 +43,32 @@ class BaseViewModel { if (value instanceof Session) { this.#current = value; this.#currentLoading = null; - m.route.set("/", { session: value.id }); } else if (typeof value === "number") { if (value === this.#current?.id || value === this.#currentLoading) return; this.#currentLoading = value; - m.route.set("/", { session: value }); SessionRepo .get(value) .then((s) => { if (this.#currentLoading === s?.id) this.#current = s; }) + .catch((e) => { + console.error("failed to load session: ", e); + }) .finally(() => { + m.redraw(); if (this.#currentLoading === value) this.#currentLoading = null; - m.redraw(); }); } else if (value === null) { + if (this.#current === null) + return; this.#current = null; this.#currentLoading = null; this.loadAllSessions(); - m.route.set("/"); } else { throw new TypeError("current session must be session or id or null"); @@ -96,13 +93,22 @@ class BaseViewModel { .then(() => { if (this.#currentLoading === BaseViewModel.#newSessionMarker) { this.#current = session; - m.route.set("/", { session: session.id }); + m.route.set("/", { session: session.id }, { replace: true }); } }) + .catch((e) => { + console.error("failed to create new session: ", e); + if (this.#currentLoading === BaseViewModel.#newSessionMarker) + m.route.set( + "/", + { session: this.#current?.id ?? undefined }, + { replace: true }); + else + m.redraw(); + }) .finally(() => { if (this.#currentLoading === BaseViewModel.#newSessionMarker) this.#currentLoading = null; - m.redraw(); }); } diff --git a/ui/session.js b/ui/session.js index ddeb9df..d7c1076 100644 --- a/ui/session.js +++ b/ui/session.js @@ -6,12 +6,12 @@ import RoundView from "/ui/round.js"; export default class SessionView { /** @param {{ attrs: { model: Session } }} param The session model to use. */ - view({ attrs: { model, onDeselect } }) { + view({ attrs: { model } }) { let res = model.result; return m("article.session-view", [ - m("button", { onclick: () => onDeselect() }, "Zruck"), + m(m.route.Link, { href: "/", selector: "button" }, "Zruck"), m("table", [ m("thead", [ m("tr", [ diff --git a/ui/session_list.js b/ui/session_list.js index d3f35a3..6736107 100644 --- a/ui/session_list.js +++ b/ui/session_list.js @@ -4,17 +4,32 @@ import Session from "/models/session.js"; export default class SessionList { /** @param {{ attrs: { models: Session[] } }} param The sessions to show. */ - view({attrs: { models, onSelect, onNew } }) { + view({attrs: { models, onSelect } }) { return m("section", [ - m("button", { onclick: () => onNew() }, "Neie Session"), + m(m.route.Link, { + href: "/", + selector: "button", + options: { + state: { newSession: true }, + }, + }, "Neie Session"), m("ol", [ models.map((s) => m("li", [ - m("button", { onclick: () => onSelect(s) }, [ - m("p", s.ourTeam !== "" ? s.ourTeam : "Unbnannts Team"), - m("p", s.theirTeam !== "" ? s.theirTeam : "Unbnannts Team"), - m("p", "•".repeat(s.result.ourPoints)), - m("p", "•".repeat(s.result.theirPoints)), - ]) + m( + m.route.Link, + { + href: "/", + selector: "button", + params: { session: s.id }, + onclick: () => onSelect(s), + }, + [ + m("p", s.ourTeam !== "" ? s.ourTeam : "Unbnannts Team"), + m("p", s.theirTeam !== "" ? s.theirTeam : "Unbnannts Team"), + m("p", "•".repeat(s.result.ourPoints)), + m("p", "•".repeat(s.result.theirPoints)), + ], + ), ])) ]) ]);