diff --git a/webui2/src/components/code/FileTree.tsx b/webui2/src/components/code/FileTree.tsx index d4c05c2315e3827225358616366cc0743ff62531..d57091138a60f7597cda4299dedbe75554af1538 100644 --- a/webui2/src/components/code/FileTree.tsx +++ b/webui2/src/components/code/FileTree.tsx @@ -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 ; - return (
@@ -120,20 +116,3 @@ function FileTreeRow({ ); } - -function FileTreeSkeleton() { - return ( -
-
- {Array.from({ length: 8 }).map((_, i) => ( -
- - - - -
- ))} -
-
- ); -} diff --git a/webui2/src/components/code/FileViewer.tsx b/webui2/src/components/code/FileViewer.tsx index 4e5c23294382ff8914bd16b8d7183aa964d48a4b..4c6518990108edb08118830aa95d3a70ec766403 100644 --- a/webui2/src/components/code/FileViewer.tsx +++ b/webui2/src/components/code/FileViewer.tsx @@ -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 (
@@ -51,7 +50,7 @@ export function FileViewer({ blob, loading = false }: FileViewerProps) { }; }, [blob]); - if (loading || highlighted === null) return ; + if (highlighted === null) return ; const { html, lineCount } = highlighted; function copyToClipboard() { diff --git a/webui2/src/routes/$repo/_code/blob/$ref/$.tsx b/webui2/src/routes/$repo/_code/blob/$ref/$.tsx index 235199cb010f5c7234accd56baf9bf62b89a847d..33b10bb597e7505d012a47720a007e792a779db6 100644 --- a/webui2/src/routes/$repo/_code/blob/$ref/$.tsx +++ b/webui2/src/routes/$repo/_code/blob/$ref/$.tsx @@ -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 ( +
+
+ +
+
+ +
+
+ ); +} + 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(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(BLOB_QUERY, { - variables: { repo: repoRef, ref: currentRef, path: currentPath }, - skip: !currentPath, - }); + const { blobRef } = Route.useLoaderData(); + const { data } = useReadQuery(blobRef); - return ; + return ; } diff --git a/webui2/src/routes/$repo/_code/tree/$ref/$.tsx b/webui2/src/routes/$repo/_code/tree/$ref/$.tsx index 98c7818bb17015b38a2d83c42d2e4d83e4faff12..1a0bbf50c591d674784639b7945eb60b59c9fca1 100644 --- a/webui2/src/routes/$repo/_code/tree/$ref/$.tsx +++ b/webui2/src/routes/$repo/_code/tree/$ref/$.tsx @@ -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 ( +
+
+ {Array.from({ length: 8 }).map((_, i) => ( +
+ + + + +
+ ))} +
+
+ ); +} + 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(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(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(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(BLOB_QUERY, { + const { data: readmeBlobData } = useQuery(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 && (