improve repo prefetching
3872 unmodified linesExpand all3873387438753876387738783872 unmodified linesExpand all }}
252 unmodified linesExpand all253254255256257258259260261262263264265266267268269270271272273274275276252 unmodified linesExpand all 'untangled-input w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-gray-100';
export const textareaStyles = (): string => 'untangled-textarea w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 px-3 py-2 text-sm text-gray-900 dark:text-gray-100';
export const usePreloader = () => { const queryClient = useQueryClient();
return (queryKey: readonly unknown[], queryFn: () => Promise<any>, delayMs = 30) => { let hoverTimer: ReturnType<typeof setTimeout> | undefined;
const onMouseEnter = () => { hoverTimer = setTimeout(() => { queryClient.prefetchQuery({ queryKey, queryFn }); }, delayMs); };
const onMouseLeave = () => { clearTimeout(hoverTimer); };
return { onMouseEnter, onMouseLeave }; };};30 unmodified lines313233343536353637383940414270 unmodified lines1131141151161161171181191201217 unmodified lines1291301311321321331341351361371097 unmodified linesExpand all123512361237123812381239124012411242124315 unmodified lines1259126012611262126212631264126512661267486 unmodified linesExpand all30 unmodified linesimport { getRepoIssueCount } from '../lib/api/issues';import { getRepoPullCount } from '../lib/api/pulls';import { formatRelativeTime, languageColor, encodePath, formatLanguagePercent, blobHref, treeHref } from '../lib/repo-utils';import { getTangledAppviewService } from '../lib/settings';import { Avatar, SkeletonBlock, StateBadge, PlaceholderAvatar, buttonStyles, cardStyles, usePreloader } from './common';import { repoQueryKey } from '../pages/repo/shared';import { Avatar, SkeletonBlock, StateBadge, PlaceholderAvatar, buttonStyles, cardStyles } from './common';import { useRepoPreloader } from '../lib/preloading';
marked.setOptions({ gfm: true, breaks: false,});70 unmodified lines </div> </div>);
const TreeRowsSkeleton: Component = () => (export const TreeRowsSkeleton: Component = () => ( <div class="space-y-3"> <For each={skeletonRows}> {(_, index) => ( <div class="untangled-file-row">7 unmodified lines </For> </div>);
const RepoOverviewSidebarSkeleton: Component = () => (export const RepoOverviewSidebarSkeleton: Component = () => ( <div class="min-w-0 md:pt-0 md:pl-6 border-gray-100 dark:border-gray-700 md:border-t-0 md:border-l"> <div class="flex items-center gap-2 pb-3 font-bold"> <SkeletonBlock class="size-4" /> <SkeletonBlock class="h-5 w-20" />1097 unmodified linesExpand all};
export const RepoCard: Component<RepoCardProps> = (props) => { const linkHref = () => props.href || `/${props.owner}/${props.name}`; const preload = usePreloader(); const preloadRepo = useRepoPreloader();
return ( <div class={clsx(15 unmodified lines class={clsx( 'truncate min-w-0 no-underline hover:underline text-black dark:text-white font-bold', props.compact && 'focus:outline-none' )} {...preload(repoQueryKey(props.owner, props.name), () => getRepo(props.owner, props.name))} {...preloadRepo(props.owner, props.name)} > <Show when={props.showOwner !== false} fallback={props.name}> {props.owner}/{props.name} </Show>486 unmodified linesExpand all1234562345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914010 unmodified lines1511521531542615515615715815926 unmodified linesimport { useQueryClient } from '@tanstack/solid-query';import { usePreloader } from '../components/common';import { getRepo, type RepoContext } from './api/repos';import { getIssue } from './api/issues';import { fetchPullRoundPatch, getPull } from './api/pulls';import { repoQueryKey, issueQueryKey, pullQueryKey, pullPatchQueryKey } from './api/keys';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<any>, delayMs = PRELOAD_HOVER_TIME) => { let hoverTimer: ReturnType<typeof setTimeout> | 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();
10 unmodified lines return preload( [kind, uri, 'prefetch'], async () => { const repo = repoContext || repoContext || (await queryClient.fetchQuery({ queryKey: repoQueryKey(owner, slug), queryFn: () => getRepo(owner, slug), }));26 unmodified lines15 unmodified lines161718192020212223242425262727282929303132712 unmodified linesExpand all74574674774875074975075175275312 unmodified lines76676776876977177077177277377467 unmodified lines15 unmodified linesimport { A } from '@solidjs/router';import { createQuery } from '@tanstack/solid-query';import { For, Show, createEffect, createMemo, createSignal, onCleanup, type Component, type JSX } from 'solid-js';
import { Avatar, ErrorState, LoadingState, StateBadge, usePreloader } from '../components/common';import { Avatar, ErrorState, LoadingState, StateBadge } from '../components/common';import { RepoCard } from '../components/repo';import { listFollowRecords } from '../lib/api/graph';import { resolveActor } from '../lib/api/identity';import { getRepo, listRepoRecords, type RepoRecord } from '../lib/api/repos';import { listRepoRecords, type RepoRecord } from '../lib/api/repos';import { useAuth } from '../lib/auth';import { useLiveEvents, type LiveEvent } from '../lib/live-events';import { useIssuePreloader } from '../lib/preloading';import { useIssuePreloader, useRepoPreloader } from '../lib/preloading';import { formatRelativeTime, getErrorMessage, loadRecentRepos, loadRecentIssuesPulls } from '../lib/repo-utils';import { repoQueryKey } from '../lib/api/keys';
const SAMPLE_REPOS: Array<{ owner: string; slug: string;712 unmodified linesExpand all
const ActivityRow: Component<{ item: HomeActivityItem; index: number }> = (props) => { const repoUrl = () => `/${props.item.repoLabel}`; const actorUrl = () => (props.item.actorHandle ? `/${props.item.actorHandle}` : '#'); const preload = usePreloader(); const preloadRepo = useRepoPreloader(); const issuePreloader = useIssuePreloader();
const actionText = () => { if (props.item.kind === 'repo') return 'published repo';12 unmodified lines };
const preloadRepoProps = () => { const [owner, slug] = props.item.repoLabel.split('/'); return preload(repoQueryKey(owner, slug), () => getRepo(owner, slug)); return preloadRepo(owner, slug); };
return ( <div class="relative">67 unmodified lines21 unmodified lines2223242526262728293031327 unmodified lines40414243434445464748923 unmodified linesExpand all972973974975975976977978979980574 unmodified linesExpand all1555155615571558155815591560156115621563103 unmodified linesExpand all21 unmodified linesimport { A, useParams, useSearchParams, useNavigate } from '@solidjs/router';import { createQuery, useQueryClient } from '@tanstack/solid-query';import { For, Show, Switch, Match, createMemo, createSignal, createEffect, type Component } from 'solid-js';import type { Did } from '@atcute/lexicons/syntax';import { ErrorState, LoadingState, PlaceholderAvatar, SkeletonBlock, textareaStyles, inputStyles, usePreloader } from '../components/common';import { ErrorState, LoadingState, PlaceholderAvatar, SkeletonBlock, textareaStyles, inputStyles } from '../components/common';import { RepoCard, RepoCardSkeleton, StringCardSkeleton } from '../components/repo';import { useStringPreloader } from '../lib/preloading';import { listFollowRecords, createFollow, deleteFollow,7 unmodified lines type VouchRecord,} from '../lib/api/graph';import { resolveActor, getActorProfile, resolveAvatarUrl, putActorProfile } from '../lib/api/identity';import { listRepoRecords, getRepoByDid, type RepoContext } from '../lib/api/repos';import { listStringRecords, getString } from '../lib/api/strings';import { listStringRecords } from '../lib/api/strings';import { useAuth } from '../lib/auth';import { listAllAppviewRecords } from '../lib/api/appview';import { formatRelativeTime, getErrorMessage } from '../lib/repo-utils';import { ShTangledFeedStar, ShTangledActorProfile } from '@atcute/tangled';923 unmodified linesExpand all const params = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const queryClient = useQueryClient(); const auth = useAuth(); const preload = usePreloader(); const preloadString = useStringPreloader(); const navigate = useNavigate(); const [repoSearch, setRepoSearch] = createSignal('');574 unmodified linesExpand all <div class="font-medium dark:text-white flex gap-2 items-center"> <A href={`/strings/${resolvedActor().handle || resolvedActor().did}/${item.rkey}`} class="hover:underline" {...preload(['string-detail', resolvedActor().handle || resolvedActor().did, item.rkey], () => getString(resolvedActor().handle || resolvedActor().did, item.rkey))} {...preloadString(resolvedActor().handle || resolvedActor().did, item.rkey)} > {item.value.filename} </A> </div> {item.value.description && (103 unmodified linesExpand all15 unmodified lines16171819202021222324242526252627282930287 unmodified linesExpand all31831932032132232232332432532632715 unmodified lines34334434534634634734834935035115 unmodified lines367368369370370371372373374375281 unmodified linesExpand all15 unmodified linesimport { createQuery } from '@tanstack/solid-query';import type { Did, ResourceUri } from '@atcute/lexicons/syntax';import { For, Match, Show, Switch, createEffect, createMemo, createSignal, type Component, type JSX } from 'solid-js';
import { ErrorState, LoadingState, PaginationControls, usePreloader } from '../components/common';import { ErrorState, LoadingState, PaginationControls } from '../components/common';import { usePreloader, useRepoPreloader, useStringPreloader } from '../lib/preloading';import { RepoStatsList } from '../components/repo';import { getIssueRecord, getIssue } from '../lib/api/issues';import { getPullRecord, getPull } from '../lib/api/pulls';import { getRepoByDid, getRepo } from '../lib/api/repos';import { getString } from '../lib/api/strings';import { issueQueryKey, pullQueryKey, repoQueryKey } from './repo/shared';import { getRepoByDid } from '../lib/api/repos';import { issueQueryKey, pullQueryKey } from './repo/shared';import { ISSUE_COLLECTION,} from '../lib/api/constants';import {287 unmodified linesExpand all );};
const ResultTitle: Component<{ hit: SearchHit }> = (props) => { const preload = usePreloader(); const preloadRepo = useRepoPreloader(); const preloadString = useStringPreloader();
const title = createMemo(() => { const value = props.hit.value as { title?: string;15 unmodified lines <Match when={props.hit.nsid === 'sh.tangled.repo'}> <A href={`/${props.hit.author.handle}/${repoName()}`} class="truncate min-w-0" {...preload(repoQueryKey(props.hit.author.handle, repoName()), () => getRepo(props.hit.author.handle, repoName()))} {...preloadRepo(props.hit.author.handle, repoName())} > {props.hit.author.handle}/{repoName()} </A> </Match>15 unmodified lines <Match when={props.hit.nsid === 'sh.tangled.string'}> <A href={`/strings/${props.hit.author.handle || props.hit.author.did}/${props.hit.rkey}`} class="truncate min-w-0" {...preload(['string-detail', props.hit.author.handle || props.hit.author.did, props.hit.rkey], () => getString(props.hit.author.handle || props.hit.author.did, props.hit.rkey))} {...preloadString(props.hit.author.handle || props.hit.author.did, props.hit.rkey)} > {props.hit.author.handle || props.hit.author.did}/{title()} </A> </Match>281 unmodified linesExpand all