Detailed changes
@@ -3,7 +3,6 @@ import { formatDistanceToNow } from "date-fns";
import { Folder, File } from "lucide-react";
import { GitObjectType, type GitTreeEntry } from "@/__generated__/graphql";
-import { Skeleton } from "@/components/ui/skeleton";
export interface TreeEntryWithCommit extends GitTreeEntry {
lastCommit?: {
@@ -19,20 +18,17 @@ interface FileTreeProps {
currentRef: string;
currentPath: string;
entries: TreeEntryWithCommit[];
- loading?: boolean;
}
// Directory listing table for the code browser. Shows each entry's icon,
// name, last-commit message (linked to commit detail), and relative date.
-export function FileTree({ repo, currentRef, currentPath, entries, loading }: FileTreeProps) {
+export function FileTree({ repo, currentRef, currentPath, entries }: FileTreeProps) {
// Directories first, then files — each group alphabetical
const sorted = entries.toSorted((a, b) => {
if (a.type !== b.type) return a.type === GitObjectType.Tree ? -1 : 1;
return a.name.localeCompare(b.name);
});
- if (loading) return <FileTreeSkeleton />;
-
return (
<div className="border-border overflow-hidden rounded-md border">
<table className="w-full text-sm">
@@ -120,20 +116,3 @@ function FileTreeRow({
</tr>
);
}
-
-function FileTreeSkeleton() {
- return (
- <div className="border-border overflow-hidden rounded-md border">
- <div className="divide-border divide-y">
- {Array.from({ length: 8 }).map((_, i) => (
- <div key={i} className="flex items-center gap-3 px-4 py-2">
- <Skeleton className="size-4 rounded-sm" />
- <Skeleton className="h-4 w-32" />
- <Skeleton className="ml-6 hidden h-4 w-64 md:block" />
- <Skeleton className="ml-auto hidden h-4 w-20 md:block" />
- </div>
- ))}
- </div>
- </div>
- );
-}
@@ -10,11 +10,10 @@ import { Skeleton } from "@/components/ui/skeleton";
interface FileViewerProps {
blob: GitBlob | null;
- loading?: boolean;
}
-export function FileViewer({ blob, loading = false }: FileViewerProps) {
- if (loading || !blob) {
+export function FileViewer({ blob }: FileViewerProps) {
+ if (!blob) {
return (
<div className="divide-border border-border divide-y rounded-md border">
<div className="flex items-center gap-2 px-4 py-2">
@@ -51,7 +50,7 @@ export function FileViewer({ blob, loading = false }: FileViewerProps) {
};
}, [blob]);
- if (loading || highlighted === null) return <FileViewerSkeleton />;
+ if (highlighted === null) return <FileViewerSkeleton />;
const { html, lineCount } = highlighted;
function copyToClipboard() {
@@ -1,11 +1,12 @@
// Blob (file) view: /$repo/blob/$ref/...path
import { gql } from "@apollo/client";
-import { useQuery } from "@apollo/client/react";
+import { useReadQuery } from "@apollo/client/react";
import { createFileRoute } from "@tanstack/react-router";
import type { GitBlob } from "@/__generated__/graphql";
import { FileViewer } from "@/components/code/FileViewer";
+import { Skeleton } from "@/components/ui/skeleton";
const BLOB_QUERY = gql`
query CodePageBlob($repo: String, $ref: String!, $path: String!) {
@@ -26,19 +27,34 @@ interface BlobQueryData {
repository: { blob: GitBlob | null } | null;
}
+function BlobSkeleton() {
+ return (
+ <div className="border-border overflow-hidden rounded-md border">
+ <div className="flex items-center gap-2 border-b px-4 py-2">
+ <Skeleton className="h-4 w-48" />
+ </div>
+ <div className="p-4">
+ <Skeleton className="h-64 w-full" />
+ </div>
+ </div>
+ );
+}
+
export const Route = createFileRoute("/$repo/_code/blob/$ref/$")({
component: BlobView,
+ pendingComponent: BlobSkeleton,
beforeLoad: () => ({ viewMode: "blob" as const }),
+ loader: async ({ context: { preloadQuery, ref }, params: { ref: gitRef, _splat: path } }) => {
+ const blobRef = preloadQuery<BlobQueryData>(BLOB_QUERY, {
+ variables: { repo: ref, ref: gitRef, path: path || "" },
+ });
+ return { blobRef: await preloadQuery.toPromise(blobRef) };
+ },
});
function BlobView() {
- const { ref: currentRef, _splat: currentPath = "" } = Route.useParams();
- const { ref: repoRef } = Route.useRouteContext();
-
- const { data, loading } = useQuery<BlobQueryData>(BLOB_QUERY, {
- variables: { repo: repoRef, ref: currentRef, path: currentPath },
- skip: !currentPath,
- });
+ const { blobRef } = Route.useLoaderData();
+ const { data } = useReadQuery(blobRef);
- return <FileViewer blob={data?.repository?.blob ?? null} loading={loading} />;
+ return <FileViewer blob={data?.repository?.blob ?? null} />;
}
@@ -1,7 +1,7 @@
// Tree view: /$repo/tree/$ref/...path
import { gql } from "@apollo/client";
-import { useQuery } from "@apollo/client/react";
+import { useQuery, useReadQuery } from "@apollo/client/react";
import { createFileRoute } from "@tanstack/react-router";
import {
@@ -13,6 +13,7 @@ import {
import { FileTree } from "@/components/code/FileTree";
import type { TreeEntryWithCommit } from "@/components/code/FileTree";
import { Markdown } from "@/components/content/Markdown";
+import { Skeleton } from "@/components/ui/skeleton";
const TREE_QUERY = gql`
query CodePageTree($repo: String, $ref: String!, $path: String) {
@@ -42,7 +43,7 @@ const LAST_COMMITS_QUERY = gql`
}
`;
-const BLOB_QUERY = gql`
+const README_QUERY = gql`
query CodePageReadme($repo: String, $ref: String!, $path: String!) {
repository(ref: $repo) {
blob(ref: $ref, path: $path) {
@@ -62,20 +63,43 @@ interface ReadmeQueryData {
repository: { blob: GitBlob | null } | null;
}
+function TreeSkeleton() {
+ return (
+ <div className="border-border overflow-hidden rounded-md border">
+ <div className="divide-border divide-y">
+ {Array.from({ length: 8 }).map((_, i) => (
+ <div key={i} className="flex items-center gap-3 px-4 py-2">
+ <Skeleton className="size-4 rounded-sm" />
+ <Skeleton className="h-4 w-32" />
+ <Skeleton className="ml-6 hidden h-4 w-64 md:block" />
+ <Skeleton className="ml-auto hidden h-4 w-20 md:block" />
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+}
+
export const Route = createFileRoute("/$repo/_code/tree/$ref/$")({
component: TreeView,
+ pendingComponent: TreeSkeleton,
beforeLoad: () => ({ viewMode: "tree" as const }),
+ loader: async ({ context: { preloadQuery, ref }, params: { ref: gitRef, _splat: path } }) => {
+ const treeRef = preloadQuery<TreeQueryData>(TREE_QUERY, {
+ variables: { repo: ref, ref: gitRef, path: path || null },
+ });
+ return { treeRef: await preloadQuery.toPromise(treeRef) };
+ },
});
function TreeView() {
const { repo, ref: currentRef, _splat: currentPath = "" } = Route.useParams();
const { ref: repoRef } = Route.useRouteContext();
-
- const { data: treeData, loading: treeLoading } = useQuery<TreeQueryData>(TREE_QUERY, {
- variables: { repo: repoRef, ref: currentRef, path: currentPath || null },
- });
+ const { treeRef } = Route.useLoaderData();
+ const { data: treeData } = useReadQuery(treeRef);
const entries: GitTreeEntry[] = treeData?.repository?.tree ?? [];
+ // Last commits and readme are cascading queries — they depend on the tree result
const entryNames = entries.map((e) => e.name);
const { data: lastCommitsData } = useQuery<LastCommitsQueryData>(LAST_COMMITS_QUERY, {
variables: { repo: repoRef, ref: currentRef, path: currentPath || null, names: entryNames },
@@ -97,7 +121,7 @@ function TreeView() {
? `${currentPath}/${readmeEntry.name}`
: readmeEntry.name
: null;
- const { data: readmeBlobData } = useQuery<ReadmeQueryData>(BLOB_QUERY, {
+ const { data: readmeBlobData } = useQuery<ReadmeQueryData>(README_QUERY, {
variables: { repo: repoRef, ref: currentRef, path: readmePath },
skip: !readmePath,
});
@@ -110,7 +134,6 @@ function TreeView() {
currentRef={currentRef}
currentPath={currentPath}
entries={entriesWithCommits}
- loading={treeLoading}
/>
{readme && (
<div className="rounded-md border">