atmosphere explorer

browser history in car explorer

handle.invalid 5e49244f 595be972

verified
+55 -4
+55 -4
src/views/car/explore.tsx
··· 5 5 import { fromStream, isCommit } from "@atcute/repo"; 6 6 import * as TID from "@atcute/tid"; 7 7 import { Title } from "@solidjs/meta"; 8 + import { useLocation, useNavigate } from "@solidjs/router"; 8 9 import { 9 10 createEffect, 10 11 createMemo, ··· 33 34 WelcomeView, 34 35 } from "./shared.jsx"; 35 36 37 + const viewToHash = (view: View): string => { 38 + switch (view.type) { 39 + case "repo": 40 + return ""; 41 + case "collection": 42 + return `#${view.collection.name}`; 43 + case "record": 44 + return `#${view.collection.name}/${view.record.key}`; 45 + } 46 + }; 47 + 48 + const hashToView = (hash: string, archive: Archive): View => { 49 + if (!hash || hash === "#") return { type: "repo" }; 50 + 51 + const raw = hash.startsWith("#") ? hash.slice(1) : hash; 52 + const slashIdx = raw.indexOf("/"); 53 + 54 + if (slashIdx === -1) { 55 + const collection = archive.entries.find((e) => e.name === raw); 56 + if (collection) return { type: "collection", collection }; 57 + return { type: "repo" }; 58 + } 59 + 60 + const collectionName = raw.slice(0, slashIdx); 61 + const recordKey = raw.slice(slashIdx + 1); 62 + const collection = archive.entries.find((e) => e.name === collectionName); 63 + if (collection) { 64 + const record = collection.entries.find((r) => r.key === recordKey); 65 + if (record) return { type: "record", collection, record }; 66 + return { type: "collection", collection }; 67 + } 68 + 69 + return { type: "repo" }; 70 + }; 71 + 36 72 export const ExploreToolView = () => { 73 + const location = useLocation(); 74 + const navigate = useNavigate(); 75 + 37 76 const [archive, setArchive] = createSignal<Archive | null>(null); 38 77 const [loading, setLoading] = createSignal(false); 39 78 const [progress, setProgress] = createSignal(0); 40 79 const [error, setError] = createSignal<string>(); 41 - const [view, setView] = createSignal<View>({ type: "repo" }); 80 + 81 + const view = createMemo((): View => { 82 + const arch = archive(); 83 + if (!arch) return { type: "repo" }; 84 + return hashToView(location.hash, arch); 85 + }); 86 + 87 + const navigateToView = (newView: View) => { 88 + const hash = viewToHash(newView); 89 + navigate(`${location.pathname}${hash}`); 90 + }; 42 91 43 92 const parseCarFile = async (file: File) => { 44 93 setLoading(true); ··· 121 170 } 122 171 123 172 setArchive(result); 124 - setView({ type: "repo" }); 173 + if (location.hash) navigate(location.pathname, { replace: true }); 125 174 } catch (err) { 126 175 console.error("Failed to parse CAR file:", err); 127 176 setError(err instanceof Error ? err.message : "Failed to parse CAR file"); ··· 135 184 136 185 const reset = () => { 137 186 setArchive(null); 138 - setView({ type: "repo" }); 139 187 setError(undefined); 188 + if (location.hash) navigate(location.pathname, { replace: true }); 140 189 }; 141 190 142 191 return ( ··· 157 206 /> 158 207 } 159 208 > 160 - {(arch) => <ExploreView archive={arch()} view={view} setView={setView} onClose={reset} />} 209 + {(arch) => ( 210 + <ExploreView archive={arch()} view={view} setView={navigateToView} onClose={reset} /> 211 + )} 161 212 </Show> 162 213 </> 163 214 );