This commit is contained in:
parent
e3b2edd214
commit
b316680114
4 changed files with 113 additions and 20 deletions
48
src/lib/breadcrumbs.ts
Normal file
48
src/lib/breadcrumbs.ts
Normal 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();
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Reference in a new issue