1
0
watterblock/ui/base_view.js
Adrian Wannenmacher 8a1140688e
improve in-memory change handling
The base view now does a better job at keeping the sessions list up to
date with the current session, without having to reload it all the time.

The purpose of this is to be able to show the list and the current
session at the same time, and keep them in sync without polling the
database.
2026-03-01 03:14:00 +01:00

163 lines
4.0 KiB
JavaScript

"use strict";
import Session from "/models/session.js";
import SessionRepo from "/data/session_repo.js";
import SessionList from "/ui/session_list.js";
import SessionView from "/ui/session.js";
export default class BaseView {
#model = new BaseViewModel();
oninit() {
this.#model.loadAllSessions();
}
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 });
if (this.#model.sessions !== null)
return m(SessionList, {
models: this.#model.sessions,
onSelect: (session) => this.#model.current = session,
});
return m("p", "Wart kurz, i lad grad die Spiele…");
}
}
class BaseViewModel {
#current = null;
#currentLoading = null;
get current() {
return this.#current;
}
#setCurrent(value) {
if (this.#current !== null)
this.#current.removeEventListener(
Session.EVENT_CHANGE, this.#handleCurrentUpdate);
if (value !== null)
value.addEventListener(Session.EVENT_CHANGE, this.#handleCurrentUpdate);
this.#current = value;
}
set current(value) {
if (value instanceof Session) {
this.#setCurrent(value);
this.#currentLoading = null;
} else if (typeof value === "number") {
if (value === this.#current?.id || value === this.#currentLoading)
return;
this.#currentLoading = value;
SessionRepo
.get(value)
.then((s) => {
if (this.#currentLoading === s?.id)
this.#setCurrent(s);
})
.catch((e) => {
console.error("failed to load session: ", e);
})
.finally(() => {
m.redraw();
if (this.#currentLoading === value)
this.#currentLoading = null;
});
} else if (value === null) {
if (this.#current === null)
return;
this.#setCurrent(null);
this.#currentLoading = null;
} else {
throw new TypeError("current session must be session or id or null");
}
}
get currentLoading() {
return this.#currentLoading !== null;
}
static #newSessionMarker = Symbol("new session loading");
newSession() {
if (this.#currentLoading === BaseViewModel.#newSessionMarker)
return;
this.#currentLoading = BaseViewModel.#newSessionMarker;
let session = new Session();
SessionRepo
.put(session)
.then(() => {
if (this.#currentLoading === BaseViewModel.#newSessionMarker) {
this.#setCurrent(session);
this.#sessions.splice(0, 0, session);
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;
});
}
#sessions = null;
#sessionsLoading = false;
get sessions() {
return this.#sessions;
}
loadAllSessions() {
if (this.#sessionsLoading)
return;
this.#sessionsLoading = true;
SessionRepo
.getAllFromNewest()
.then(s => this.#sessions = s)
.finally(() => {
this.#sessionsLoading = false;
m.redraw();
});
}
#handleCurrentUpdate = (e) => {
if (this.#current !== e.target || this.#current === this.#sessions[0]) {
e.target.removeEventListener(
Session.EVENT_CHANGE, this.#handleCurrentUpdate);
return;
}
if (this.#current?.id === this.#sessions[0]?.id) {
e.target.removeEventListener(
Session.EVENT_CHANGE, this.#handleCurrentUpdate);
this.#sessions[0] = this.#current;
return;
}
this.loadAllSessions();
};
}