store collection cache

juli.ee dc7bfce4 5b60ff68

verified
Changed files
+79 -2
src
+24
src/utils/route-cache.ts
··· 1 + import { createStore } from "solid-js/store"; 2 + 3 + export interface CollectionCacheEntry { 4 + records: unknown[]; 5 + cursor: string | undefined; 6 + scrollY: number; 7 + reverse: boolean; 8 + } 9 + 10 + type RouteCache = Record<string, CollectionCacheEntry>; 11 + 12 + const [routeCache, setRouteCache] = createStore<RouteCache>({}); 13 + 14 + export const getCollectionCache = (key: string): CollectionCacheEntry | undefined => { 15 + return routeCache[key]; 16 + }; 17 + 18 + export const setCollectionCache = (key: string, entry: CollectionCacheEntry): void => { 19 + setRouteCache(key, entry); 20 + }; 21 + 22 + export const clearCollectionCache = (key: string): void => { 23 + setRouteCache(key, undefined!); 24 + };
+55 -2
src/views/collection.tsx
··· 2 2 import { Client, simpleFetchHandler } from "@atcute/client"; 3 3 import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons"; 4 4 import * as TID from "@atcute/tid"; 5 - import { A, useParams } from "@solidjs/router"; 6 - import { createEffect, createMemo, createResource, createSignal, For, Show } from "solid-js"; 5 + import { A, useBeforeLeave, useParams } from "@solidjs/router"; 6 + import { 7 + createEffect, 8 + createMemo, 9 + createResource, 10 + createSignal, 11 + For, 12 + onMount, 13 + Show, 14 + } from "solid-js"; 7 15 import { createStore } from "solid-js/store"; 8 16 import { hasUserScope } from "../auth/scope-utils"; 9 17 import { agent } from "../auth/state"; ··· 17 25 import { isTouchDevice } from "../layout.jsx"; 18 26 import { resolvePDS } from "../utils/api.js"; 19 27 import { localDateFromTimestamp } from "../utils/date.js"; 28 + import { 29 + clearCollectionCache, 30 + getCollectionCache, 31 + setCollectionCache, 32 + } from "../utils/route-cache.js"; 20 33 21 34 interface AtprotoRecord { 22 35 rkey: string; ··· 85 98 const [reverse, setReverse] = createSignal(false); 86 99 const [recreate, setRecreate] = createSignal(false); 87 100 const [openDelete, setOpenDelete] = createSignal(false); 101 + const [restoredFromCache, setRestoredFromCache] = createSignal(false); 88 102 const did = params.repo; 89 103 let pds: string; 90 104 let rpc: Client; 91 105 106 + const cacheKey = () => `${params.pds}/${params.repo}/${params.collection}`; 107 + 108 + onMount(() => { 109 + const cached = getCollectionCache(cacheKey()); 110 + if (cached) { 111 + setRecords(cached.records as AtprotoRecord[]); 112 + setCursor(cached.cursor); 113 + setReverse(cached.reverse); 114 + setRestoredFromCache(true); 115 + requestAnimationFrame(() => { 116 + window.scrollTo(0, cached.scrollY); 117 + }); 118 + } 119 + }); 120 + 121 + useBeforeLeave((e) => { 122 + const recordPathPrefix = `/at://${did}/${params.collection}/`; 123 + const isNavigatingToRecord = typeof e.to === "string" && e.to.startsWith(recordPathPrefix); 124 + 125 + if (isNavigatingToRecord && records.length > 0) { 126 + setCollectionCache(cacheKey(), { 127 + records: [...records], 128 + cursor: cursor(), 129 + scrollY: window.scrollY, 130 + reverse: reverse(), 131 + }); 132 + } else { 133 + clearCollectionCache(cacheKey()); 134 + } 135 + }); 136 + 92 137 const fetchRecords = async () => { 138 + if (restoredFromCache() && records.length > 0 && !cursor()) { 139 + setRestoredFromCache(false); 140 + return records; 141 + } 142 + if (restoredFromCache()) setRestoredFromCache(false); 143 + 93 144 if (!pds) pds = await resolvePDS(did!); 94 145 if (!rpc) rpc = new Client({ handler: simpleFetchHandler({ service: pds }) }); 95 146 const res = await rpc.get("com.atproto.repo.listRecords", { ··· 168 219 setCursor(undefined); 169 220 setOpenDelete(false); 170 221 setRecreate(false); 222 + clearCollectionCache(cacheKey()); 171 223 refetch(); 172 224 }; 173 225 ··· 304 356 setReverse(!reverse()); 305 357 setRecords([]); 306 358 setCursor(undefined); 359 + clearCollectionCache(cacheKey()); 307 360 refetch(); 308 361 }} 309 362 >