Skip to content

Commit f7ded19

Browse files
move file source & tree preview data fetching to server
1 parent ebf6721 commit f7ded19

File tree

6 files changed

+190
-177
lines changed

6 files changed

+190
-177
lines changed

packages/web/src/app/[domain]/browse/[...path]/components/codePreviewPanel.tsx

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
'use client';
2-
3-
import { getCodeHostInfoForRepo, unwrapServiceError } from "@/lib/utils";
1+
import { getCodeHostInfoForRepo, isServiceError, unwrapServiceError } from "@/lib/utils";
42
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
53
import { useQuery } from "@tanstack/react-query";
64
import { getFileSource } from "@/features/search/fileSourceApi";
@@ -10,54 +8,36 @@ import { Separator } from "@/components/ui/separator";
108
import { getRepoInfoByName } from "@/actions";
119
import { cn } from "@/lib/utils";
1210
import Image from "next/image";
13-
import { useMemo } from "react";
1411
import { PureCodePreviewPanel } from "./pureCodePreviewPanel";
1512
import { PathHeader } from "@/app/[domain]/components/pathHeader";
1613

17-
export const CodePreviewPanel = () => {
18-
const { path, repoName, revisionName } = useBrowseParams();
19-
const domain = useDomain();
20-
21-
const { data: fileSourceResponse, isPending: isFileSourcePending, isError: isFileSourceError } = useQuery({
22-
queryKey: ['fileSource', repoName, revisionName, path, domain],
23-
queryFn: () => unwrapServiceError(getFileSource({
24-
fileName: path,
25-
repository: repoName,
26-
branch: revisionName
27-
}, domain)),
28-
});
29-
30-
const { data: repoInfoResponse, isPending: isRepoInfoPending, isError: isRepoInfoError } = useQuery({
31-
queryKey: ['repoInfo', repoName, domain],
32-
queryFn: () => unwrapServiceError(getRepoInfoByName(repoName, domain)),
33-
});
34-
35-
const codeHostInfo = useMemo(() => {
36-
if (!repoInfoResponse) {
37-
return undefined;
38-
}
14+
interface CodePreviewPanelProps {
15+
path: string;
16+
repoName: string;
17+
revisionName?: string;
18+
domain: string;
19+
}
3920

40-
return getCodeHostInfoForRepo({
41-
codeHostType: repoInfoResponse.codeHostType,
42-
name: repoInfoResponse.name,
43-
displayName: repoInfoResponse.displayName,
44-
webUrl: repoInfoResponse.webUrl,
45-
});
46-
}, [repoInfoResponse]);
21+
export const CodePreviewPanel = async ({ path, repoName, revisionName, domain }: CodePreviewPanelProps) => {
22+
const fileSourceResponse = await getFileSource({
23+
fileName: path,
24+
repository: repoName,
25+
branch: revisionName,
26+
}, domain);
4727

48-
if (isFileSourcePending || isRepoInfoPending) {
49-
return (
50-
<div className="flex flex-col w-full min-h-full items-center justify-center">
51-
<Loader2 className="w-4 h-4 animate-spin" />
52-
Loading...
53-
</div>
54-
)
55-
}
28+
const repoInfoResponse = await getRepoInfoByName(repoName, domain);
5629

57-
if (isFileSourceError || isRepoInfoError) {
30+
if (isServiceError(fileSourceResponse) || isServiceError(repoInfoResponse)) {
5831
return <div>Error loading file source</div>
5932
}
6033

34+
const codeHostInfo = getCodeHostInfoForRepo({
35+
codeHostType: repoInfoResponse.codeHostType,
36+
name: repoInfoResponse.name,
37+
displayName: repoInfoResponse.displayName,
38+
webUrl: repoInfoResponse.webUrl,
39+
});
40+
6141
return (
6242
<>
6343
<div className="flex flex-row py-1 px-2 items-center justify-between">
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use client';
2+
3+
import { useCallback, useRef } from "react";
4+
import { FileTreeItem } from "@/features/fileTree/actions";
5+
import { FileTreeItemComponent } from "@/features/fileTree/components/fileTreeItemComponent";
6+
import { useBrowseNavigation } from "../../hooks/useBrowseNavigation";
7+
import { ScrollArea } from "@/components/ui/scroll-area";
8+
import { useBrowseParams } from "../../hooks/useBrowseParams";
9+
import { usePrefetchFileSource } from "@/hooks/usePrefetchFileSource";
10+
import { usePrefetchFolderContents } from "@/hooks/usePrefetchFolderContents";
11+
12+
interface PureTreePreviewPanelProps {
13+
items: FileTreeItem[];
14+
}
15+
16+
export const PureTreePreviewPanel = ({ items }: PureTreePreviewPanelProps) => {
17+
const { repoName, revisionName } = useBrowseParams();
18+
const { navigateToPath } = useBrowseNavigation();
19+
const { prefetchFileSource } = usePrefetchFileSource();
20+
const { prefetchFolderContents } = usePrefetchFolderContents();
21+
const scrollAreaRef = useRef<HTMLDivElement>(null);
22+
23+
const onNodeClicked = useCallback((node: FileTreeItem) => {
24+
navigateToPath({
25+
repoName: repoName,
26+
revisionName: revisionName,
27+
path: node.path,
28+
pathType: node.type === 'tree' ? 'tree' : 'blob',
29+
});
30+
}, [navigateToPath, repoName, revisionName]);
31+
32+
const onNodeMouseEnter = useCallback((node: FileTreeItem) => {
33+
if (node.type === 'blob') {
34+
prefetchFileSource(repoName, revisionName ?? 'HEAD', node.path);
35+
} else if (node.type === 'tree') {
36+
prefetchFolderContents(repoName, revisionName ?? 'HEAD', node.path);
37+
}
38+
}, [prefetchFileSource, prefetchFolderContents, repoName, revisionName]);
39+
40+
return (
41+
<ScrollArea
42+
className="flex flex-col p-0.5"
43+
ref={scrollAreaRef}
44+
>
45+
{items.map((item) => (
46+
<FileTreeItemComponent
47+
key={item.path}
48+
node={item}
49+
isActive={false}
50+
depth={0}
51+
isCollapseChevronVisible={false}
52+
onClick={() => onNodeClicked(item)}
53+
onMouseEnter={() => onNodeMouseEnter(item)}
54+
parentRef={scrollAreaRef}
55+
/>
56+
))}
57+
</ScrollArea>
58+
)
59+
}
Lines changed: 23 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,29 @@
1-
'use client';
21

3-
import { Loader2 } from "lucide-react";
42
import { Separator } from "@/components/ui/separator";
53
import { getRepoInfoByName } from "@/actions";
64
import { PathHeader } from "@/app/[domain]/components/pathHeader";
7-
import { useCallback, useRef } from "react";
8-
import { FileTreeItem, getFolderContents } from "@/features/fileTree/actions";
9-
import { FileTreeItemComponent } from "@/features/fileTree/components/fileTreeItemComponent";
10-
import { useBrowseNavigation } from "../../hooks/useBrowseNavigation";
11-
import { ScrollArea } from "@/components/ui/scroll-area";
12-
import { unwrapServiceError } from "@/lib/utils";
13-
import { useBrowseParams } from "../../hooks/useBrowseParams";
14-
import { useDomain } from "@/hooks/useDomain";
15-
import { useQuery } from "@tanstack/react-query";
16-
import { usePrefetchFileSource } from "@/hooks/usePrefetchFileSource";
17-
import { usePrefetchFolderContents } from "@/hooks/usePrefetchFolderContents";
18-
19-
export const TreePreviewPanel = () => {
20-
const { path } = useBrowseParams();
21-
const { repoName, revisionName } = useBrowseParams();
22-
const domain = useDomain();
23-
const { navigateToPath } = useBrowseNavigation();
24-
const { prefetchFileSource } = usePrefetchFileSource();
25-
const { prefetchFolderContents } = usePrefetchFolderContents();
26-
const scrollAreaRef = useRef<HTMLDivElement>(null);
27-
28-
const { data: repoInfoResponse, isPending: isRepoInfoPending, isError: isRepoInfoError } = useQuery({
29-
queryKey: ['repoInfo', repoName, domain],
30-
queryFn: () => unwrapServiceError(getRepoInfoByName(repoName, domain)),
31-
});
32-
33-
const { data, isPending: isFolderContentsPending, isError: isFolderContentsError } = useQuery({
34-
queryKey: ['tree', repoName, revisionName, path, domain],
35-
queryFn: () => unwrapServiceError(
36-
getFolderContents({
37-
repoName,
38-
revisionName: revisionName ?? 'HEAD',
39-
path,
40-
}, domain)
41-
),
42-
});
43-
44-
const onNodeClicked = useCallback((node: FileTreeItem) => {
45-
navigateToPath({
46-
repoName: repoName,
47-
revisionName: revisionName,
48-
path: node.path,
49-
pathType: node.type === 'tree' ? 'tree' : 'blob',
50-
});
51-
}, [navigateToPath, repoName, revisionName]);
52-
53-
const onNodeMouseEnter = useCallback((node: FileTreeItem) => {
54-
if (node.type === 'blob') {
55-
prefetchFileSource(repoName, revisionName ?? 'HEAD', node.path);
56-
} else if (node.type === 'tree') {
57-
prefetchFolderContents(repoName, revisionName ?? 'HEAD', node.path);
58-
}
59-
}, [prefetchFileSource, prefetchFolderContents, repoName, revisionName]);
60-
61-
if (isFolderContentsPending || isRepoInfoPending) {
62-
return (
63-
<div className="flex flex-col w-full min-h-full items-center justify-center">
64-
<Loader2 className="w-4 h-4 animate-spin" />
65-
Loading...
66-
</div>
67-
)
68-
}
69-
70-
if (isFolderContentsError || isRepoInfoError) {
71-
return <div>Error loading tree</div>
5+
import { getFolderContents } from "@/features/fileTree/actions";
6+
import { isServiceError } from "@/lib/utils";
7+
import { PureTreePreviewPanel } from "./pureTreePreviewPanel";
8+
9+
interface TreePreviewPanelProps {
10+
path: string;
11+
repoName: string;
12+
revisionName?: string;
13+
domain: string;
14+
}
15+
16+
export const TreePreviewPanel = async ({ path, repoName, revisionName, domain }: TreePreviewPanelProps) => {
17+
const repoInfoResponse = await getRepoInfoByName(repoName, domain);
18+
19+
const folderContentsResponse = await getFolderContents({
20+
repoName,
21+
revisionName: revisionName ?? 'HEAD',
22+
path,
23+
}, domain);
24+
25+
if (isServiceError(folderContentsResponse) || isServiceError(repoInfoResponse)) {
26+
return <div>Error loading tree preview</div>
7227
}
7328

7429
return (
@@ -86,23 +41,7 @@ export const TreePreviewPanel = () => {
8641
/>
8742
</div>
8843
<Separator />
89-
<ScrollArea
90-
className="flex flex-col p-0.5"
91-
ref={scrollAreaRef}
92-
>
93-
{data.map((item) => (
94-
<FileTreeItemComponent
95-
key={item.path}
96-
node={item}
97-
isActive={false}
98-
depth={0}
99-
isCollapseChevronVisible={false}
100-
onClick={() => onNodeClicked(item)}
101-
onMouseEnter={() => onNodeMouseEnter(item)}
102-
parentRef={scrollAreaRef}
103-
/>
104-
))}
105-
</ScrollArea>
44+
<PureTreePreviewPanel items={folderContentsResponse} />
10645
</>
10746
)
10847
}

packages/web/src/app/[domain]/browse/[...path]/page.tsx

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,47 @@
1-
'use client';
1+
// import { CodePreviewPanel } from "./components/codePreviewPanel";
2+
// import { TreePreviewPanel } from "./components/treePreviewPanel";
23

3-
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
4+
import { Suspense } from "react";
5+
import { getBrowseParamsFromPathParam } from "../hooks/utils";
46
import { CodePreviewPanel } from "./components/codePreviewPanel";
7+
import { Loader2 } from "lucide-react";
58
import { TreePreviewPanel } from "./components/treePreviewPanel";
69

7-
export default function BrowsePage() {
8-
const { pathType } = useBrowseParams();
10+
interface BrowsePageProps {
11+
params: {
12+
path: string[];
13+
domain: string;
14+
};
15+
}
16+
17+
export default async function BrowsePage({ params: { path: _rawPath, domain } }: BrowsePageProps) {
18+
const rawPath = decodeURIComponent(_rawPath.join('/'));
19+
const { repoName, revisionName, path, pathType } = getBrowseParamsFromPathParam(rawPath);
20+
921
return (
1022
<div className="flex flex-col h-full">
11-
12-
{pathType === 'blob' ? (
13-
<CodePreviewPanel />
14-
) : (
15-
<TreePreviewPanel />
16-
)}
23+
<Suspense fallback={
24+
<div className="flex flex-col w-full min-h-full items-center justify-center">
25+
<Loader2 className="w-4 h-4 animate-spin" />
26+
Loading...
27+
</div>
28+
}>
29+
{pathType === 'blob' ? (
30+
<CodePreviewPanel
31+
path={path}
32+
repoName={repoName}
33+
revisionName={revisionName}
34+
domain={domain}
35+
/>
36+
) : (
37+
<TreePreviewPanel
38+
path={path}
39+
repoName={repoName}
40+
revisionName={revisionName}
41+
domain={domain}
42+
/>
43+
)}
44+
</Suspense>
1745
</div>
1846
)
1947
}
Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,18 @@
1-
'use client';
2-
31
import { usePathname } from "next/navigation";
2+
import { useMemo } from "react";
3+
import { getBrowseParamsFromPathParam } from "./utils";
44

55
export const useBrowseParams = () => {
66
const pathname = usePathname();
77

8-
const startIndex = pathname.indexOf('/browse/');
9-
if (startIndex === -1) {
10-
throw new Error(`Invalid browse pathname: "${pathname}" - expected to contain "/browse/"`);
11-
}
12-
13-
const rawPath = pathname.substring(startIndex + '/browse/'.length);
14-
const sentinalIndex = rawPath.search(/\/-\/(tree|blob)\//);
15-
if (sentinalIndex === -1) {
16-
throw new Error(`Invalid browse pathname: "${pathname}" - expected to contain "/-/(tree|blob)/" pattern`);
17-
}
18-
19-
const repoAndRevisionName = rawPath.substring(0, sentinalIndex).split('@');
20-
const repoName = repoAndRevisionName[0];
21-
const revisionName = repoAndRevisionName.length > 1 ? repoAndRevisionName[1] : undefined;
22-
23-
const { path, pathType } = ((): { path: string, pathType: 'tree' | 'blob' } => {
24-
const path = rawPath.substring(sentinalIndex + '/-/'.length);
25-
const pathType = path.startsWith('tree/') ? 'tree' : 'blob';
26-
27-
// @note: decodedURIComponent is needed here incase the path contains a space.
28-
switch (pathType) {
29-
case 'tree':
30-
return {
31-
path: decodeURIComponent(path.substring('tree/'.length)),
32-
pathType,
33-
};
34-
case 'blob':
35-
return {
36-
path: decodeURIComponent(path.substring('blob/'.length)),
37-
pathType,
38-
};
8+
return useMemo(() => {
9+
const startIndex = pathname.indexOf('/browse/');
10+
if (startIndex === -1) {
11+
throw new Error(`Invalid browse pathname: "${pathname}" - expected to contain "/browse/"`);
3912
}
40-
})();
4113

42-
return {
43-
repoName,
44-
revisionName,
45-
path,
46-
pathType,
47-
}
48-
}
14+
const rawPath = pathname.substring(startIndex + '/browse/'.length);
15+
return getBrowseParamsFromPathParam(rawPath);
16+
}, [pathname]);
17+
}
18+

0 commit comments

Comments
 (0)