import { useQueryClient } from '../lib/atproto-query'; import { useAuth } from './auth'; import { getRepo, getRepoBranches, getRepoDefaultBranch, getRepoTags, getRepoForkCount, getRepoTree, getRepoLog, getRepoLanguages, type RepoContext, } from './api/repos'; import { getIssue, getRepoIssueCount } from './api/issues'; import { fetchPullRoundPatch, getPull, getRepoPullCount } from './api/pulls'; import { getString } from './api/strings'; import { getRepoStarSummary } from './api/stars'; import { repoQueryKey, issueQueryKey, pullQueryKey, pullPatchQueryKey, issuesQueryKey, pullsQueryKey } from './api/keys'; import { resolveDefaultBranchName } from '../pages/repo/code-helpers'; export const PRELOAD_HOVER_TIME = 30; export const usePreloader = () => { const queryClient = useQueryClient(); return (queryKey: readonly unknown[], queryFn: () => Promise, delayMs = PRELOAD_HOVER_TIME) => { let hoverTimer: ReturnType | undefined; const onMouseEnter = () => { hoverTimer = setTimeout(() => { queryClient.prefetchQuery({ queryKey, queryFn }); }, delayMs); }; const onMouseLeave = () => { clearTimeout(hoverTimer); }; return { onMouseEnter, onMouseLeave }; }; }; export const useRepoPreloader = () => { const queryClient = useQueryClient(); const auth = useAuth(); const preload = usePreloader(); return (owner: string, name: string) => { return preload( ['repo-prefetch', owner, name], async () => { const repo = await queryClient.fetchQuery({ queryKey: repoQueryKey(owner, name), queryFn: () => getRepo(owner, name), }); if (!repo) { return repo; } const currentDid = auth.currentDid(); // Fetch branches and default-branch, and prefetch counts and tags concurrently const [branches, defaultBranchRes] = await Promise.all([ queryClient.fetchQuery({ queryKey: ['repo-branches', repo.repoDid], queryFn: () => getRepoBranches(repo), }), queryClient.fetchQuery({ queryKey: ['repo-default-branch', repo.repoDid], queryFn: () => getRepoDefaultBranch(repo), }), queryClient.prefetchQuery({ queryKey: ['repo-star-summary', repo.repoDid, currentDid], queryFn: () => getRepoStarSummary(repo, currentDid), }), queryClient.prefetchQuery({ queryKey: ['repo-fork-count', repo.repoDid], queryFn: () => getRepoForkCount(repo), }), queryClient.prefetchQuery({ queryKey: [...issuesQueryKey(repo.repoDid), 'count'], queryFn: () => getRepoIssueCount(repo), }), queryClient.prefetchQuery({ queryKey: [...pullsQueryKey(repo.repoDid), 'count'], queryFn: () => getRepoPullCount(repo), }), queryClient.prefetchQuery({ queryKey: ['repo-tags', repo.repoDid], queryFn: () => getRepoTags(repo), }), ]); const defaultBranchName = defaultBranchRes?.name ?? defaultBranchRes?.branch ?? defaultBranchRes?.reference?.name ?? resolveDefaultBranchName(branches?.branches) ?? 'main'; // Now that we have the default branch name, prefetch tree, log and languages await Promise.all([ queryClient.prefetchQuery({ queryKey: ['repo-tree', repo.repoDid, defaultBranchName, ''], queryFn: async () => ({ tree: await getRepoTree(repo, defaultBranchName, ''), path: '', ref: defaultBranchName, }), }), queryClient.prefetchQuery({ queryKey: ['repo-log', repo.repoDid, defaultBranchName], queryFn: () => getRepoLog(repo, defaultBranchName), }), queryClient.prefetchQuery({ queryKey: ['repo-languages', repo.repoDid, defaultBranchName], queryFn: () => getRepoLanguages(repo, defaultBranchName), }), ]).catch(() => { // Ignore background prefetch errors }); return repo; } ); }; }; export const useStringPreloader = () => { const preload = usePreloader(); return (actor: string, rkey: string) => { return preload(['string-detail', actor, rkey], () => getString(actor, rkey)); }; }; export const useIssuePreloader = () => { const queryClient = useQueryClient(); const preload = usePreloader(); return ( repoLabel: string, kind: 'issue' | 'pull', uri: string, number?: string | number, repoContext?: RepoContext, ) => { const [owner, slug] = repoLabel.split('/'); const issueRef = String(number || uri.split('/').pop()); return preload( [kind, uri, 'prefetch'], async () => { const repo = repoContext || (await queryClient.fetchQuery({ queryKey: repoQueryKey(owner, slug), queryFn: () => getRepo(owner, slug), })); if (!repo) { return; } if (kind === 'issue') { await queryClient.prefetchQuery({ queryKey: issueQueryKey(repo.repoDid, issueRef), queryFn: () => getIssue(repo, issueRef), }); } else { const pullDetail = await queryClient.fetchQuery({ queryKey: pullQueryKey(repo.repoDid, issueRef), queryFn: () => getPull(repo, issueRef), }); const roundIndex = Math.max(0, pullDetail.pull.value.rounds.length - 1); await queryClient.prefetchQuery({ queryKey: pullPatchQueryKey(pullDetail.pull.uri, roundIndex), queryFn: () => fetchPullRoundPatch(pullDetail.pull, roundIndex), }); } }, ); }; };