Detailed changes
@@ -29,11 +29,12 @@ type Documents = {
"\n mutation BugSetTitle($input: BugSetTitleInput!) {\n bugSetTitle(input: $input) {\n bug {\n id\n title\n }\n }\n }\n": typeof types.BugSetTitleDocument,
"\n query CommitList($repo: String, $ref: String!, $path: String, $after: String, $first: Int) {\n repository(ref: $repo) {\n commits(ref: $ref, path: $path, after: $after, first: $first) {\n nodes {\n hash\n shortHash\n message\n authorName\n date\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n": typeof types.CommitListDocument,
"\n query FileDiff($repo: String, $hash: String!, $path: String!) {\n repository(ref: $repo) {\n commit(hash: $hash) {\n diff(path: $path) {\n path\n oldPath\n isBinary\n isNew\n isDelete\n hunks {\n oldStart\n oldLines\n newStart\n newLines\n lines {\n type\n content\n oldLine\n newLine\n }\n }\n }\n }\n }\n }\n": typeof types.FileDiffDocument,
+ "\n fragment RefSelectorRefs on GitRefConnection {\n nodes {\n name\n shortName\n type\n }\n }\n": typeof types.RefSelectorRefsFragmentDoc,
"\n fragment IdentitySummary on Identity {\n id\n humanId\n displayName\n avatarUrl\n }\n": typeof types.IdentitySummaryFragmentDoc,
"\n fragment BugSummary on Bug {\n id\n humanId\n status\n title\n labels {\n name\n ...LabelFields\n }\n author {\n ...IdentitySummary\n }\n createdAt\n comments {\n totalCount\n }\n }\n": typeof types.BugSummaryFragmentDoc,
"\n fragment LabelFields on Label {\n name\n color {\n R\n G\n B\n }\n }\n": typeof types.LabelFieldsFragmentDoc,
"\n query UserIdentity {\n repository {\n userIdentity {\n id\n humanId\n name\n displayName\n avatarUrl\n email\n login\n }\n }\n }\n": typeof types.UserIdentityDocument,
- "\n query CodePageRefs($repo: String) {\n repository(ref: $repo) {\n name\n head {\n shortName\n }\n refs {\n nodes {\n name\n shortName\n type\n hash\n }\n }\n }\n }\n": typeof types.CodePageRefsDocument,
+ "\n query CodePageRefs($repo: String) {\n repository(ref: $repo) {\n name\n head {\n shortName\n }\n refs {\n ...RefSelectorRefs\n }\n }\n }\n": typeof types.CodePageRefsDocument,
"\n query CodePageBlob($repo: String, $ref: String!, $path: String!) {\n repository(ref: $repo) {\n blob(ref: $ref, path: $path) {\n path\n hash\n text\n size\n isBinary\n isTruncated\n }\n }\n }\n": typeof types.CodePageBlobDocument,
"\n query CodePageTree($repo: String, $ref: String!, $path: String) {\n repository(ref: $repo) {\n tree(ref: $ref, path: $path) {\n name\n type\n hash\n }\n }\n }\n": typeof types.CodePageTreeDocument,
"\n query CodePageLastCommits(\n $repo: String\n $ref: String!\n $path: String\n $names: [String!]!\n ) {\n repository(ref: $repo) {\n lastCommits(ref: $ref, path: $path, names: $names) {\n name\n commit {\n hash\n shortHash\n message\n date\n }\n }\n }\n }\n": typeof types.CodePageLastCommitsDocument,
@@ -63,11 +64,12 @@ const documents: Documents = {
"\n mutation BugSetTitle($input: BugSetTitleInput!) {\n bugSetTitle(input: $input) {\n bug {\n id\n title\n }\n }\n }\n": types.BugSetTitleDocument,
"\n query CommitList($repo: String, $ref: String!, $path: String, $after: String, $first: Int) {\n repository(ref: $repo) {\n commits(ref: $ref, path: $path, after: $after, first: $first) {\n nodes {\n hash\n shortHash\n message\n authorName\n date\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n": types.CommitListDocument,
"\n query FileDiff($repo: String, $hash: String!, $path: String!) {\n repository(ref: $repo) {\n commit(hash: $hash) {\n diff(path: $path) {\n path\n oldPath\n isBinary\n isNew\n isDelete\n hunks {\n oldStart\n oldLines\n newStart\n newLines\n lines {\n type\n content\n oldLine\n newLine\n }\n }\n }\n }\n }\n }\n": types.FileDiffDocument,
+ "\n fragment RefSelectorRefs on GitRefConnection {\n nodes {\n name\n shortName\n type\n }\n }\n": types.RefSelectorRefsFragmentDoc,
"\n fragment IdentitySummary on Identity {\n id\n humanId\n displayName\n avatarUrl\n }\n": types.IdentitySummaryFragmentDoc,
"\n fragment BugSummary on Bug {\n id\n humanId\n status\n title\n labels {\n name\n ...LabelFields\n }\n author {\n ...IdentitySummary\n }\n createdAt\n comments {\n totalCount\n }\n }\n": types.BugSummaryFragmentDoc,
"\n fragment LabelFields on Label {\n name\n color {\n R\n G\n B\n }\n }\n": types.LabelFieldsFragmentDoc,
"\n query UserIdentity {\n repository {\n userIdentity {\n id\n humanId\n name\n displayName\n avatarUrl\n email\n login\n }\n }\n }\n": types.UserIdentityDocument,
- "\n query CodePageRefs($repo: String) {\n repository(ref: $repo) {\n name\n head {\n shortName\n }\n refs {\n nodes {\n name\n shortName\n type\n hash\n }\n }\n }\n }\n": types.CodePageRefsDocument,
+ "\n query CodePageRefs($repo: String) {\n repository(ref: $repo) {\n name\n head {\n shortName\n }\n refs {\n ...RefSelectorRefs\n }\n }\n }\n": types.CodePageRefsDocument,
"\n query CodePageBlob($repo: String, $ref: String!, $path: String!) {\n repository(ref: $repo) {\n blob(ref: $ref, path: $path) {\n path\n hash\n text\n size\n isBinary\n isTruncated\n }\n }\n }\n": types.CodePageBlobDocument,
"\n query CodePageTree($repo: String, $ref: String!, $path: String) {\n repository(ref: $repo) {\n tree(ref: $ref, path: $path) {\n name\n type\n hash\n }\n }\n }\n": types.CodePageTreeDocument,
"\n query CodePageLastCommits(\n $repo: String\n $ref: String!\n $path: String\n $names: [String!]!\n ) {\n repository(ref: $repo) {\n lastCommits(ref: $ref, path: $path, names: $names) {\n name\n commit {\n hash\n shortHash\n message\n date\n }\n }\n }\n }\n": types.CodePageLastCommitsDocument,
@@ -156,6 +158,10 @@ export function graphql(source: "\n query CommitList($repo: String, $ref: Strin
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -1245,6 +1245,8 @@ export type FileDiffQueryVariables = Exact<{
export type FileDiffQuery = { repository: { __typename: 'Repository', commit: { __typename: 'GitCommit', diff: { __typename: 'GitFileDiff', path: string, oldPath: string | null, isBinary: boolean, isNew: boolean, isDelete: boolean, hunks: Array<{ __typename: 'GitDiffHunk', oldStart: number, oldLines: number, newStart: number, newLines: number, lines: Array<{ __typename: 'GitDiffLine', type: GitDiffLineType, content: string, oldLine: number, newLine: number }> }> } | null } | null } | null };
+export type RefSelectorRefsFragment = { __typename: 'GitRefConnection', nodes: Array<{ __typename: 'GitRef', name: string, shortName: string, type: GitRefType }> };
+
export type IdentitySummaryFragment = { __typename: 'Identity', id: string, humanId: string, displayName: string, avatarUrl: string | null };
export type BugSummaryFragment = { __typename: 'Bug', id: string, humanId: string, status: Status, title: string, createdAt: string, labels: Array<{ __typename: 'Label', name: string, color: { __typename: 'Color', R: number, G: number, B: number } }>, author: { __typename: 'Identity', id: string, humanId: string, displayName: string, avatarUrl: string | null }, comments: { __typename: 'BugCommentConnection', totalCount: number } };
@@ -1261,7 +1263,7 @@ export type CodePageRefsQueryVariables = Exact<{
}>;
-export type CodePageRefsQuery = { repository: { __typename: 'Repository', name: string | null, head: { __typename: 'GitRef', shortName: string } | null, refs: { __typename: 'GitRefConnection', nodes: Array<{ __typename: 'GitRef', name: string, shortName: string, type: GitRefType, hash: string }> } } | null };
+export type CodePageRefsQuery = { repository: { __typename: 'Repository', name: string | null, head: { __typename: 'GitRef', shortName: string } | null, refs: { __typename: 'GitRefConnection', nodes: Array<{ __typename: 'GitRef', name: string, shortName: string, type: GitRefType }> } } | null };
export type CodePageBlobQueryVariables = Exact<{
repo?: InputMaybe<Scalars['String']['input']>;
@@ -1379,6 +1381,7 @@ export const LabelFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":
@@ -1,3 +1,5 @@
+import { useSuspenseFragment } from "@apollo/client/react";
+import type { FragmentType } from "@apollo/client/masking";
import {
useFloating,
useClick,
@@ -14,20 +16,33 @@ import { GitBranch, Tag } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { GitRefType } from "@/__generated__/graphql";
-import type { RefsQueryRef } from "@/routes/$repo";
+import { graphql } from "@/__generated__/gql";
import { Button } from "@/components/ui/button";
import * as Listbox from "@/components/ui/listbox";
import { cn } from "@/lib/utils";
+export const REF_SELECTOR_REFS_FRAGMENT = graphql(`
+ fragment RefSelectorRefs on GitRefConnection {
+ nodes {
+ name
+ shortName
+ type
+ }
+ }
+`);
+
interface RefSelectorProps {
- gitRefs: RefsQueryRef[];
+ refs: FragmentType<typeof REF_SELECTOR_REFS_FRAGMENT>;
currentRef: string;
- onSelect: (ref: RefsQueryRef) => void;
+ onSelect: (shortName: string) => void;
}
// Branch / tag selector dropdown for the code browser. Shown in two groups
// (branches, tags) with an inline search filter.
-export function RefSelector({ gitRefs, currentRef, onSelect }: RefSelectorProps) {
+export function RefSelector({ refs: refsProp, currentRef, onSelect }: RefSelectorProps) {
+ const { data } = useSuspenseFragment({ fragment: REF_SELECTOR_REFS_FRAGMENT, from: refsProp });
+ const gitRefs = data.nodes;
+
const [open, setOpen] = useState(false);
const [filter, setFilter] = useState("");
const [activeIndex, setActiveIndex] = useState<number | null>(null);
@@ -84,7 +99,7 @@ export function RefSelector({ gitRefs, currentRef, onSelect }: RefSelectorProps)
e.preventDefault();
const ref = flatItems[activeIndex];
if (ref) {
- onSelect(ref);
+ onSelect(ref.shortName);
setOpen(false);
setFilter("");
}
@@ -136,21 +151,26 @@ export function RefSelector({ gitRefs, currentRef, onSelect }: RefSelectorProps)
{branches.map((ref) => {
const i = itemIndex++;
return (
- <RefItem
+ <Listbox.Item
key={ref.name}
id={`ref-option-${i}`}
- ref_={ref}
- index={i}
+ ref={(el) => {
+ elementsRef.current[i] = el;
+ }}
active={activeIndex === i}
selected={ref.shortName === currentRef}
- elementsRef={elementsRef}
- getItemProps={getItemProps}
- onSelect={() => {
- onSelect(ref);
- setOpen(false);
- setFilter("");
- }}
- />
+ className={cn("text-xs", ref.shortName === currentRef && "font-medium")}
+ {...getItemProps({
+ onClick: () => {
+ onSelect(ref.shortName);
+ setOpen(false);
+ setFilter("");
+ },
+ })}
+ >
+ <GitBranch className="text-muted-foreground size-3 shrink-0" />
+ <span className="flex-1 truncate font-mono">{ref.shortName}</span>
+ </Listbox.Item>
);
})}
</Listbox.Group>
@@ -161,21 +181,26 @@ export function RefSelector({ gitRefs, currentRef, onSelect }: RefSelectorProps)
{tags.map((ref) => {
const i = itemIndex++;
return (
- <RefItem
+ <Listbox.Item
key={ref.name}
id={`ref-option-${i}`}
- ref_={ref}
- index={i}
+ ref={(el) => {
+ elementsRef.current[i] = el;
+ }}
active={activeIndex === i}
selected={ref.shortName === currentRef}
- elementsRef={elementsRef}
- getItemProps={getItemProps}
- onSelect={() => {
- onSelect(ref);
- setOpen(false);
- setFilter("");
- }}
- />
+ className={cn("text-xs", ref.shortName === currentRef && "font-medium")}
+ {...getItemProps({
+ onClick: () => {
+ onSelect(ref.shortName);
+ setOpen(false);
+ setFilter("");
+ },
+ })}
+ >
+ <Tag className="text-muted-foreground size-3 shrink-0" />
+ <span className="flex-1 truncate font-mono">{ref.shortName}</span>
+ </Listbox.Item>
);
})}
</Listbox.Group>
@@ -189,43 +214,3 @@ export function RefSelector({ gitRefs, currentRef, onSelect }: RefSelectorProps)
</>
);
}
-
-function RefItem({
- id,
- ref_,
- index,
- active,
- selected,
- elementsRef,
- getItemProps,
- onSelect,
-}: {
- id: string;
- ref_: RefsQueryRef;
- index: number;
- active: boolean;
- selected: boolean;
- elementsRef: React.MutableRefObject<(HTMLElement | null)[]>;
- getItemProps: (props?: Record<string, unknown>) => Record<string, unknown>;
- onSelect: () => void;
-}) {
- return (
- <Listbox.Item
- id={id}
- ref={(el) => {
- elementsRef.current[index] = el;
- }}
- active={active}
- selected={selected}
- className={cn("text-xs", selected && "font-medium")}
- {...getItemProps({ onClick: onSelect })}
- >
- {ref_.type === GitRefType.Branch ? (
- <GitBranch className="text-muted-foreground size-3 shrink-0" />
- ) : (
- <Tag className="text-muted-foreground size-3 shrink-0" />
- )}
- <span className="flex-1 truncate font-mono">{ref_.shortName}</span>
- </Listbox.Item>
- );
-}
@@ -11,12 +11,7 @@ export const REFS_QUERY = graphql(`
shortName
}
refs {
- nodes {
- name
- shortName
- type
- hash
- }
+ ...RefSelectorRefs
}
}
}
@@ -24,11 +19,6 @@ export const REFS_QUERY = graphql(`
export type RefsQueryData = ResultOf<typeof REFS_QUERY>;
-/** A single git ref as returned by the REFS_QUERY. */
-export type RefsQueryRef = NonNullable<
- NonNullable<RefsQueryData["repository"]>["refs"]
->["nodes"][number];
-
export const Route = createFileRoute("/$repo")({
beforeLoad: ({ params: { repo }, context: { preloadQuery } }) => {
// Normalize the repo slug: "_" means the default (null) repo
@@ -12,7 +12,6 @@ import {
} from "@tanstack/react-router";
import { GitCommit } from "lucide-react";
-import type { RefsQueryRef } from "@/routes/$repo";
import { CodeBreadcrumb } from "@/components/code/code-breadcrumb";
import { RefSelector } from "@/components/code/ref-selector";
import { ButtonLink } from "@/components/ui/button-link";
@@ -29,7 +28,7 @@ function CodeLayout() {
const { repo } = Route.useParams();
const { ref: repoRef, refsRef } = Route.useRouteContext();
const { data: refsData } = useReadQuery(refsRef);
- const refs: RefsQueryRef[] = refsData?.repository?.refs?.nodes ?? [];
+ const refs = refsData?.repository?.refs;
const repoName = refsData?.repository?.name ?? repoRef ?? "default-repo";
// Read child route params (ref and splat path)
@@ -51,8 +50,7 @@ function CodeLayout() {
const navigate = useNavigate();
- function handleRefSelect(newRef: RefsQueryRef) {
- const refName = newRef.shortName;
+ function handleRefSelect(refName: string) {
if (viewMode === "commits") {
void navigate({ to: "/$repo/commits/$ref", params: { repo, ref: refName }, search: { path: currentPath || undefined } });
} else if (viewMode === "blob") {
@@ -100,7 +98,7 @@ function CodeLayout() {
History
</ButtonLink>
)}
- <RefSelector gitRefs={refs} currentRef={currentRef} onSelect={handleRefSelect} />
+ {refs && <RefSelector refs={refs} currentRef={currentRef} onSelect={handleRefSelect} />}
</div>
</div>