mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 12:25:22 +00:00
fileTree
This commit is contained in:
parent
8ae43fd772
commit
41b2226beb
3 changed files with 50 additions and 44 deletions
|
|
@ -1,11 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useRef } from "react";
|
||||
import { useRef } from "react";
|
||||
import { FileTreeItem } from "@/features/fileTree/actions";
|
||||
import { FileTreeItemComponent } from "@/features/fileTree/components/fileTreeItemComponent";
|
||||
import { useBrowseNavigation } from "../../hooks/useBrowseNavigation";
|
||||
import { getBrowsePath } from "../../hooks/useBrowseNavigation";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { useBrowseParams } from "../../hooks/useBrowseParams";
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
|
||||
interface PureTreePreviewPanelProps {
|
||||
items: FileTreeItem[];
|
||||
|
|
@ -13,18 +14,9 @@ interface PureTreePreviewPanelProps {
|
|||
|
||||
export const PureTreePreviewPanel = ({ items }: PureTreePreviewPanelProps) => {
|
||||
const { repoName, revisionName } = useBrowseParams();
|
||||
const { navigateToPath } = useBrowseNavigation();
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onNodeClicked = useCallback((node: FileTreeItem) => {
|
||||
navigateToPath({
|
||||
repoName: repoName,
|
||||
revisionName: revisionName,
|
||||
path: node.path,
|
||||
pathType: node.type === 'tree' ? 'tree' : 'blob',
|
||||
});
|
||||
}, [navigateToPath, repoName, revisionName]);
|
||||
|
||||
const domain = useDomain();
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className="flex flex-col p-0.5"
|
||||
|
|
@ -37,8 +29,14 @@ export const PureTreePreviewPanel = ({ items }: PureTreePreviewPanelProps) => {
|
|||
isActive={false}
|
||||
depth={0}
|
||||
isCollapseChevronVisible={false}
|
||||
onClick={() => onNodeClicked(item)}
|
||||
parentRef={scrollAreaRef}
|
||||
href={getBrowsePath({
|
||||
repoName,
|
||||
revisionName,
|
||||
path: item.path,
|
||||
pathType: item.type === 'tree' ? 'tree' : 'blob',
|
||||
domain,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</ScrollArea>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import clsx from "clsx";
|
|||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons";
|
||||
import { FileTreeItemIcon } from "./fileTreeItemIcon";
|
||||
import Link from "next/link";
|
||||
|
||||
export const FileTreeItemComponent = ({
|
||||
node,
|
||||
|
|
@ -13,7 +14,9 @@ export const FileTreeItemComponent = ({
|
|||
depth,
|
||||
isCollapsed = false,
|
||||
isCollapseChevronVisible = true,
|
||||
href,
|
||||
onClick,
|
||||
onNavigate,
|
||||
parentRef,
|
||||
}: {
|
||||
node: FileTreeItem,
|
||||
|
|
@ -21,10 +24,12 @@ export const FileTreeItemComponent = ({
|
|||
depth: number,
|
||||
isCollapsed?: boolean,
|
||||
isCollapseChevronVisible?: boolean,
|
||||
onClick: () => void,
|
||||
href: string,
|
||||
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void,
|
||||
onNavigate?: (e: { preventDefault: () => void }) => void,
|
||||
parentRef: React.RefObject<HTMLDivElement | null>,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const ref = useRef<HTMLAnchorElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isActive && ref.current) {
|
||||
|
|
@ -51,20 +56,16 @@ export const FileTreeItemComponent = ({
|
|||
}, [isActive, parentRef]);
|
||||
|
||||
return (
|
||||
<div
|
||||
<Link
|
||||
ref={ref}
|
||||
href={href}
|
||||
className={clsx("flex flex-row gap-1 items-center hover:bg-accent hover:text-accent-foreground rounded-sm cursor-pointer p-0.5", {
|
||||
'bg-accent': isActive,
|
||||
})}
|
||||
style={{ paddingLeft: `${depth * 16}px` }}
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
}}
|
||||
onClick={onClick}
|
||||
onNavigate={onNavigate}
|
||||
>
|
||||
<div
|
||||
className="flex flex-row gap-1 cursor-pointer w-4 h-4 flex-shrink-0"
|
||||
|
|
@ -79,6 +80,6 @@ export const FileTreeItemComponent = ({
|
|||
</div>
|
||||
<FileTreeItemIcon item={node} />
|
||||
<span className="text-sm">{node.name}</span>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import { FileTreeNode as RawFileTreeNode } from "../actions";
|
|||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import React, { useCallback, useMemo, useState, useEffect, useRef } from "react";
|
||||
import { FileTreeItemComponent } from "./fileTreeItemComponent";
|
||||
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
|
||||
import { getBrowsePath } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
|
||||
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
|
||||
|
||||
import { useDomain } from "@/hooks/useDomain";
|
||||
|
||||
export type FileTreeNode = Omit<RawFileTreeNode, 'children'> & {
|
||||
isCollapsed: boolean;
|
||||
|
|
@ -41,8 +41,8 @@ interface PureFileTreePanelProps {
|
|||
export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps) => {
|
||||
const [tree, setTree] = useState<FileTreeNode>(buildCollapsibleTree(_tree));
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||
const { navigateToPath } = useBrowseNavigation();
|
||||
const { repoName, revisionName } = useBrowseParams();
|
||||
const domain = useDomain();
|
||||
|
||||
// @note: When `_tree` changes, it indicates that a new tree has been loaded.
|
||||
// In that case, we need to rebuild the collapsible tree.
|
||||
|
|
@ -72,21 +72,6 @@ export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps)
|
|||
}
|
||||
}, [path, setIsCollapsed]);
|
||||
|
||||
const onNodeClicked = useCallback((node: FileTreeNode) => {
|
||||
if (node.type === 'tree') {
|
||||
setIsCollapsed(node.path, !node.isCollapsed);
|
||||
}
|
||||
else if (node.type === 'blob') {
|
||||
navigateToPath({
|
||||
repoName: repoName,
|
||||
revisionName: revisionName,
|
||||
path: node.path,
|
||||
pathType: 'blob',
|
||||
});
|
||||
|
||||
}
|
||||
}, [setIsCollapsed, navigateToPath, repoName, revisionName]);
|
||||
|
||||
const renderTree = useCallback((nodes: FileTreeNode, depth = 0): React.ReactNode => {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -94,13 +79,35 @@ export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps)
|
|||
return (
|
||||
<React.Fragment key={node.path}>
|
||||
<FileTreeItemComponent
|
||||
href={getBrowsePath({
|
||||
repoName,
|
||||
revisionName,
|
||||
path: node.path,
|
||||
pathType: node.type === 'tree' ? 'tree' : 'blob',
|
||||
domain,
|
||||
})}
|
||||
key={node.path}
|
||||
node={node}
|
||||
isActive={node.path === path}
|
||||
depth={depth}
|
||||
isCollapsed={node.isCollapsed}
|
||||
isCollapseChevronVisible={node.type === 'tree'}
|
||||
onClick={() => onNodeClicked(node)}
|
||||
// Only collapse the tree when a regular click happens.
|
||||
// (i.e., not ctrl/cmd click).
|
||||
onClick={(e) => {
|
||||
const isMetaOrCtrlKey = e.metaKey || e.ctrlKey;
|
||||
if (node.type === 'tree' && !isMetaOrCtrlKey) {
|
||||
setIsCollapsed(node.path, !node.isCollapsed);
|
||||
}
|
||||
}}
|
||||
// @note: onNavigate _won't_ be called when the user ctrl/cmd clicks on a tree node.
|
||||
// So when a regular click happens, we want to prevent the navigation from happening
|
||||
// and instead collapse the tree.
|
||||
onNavigate={(e) => {
|
||||
if (node.type === 'tree') {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
parentRef={scrollAreaRef}
|
||||
/>
|
||||
{node.children.length > 0 && !node.isCollapsed && renderTree(node, depth + 1)}
|
||||
|
|
@ -109,7 +116,7 @@ export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps)
|
|||
})}
|
||||
</>
|
||||
);
|
||||
}, [path, onNodeClicked]);
|
||||
}, [path]);
|
||||
|
||||
const renderedTree = useMemo(() => renderTree(tree), [tree, renderTree]);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue