import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useNavigate as useRouterNavigate, useRouterState, } from '@tanstack/react-router'; import { useCallback } from 'react'; import { loadAccount } from '#/lib/account.js'; import { loadRepos, repoRecord, reposKey, spaceOf } from '#/lib/git.js'; /** * The router's location and navigation, in the shape the pages take: a path * string, and a function that goes to one. * * The pages build their own addresses, since a pull request's address holds * a DID and a branch name with slashes in it, and a route pattern cannot say * where one ends. So a link and a navigation take a path, and the router * matches it. */ export function useLocation() { const path = useRouterState({ select: (state) => state.location.pathname }); const navigate = useNavigate(); return { path, navigate }; } /** @returns {(to: string, search?: Record) => void} */ export function useNavigate() { const navigate = useRouterNavigate(); return useCallback( (to, search) => { navigate(search ? { to, search } : { to }); window.scrollTo(0, 0); }, [navigate], ); } /** * The space a repository page is reading from, from its `?space=`. A private * repository is addressed by name and space together; the space is kept on * the address as a query parameter, so a page inside the repository carries * it by the router's retainSearchParams middleware. * @returns {string} - the space URI, '' for a public repository */ export function useSpace() { const repo = useRouterState({ select: (state) => state.location.pathname.split('/').filter(Boolean)[0], }); return repo ? spaceOf(decodeURIComponent(repo)) : ''; } /** * The fragment on the address now, and a way to set one. * * A fragment names a place on the page the reader is already on, so setting * one replaces the history entry rather than adding to it, and neither the * router nor the browser moves the page to it. * @returns {[string, (hash: string) => void]} */ export function useHash() { const hash = useRouterState({ select: (state) => state.location.hash }); const navigate = useRouterNavigate(); const setHash = useCallback( (next) => { navigate({ hash: next, replace: true, resetScroll: false, hashScrollIntoView: false, }); }, [navigate], ); return [hash, setHash]; } /** * The repository record a route names, subscribed to the listing so the * route re-renders when a fresh listing lands. A direct load paints from the * last visit's rows, which may not hold a repository made since; `settled` * says whether the fresh listing has been read, so a route can hold its * placeholder rather than saying "no such repository" too soon. * @param {string} repo * @param {string} [space] * @returns {{record: Record|null, settled: boolean}} */ export function useRepoRecord(repo, space) { const queryClient = useQueryClient(); const account = useQuery({ queryKey: ['account'], queryFn: () => loadAccount(queryClient), staleTime: Number.POSITIVE_INFINITY, }); useQuery({ queryKey: [reposKey()], queryFn: loadRepos, staleTime: Number.POSITIVE_INFINITY, }); return { record: repoRecord(repo, space || spaceOf(repo)), settled: account.isSuccess || account.isError, }; }