From b3166801142648675b40559a2de1ccd85bd1747c Mon Sep 17 00:00:00 2001 From: Nulo Date: Tue, 29 Aug 2023 20:50:09 -0300 Subject: [PATCH] breadcrumbs --- src/lib/breadcrumbs.ts | 48 ++++++++++++++++++++++++++++++++++++++++++ src/lib/idbValStore.ts | 29 ++++++++++++++++++++++--- src/lib/router.ts | 22 ++++++++++--------- src/views/Page.svelte | 34 ++++++++++++++++++++++++------ 4 files changed, 113 insertions(+), 20 deletions(-) create mode 100644 src/lib/breadcrumbs.ts diff --git a/src/lib/breadcrumbs.ts b/src/lib/breadcrumbs.ts new file mode 100644 index 0000000..b4c4b7c --- /dev/null +++ b/src/lib/breadcrumbs.ts @@ -0,0 +1,48 @@ +import { derived } from "svelte/store"; +import IdbValStore from "./idbValStore"; + +export type PageParams = { + worldId: string; + pageId: string; +}; +type WorldId = string; +type PageId = string; + +class BreadcrumbsStore { + idb: IdbValStore>; + idbLastWorld: IdbValStore; + constructor() { + this.idb = new IdbValStore("schreiben-breadcrumbs"); + this.idbLastWorld = new IdbValStore("schreiben-last-world"); + } + + async newCrumb({ worldId, pageId }: PageParams) { + await this.idbLastWorld.set(worldId); + await this.idb.update((map) => { + if (!map) map = new Map(); + let crumbs = map.get(worldId) || []; + if (crumbs.includes(pageId)) { + crumbs = crumbs.slice(0, crumbs.indexOf(pageId) + 1); + } else { + crumbs.push(pageId); + } + map.set(worldId, crumbs); + return map; + }); + } + + async lastWorldBreadcrumbs() { + const map = await this.idb.get(); + const worldId = await this.idbLastWorld.get(); + if (!worldId) return; + const breadcrumbs = map?.get(worldId); + if (!breadcrumbs) return; + return { worldId, breadcrumbs }; + } + + worldStore(worldId: WorldId) { + return derived(this.idb, (map) => map?.get(worldId) || []); + } +} + +export default new BreadcrumbsStore(); diff --git a/src/lib/idbValStore.ts b/src/lib/idbValStore.ts index b3ceb2c..e478d4e 100644 --- a/src/lib/idbValStore.ts +++ b/src/lib/idbValStore.ts @@ -1,14 +1,37 @@ -import { get, set } from "idb-keyval"; +import { get, set, update } from "idb-keyval"; export default class IdbValStore { name: string; + subscriptors: ((val: T) => void)[]; constructor(name: string) { this.name = name; + this.subscriptors = []; } get(): Promise { return get(this.name); } - set(val: T): Promise { - return set(this.name, val); + async set(val: T): Promise { + await set(this.name, val); + this.push(val); + } + async update(updater: (val: T | undefined) => T): Promise { + let newVal: T | undefined = undefined; + await update(this.name, (val) => (newVal = updater(val))); + if (newVal === undefined) throw new Error("what the fuck"); + this.push(newVal); + } + + private push(val: T) { + for (const sub of this.subscriptors) sub(val); + } + subscribe(subscription: (value: T | undefined) => void): () => void { + this.subscriptors.push(subscription); + (async () => { + const val = await this.get(); + if (this.subscriptors.includes(subscription)) subscription(val); + })(); + return () => { + this.subscriptors = this.subscriptors.filter((s) => s !== subscription); + }; } } diff --git a/src/lib/router.ts b/src/lib/router.ts index 5bfe4e3..783b748 100644 --- a/src/lib/router.ts +++ b/src/lib/router.ts @@ -1,6 +1,5 @@ import navaid from "navaid"; import { writable } from "svelte/store"; -import { inject } from "regexparam"; import ChooseWorld from "../views/ChooseWorld.svelte"; import CreateWorld from "../views/CreateWorld.svelte"; @@ -9,9 +8,8 @@ import NotFound from "../views/NotFound.svelte"; import Page from "../views/Page.svelte"; import ShareWorld from "../views/ShareWorld.svelte"; import { routes } from "./routes"; -import IdbValStore from "./idbValStore"; - -export const lastPageStore = new IdbValStore("schreiben-last-page"); +import breadcrumbs, { type PageParams } from "./breadcrumbs"; +import { inject } from "regexparam"; export const currentRoute = writable<{ // XXX: in lack of a better type for Svelte components @@ -34,15 +32,19 @@ router.on(routes.ShareWorld, (params) => router.on(routes.JoinWorld, (params) => currentRoute.set({ component: JoinWorld, params }), ); -router.on(routes.Page, (params) => - currentRoute.set({ component: Page, params }), -); +router.on(routes.Page, (params) => { + currentRoute.set({ component: Page, params }); + breadcrumbs.newCrumb(params as PageParams); +}); async function setRouteToLastPage() { if (location.pathname === "/") { - const lastPage = await lastPageStore.get(); - if (lastPage) { - router.route(inject(routes.Page, lastPage)); + const lastWorldBreadcrumbs = await breadcrumbs.lastWorldBreadcrumbs(); + if (lastWorldBreadcrumbs) { + const { worldId, breadcrumbs } = lastWorldBreadcrumbs; + for (const crumb of breadcrumbs) { + router.route(inject(routes.Page, { worldId, pageId: crumb })); + } } } } diff --git a/src/views/Page.svelte b/src/views/Page.svelte index f28abf7..a8d9e4b 100644 --- a/src/views/Page.svelte +++ b/src/views/Page.svelte @@ -1,18 +1,21 @@ {#if state === "loading"}Cargando...{:else if "doc" in state}