breadcrumbs
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
Cat /dev/Nulo 2023-08-29 20:50:09 -03:00
parent e3b2edd214
commit b316680114
4 changed files with 113 additions and 20 deletions

48
src/lib/breadcrumbs.ts Normal file
View file

@ -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<Map<WorldId, PageId[]>>;
idbLastWorld: IdbValStore<WorldId>;
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();

View file

@ -1,14 +1,37 @@
import { get, set } from "idb-keyval";
import { get, set, update } from "idb-keyval";
export default class IdbValStore<T> {
name: string;
subscriptors: ((val: T) => void)[];
constructor(name: string) {
this.name = name;
this.subscriptors = [];
}
get(): Promise<T | undefined> {
return get<T>(this.name);
}
set(val: T): Promise<void> {
return set(this.name, val);
async set(val: T): Promise<void> {
await set(this.name, val);
this.push(val);
}
async update(updater: (val: T | undefined) => T): Promise<void> {
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);
};
}
}

View file

@ -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 }));
}
}
}
}

View file

@ -1,18 +1,21 @@
<script lang="ts">
import type { XmlFragment } from "yjs";
import { inject } from "regexparam";
import ChevronRight from "eva-icons/fill/svg/chevron-right.svg";
import Editor from "../editor/Editor.svelte";
import { getWorldPage, getWorldY, type WorldY } from "../lib/doc";
import { routes } from "../lib/routes";
import { loadWorlds } from "../lib/worldStorage";
import { lastPageStore } from "../lib/router";
import breadcrumbs from "../lib/breadcrumbs";
export let worldId: string;
export let pageId: string;
$: pageBreadcrumbs = breadcrumbs.worldStore(worldId);
async function loadDoc(
worldId: string,
pageId: string
pageId: string,
): Promise<{ worldY: WorldY; doc: XmlFragment }> {
const worlds = await loadWorlds();
const worldIdentifier = worlds.find((w) => w.room === worldId);
@ -33,11 +36,6 @@
})
.catch((error) => (state = { error }));
}
async function saveLastPage() {
await lastPageStore.set({ worldId, pageId });
}
saveLastPage();
</script>
<nav>
@ -52,6 +50,28 @@
</li>
</ul>
</details>
<!-- https://devdojo.com/pines/docs/breadcrumbs -->
<div
class="flex justify-between rounded-md border border-neutral-200/60 px-3.5 py-1"
>
<ol
class="mb-3 inline-flex items-center space-x-1 text-xs text-neutral-500 sm:mb-0 [&_.active-breadcrumb]:font-medium [&_.active-breadcrumb]:text-neutral-600"
>
{#each $pageBreadcrumbs as crumb, index}
<li>
<a
href={inject(routes.Page, { worldId, pageId: crumb })}
class="inline-flex items-center py-1 font-normal hover:text-neutral-900 focus:outline-none"
class:active-breadcrumb={crumb === pageId}>{crumb}</a
>
</li>
{#if index !== $pageBreadcrumbs.length - 1}
<ChevronRight class="h-5 w-5 fill-current text-gray-400/70" />
{/if}
{/each}
</ol>
</div>
</nav>
{#if state === "loading"}Cargando...{:else if "doc" in state}