Ports main.ts's parseRoute/dispatch hash parsing into a React hook: same #/ and #/attach/:id shape, current route plus a navigate function. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
// React port of main.ts's `parseRoute`/`dispatch` pair (issue #240). Same two
|
|
// routes, same hash shape (`#/`, `#/attach/:id`) — kept so a deep link or a
|
|
// bookmark from the vanilla shell still resolves.
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
export type Route = { kind: "catalog" } | { kind: "attach"; sessionId: string };
|
|
|
|
function parseHash(hash: string): Route {
|
|
const raw = hash.replace(/^#/, "");
|
|
if (raw === "" || raw === "/") return { kind: "catalog" };
|
|
const m = raw.match(/^\/attach\/([^/?#]+)$/);
|
|
if (m) return { kind: "attach", sessionId: decodeURIComponent(m[1]) };
|
|
return { kind: "catalog" };
|
|
}
|
|
|
|
function routeToHash(route: Route): string {
|
|
return route.kind === "catalog" ? "#/" : `#/attach/${encodeURIComponent(route.sessionId)}`;
|
|
}
|
|
|
|
export interface UseHashRouterResult {
|
|
route: Route;
|
|
navigate(route: Route): void;
|
|
}
|
|
|
|
export function useHashRouter(): UseHashRouterResult {
|
|
const [route, setRoute] = useState<Route>(() => parseHash(location.hash));
|
|
|
|
useEffect(() => {
|
|
const onHashChange = () => setRoute(parseHash(location.hash));
|
|
window.addEventListener("hashchange", onHashChange);
|
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
}, []);
|
|
|
|
const navigate = useCallback((next: Route) => {
|
|
const hash = routeToHash(next);
|
|
// Setting an unchanged hash never fires `hashchange`, so the state update
|
|
// that would otherwise ride on that event has to happen here instead.
|
|
if (location.hash === hash) {
|
|
setRoute(next);
|
|
} else {
|
|
location.hash = hash;
|
|
}
|
|
}, []);
|
|
|
|
return { route, navigate };
|
|
}
|