Skip to content

Commit

Permalink
feat(ui): support browsing different git tree in code browser
Browse files Browse the repository at this point in the history
  • Loading branch information
liangfung committed Jun 3, 2024
1 parent a4ec2d7 commit 695fc71
Show file tree
Hide file tree
Showing 12 changed files with 367 additions and 113 deletions.
26 changes: 16 additions & 10 deletions ee/tabby-ui/app/files/components/chat-side-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const ChatSideBar: React.FC<ChatSideBarProps> = ({
...props
}) => {
const { setProgress } = useTopbarProgress()
const { updateSearchParams } = useRouterStuff()
const { updateSearchParams, updatePathnameAndSearch } = useRouterStuff()
const [{ data }] = useMe()
const {
pendingEvent,
Expand All @@ -52,13 +52,17 @@ export const ChatSideBar: React.FC<ChatSideBarProps> = ({
if (matchedRepositoryKey) {
const repository = repoMap[matchedRepositoryKey]
const repositorySpecifier = resolveRepoSpecifierFromRepoInfo(repository)
const fullPath = `${repositorySpecifier}/${context.filepath}`
// todo should contain rev or get default rev
const rev = 'main'
const fullPath = `${repositorySpecifier}/${rev}/${context.filepath}`
if (!fullPath) return
try {
setProgress(true)
const entries = await fetchEntriesFromPath(
fullPath,
repositorySpecifier ? repoMap?.[repositorySpecifier] : undefined
repositorySpecifier
? repoMap?.[`${repositorySpecifier}/${rev}`]
: undefined
)
const initialExpandedDirs = getDirectoriesFromBasename(
context.filepath
Expand Down Expand Up @@ -92,13 +96,15 @@ export const ChatSideBar: React.FC<ChatSideBarProps> = ({
}
} catch (e) {
} finally {
updateSearchParams({
set: {
path: `${repositorySpecifier ?? ''}/${context.filepath}`,
line: String(context.range.start ?? '')
},
del: 'plain'
})
updatePathnameAndSearch(
`${repositorySpecifier ?? ''}/${context.filepath}`,
{
set: {
line: String(context.range.start ?? '')
},
del: 'plain'
}
)
setProgress(false)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface FileDirectoryBreadcrumbProps
const FileDirectoryBreadcrumb: React.FC<FileDirectoryBreadcrumbProps> = ({
className
}) => {
const { currentFileRoutes, setActivePath, activePath } = React.useContext(
const { currentFileRoutes, updateActivePath, activePath } = React.useContext(
SourceCodeBrowserContext
)
const basename = React.useMemo(
Expand All @@ -25,7 +25,7 @@ const FileDirectoryBreadcrumb: React.FC<FileDirectoryBreadcrumbProps> = ({
<div className="flex items-center gap-1 overflow-x-auto leading-8">
<div
className="cursor-pointer font-medium text-primary hover:underline"
onClick={e => setActivePath(undefined)}
onClick={e => updateActivePath(undefined)}
>
Repositories
</div>
Expand All @@ -45,7 +45,7 @@ const FileDirectoryBreadcrumb: React.FC<FileDirectoryBreadcrumbProps> = ({
: 'cursor-pointer text-primary hover:underline',
isRepo ? 'hover:underline' : undefined
)}
onClick={e => setActivePath(route.fullPath)}
onClick={e => updateActivePath(route.fullPath)}
>
{route.name}
</div>
Expand Down
20 changes: 12 additions & 8 deletions ee/tabby-ui/app/files/components/file-directory-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const DirectoryView: React.FC<DirectoryViewProps> = ({
loading: propsLoading,
initialized
}) => {
const { activePath, currentFileRoutes, setActivePath, fileTreeData } =
const { activePath, currentFileRoutes, updateActivePath, fileTreeData } =
React.useContext(SourceCodeBrowserContext)

const files = React.useMemo(() => {
Expand All @@ -37,11 +37,11 @@ const DirectoryView: React.FC<DirectoryViewProps> = ({
const onClickParent = () => {
const parentPath =
currentFileRoutes[currentFileRoutes?.length - 2]?.fullPath
setActivePath(parentPath)
updateActivePath(parentPath)
}

const onClickFile = (file: TFileMapItem) => {
setActivePath(file.fullPath)
updateActivePath(file.fullPath)
}

return (
Expand Down Expand Up @@ -138,11 +138,15 @@ function getCurrentDirFromTree(
const repos = treeData.map(x => omit(x, 'children')) || []
return repos
} else {
let { repositorySpecifier = '', basename = '' } =
resolveRepositoryInfoFromPath(path)
let pathSegments = [repositorySpecifier, ...basename.split('/')].filter(
Boolean
)
let {
repositorySpecifier = '',
basename = '',
rev = ''
} = resolveRepositoryInfoFromPath(path)
let pathSegments = [
`${repositorySpecifier}/${rev}`,
...basename.split('/')
].filter(Boolean)
let currentNodes: TFileTreeNode[] = treeData
for (let i = 0; i < pathSegments.length; i++) {
const path = pathSegments.slice(0, i + 1).join('/')
Expand Down
115 changes: 109 additions & 6 deletions ee/tabby-ui/app/files/components/file-tree-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,35 @@ import { useDebounceCallback } from '@/lib/hooks/use-debounce'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList
} from '@/components/ui/command'
import {
IconCheck,
IconClose,
IconDirectorySolid,
IconFile,
IconFolderGit
IconFolderGit,
IconGitFork,
IconTag
} from '@/components/ui/icons'
import {
Popover,
PopoverContent,
PopoverTrigger
} from '@/components/ui/popover'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
SearchableSelect,
SearchableSelectAnchor,
Expand All @@ -36,6 +53,7 @@ import {
fetchEntriesFromPath,
getDirectoriesFromBasename,
resolveFileNameFromPath,
resolveRepoRef,
resolveRepositoryInfoFromPath
} from './utils'

Expand All @@ -48,6 +66,8 @@ type SearchOption = {
id: string
}

type RepositoryRefKind = 'branch' | 'tag'

const repositorySearch = graphql(/* GraphQL */ `
query RepositorySearch($kind: RepositoryKind!, $id: ID!, $pattern: String!) {
repositorySearch(kind: $kind, id: $id, pattern: $pattern) {
Expand All @@ -65,18 +85,30 @@ const FileTreeHeader: React.FC<FileTreeHeaderProps> = ({
const {
activePath,
fileTreeData,
setActivePath,
updateActivePath,
initialized,
updateFileMap,
setExpandedKeys,
repoMap,
activeRepo
activeRepo,
activeRepoRef
} = useContext(SourceCodeBrowserContext)
const { setProgress } = useTopbarProgress()

const [refSelectVisible, setRefSelectVisible] = React.useState(false)
// todo default kind, consider abstarct to upper level
const [activeRefKind, setActiveRefKind] =
React.useState<RepositoryRefKind>('branch')
const { repositoryKind, repositoryName, repositorySpecifier } =
resolveRepositoryInfoFromPath(activePath)
const repoId = activeRepo?.id
const refs = activeRepo?.refs
const formattedRefs = React.useMemo(() => {
if (!refs?.length) return []
return refs.map(ref => resolveRepoRef(ref))
}, [refs])
const branches = formattedRefs.filter(o => o.kind === 'branch')
const tags = formattedRefs.filter(o => o.kind === 'tag')
const commandOptions = activeRefKind === 'tag' ? tags : branches

const inputRef = React.useRef<HTMLInputElement>(null)
const [input, setInput] = React.useState<string>()
Expand Down Expand Up @@ -108,7 +140,7 @@ const FileTreeHeader: React.FC<FileTreeHeaderProps> = ({
}, [repositorySearchData?.repositorySearch])

const onSelectRepo = (name: string) => {
setActivePath(name)
updateActivePath(name)
}

const onInputValueChange = useDebounceCallback((v: string | undefined) => {
Expand Down Expand Up @@ -167,7 +199,7 @@ const FileTreeHeader: React.FC<FileTreeHeaderProps> = ({
}
} catch (e) {
} finally {
setActivePath(fullPath)
updateActivePath(fullPath)
setProgress(false)
}
}
Expand Down Expand Up @@ -202,6 +234,7 @@ const FileTreeHeader: React.FC<FileTreeHeaderProps> = ({
<div className={cn(className)} {...props}>
<div className="py-4 font-bold leading-8">Files</div>
<div className="space-y-3">
{/* Repository select */}
<Select
disabled={!initialized}
onValueChange={onSelectRepo}
Expand Down Expand Up @@ -251,6 +284,76 @@ const FileTreeHeader: React.FC<FileTreeHeaderProps> = ({
)}
</SelectContent>
</Select>
{/* branch select */}
<Popover open={refSelectVisible} onOpenChange={setRefSelectVisible}>
<PopoverTrigger asChild>
<Button
className="w-full justify-start gap-2 px-3"
variant="outline"
>
{!!activeRepoRef && (
<>
{activeRepoRef.kind === 'branch' ? (
<IconGitFork />
) : (
<IconTag />
)}
{activeRepoRef.name}
</>
)}
</Button>
</PopoverTrigger>
<PopoverContent
className="w-[var(--radix-popover-trigger-width)] p-0"
align="start"
side="bottom"
>
{/* <div className='px-2 py-1'>Switch branches/tags</div> */}
<Command className="transition-all">
<CommandInput
placeholder={
activeRefKind === 'tag' ? 'Find a tag' : 'Find a branch'
}
/>
<Tabs
className="my-1 border-b"
value={activeRefKind}
onValueChange={v => setActiveRefKind(v as RepositoryRefKind)}
>
{/* todo style */}
<TabsList className="bg-popover py-0">
<TabsTrigger value="branch">Branches</TabsTrigger>
<TabsTrigger value="tag">Tags</TabsTrigger>
</TabsList>
</Tabs>
<CommandList className="max-h-[30vh]">
<CommandEmpty>Nothing to show</CommandEmpty>
<CommandGroup>
{commandOptions.map((ref, refIndex) => (
<CommandItem
key={ref.ref ?? refIndex}
onSelect={() => {
setRefSelectVisible(false)
// todo
}}
>
{/* <IconCheck
className={cn(
'mr-2',
ref.node.id === field.value
? 'opacity-100'
: 'opacity-0'
)}
/> */}
{ref.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
{/* Go to file */}
<SearchableSelect
stayOpenOnInputClick
options={options}
Expand Down
9 changes: 5 additions & 4 deletions ee/tabby-ui/app/files/components/file-tree-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface FileTreePanelProps extends React.HTMLAttributes<HTMLDivElement> {}
export const FileTreePanel: React.FC<FileTreePanelProps> = () => {
const {
activePath,
setActivePath,
updateActivePath,
expandedKeys,
updateFileMap,
toggleExpandedKey,
Expand All @@ -25,13 +25,14 @@ export const FileTreePanel: React.FC<FileTreePanelProps> = () => {
const containerRef = React.useRef<HTMLDivElement>(null)
const scrollTop = useScrollTop(containerRef, 200)
const onSelectTreeNode = (treeNode: TFileTreeNode) => {
setActivePath(treeNode.fullPath)
updateActivePath(treeNode.fullPath)
}

const currentFileTreeData = React.useMemo(() => {
const { repositorySpecifier } = resolveRepositoryInfoFromPath(activePath)
const { repositorySpecifier, rev } =
resolveRepositoryInfoFromPath(activePath)
const repo = fileTreeData.find(
treeNode => treeNode.fullPath === repositorySpecifier
treeNode => treeNode.fullPath === `${repositorySpecifier}/${rev}`
)
return repo?.children ?? []
}, [activePath, fileTreeData])
Expand Down
Loading

0 comments on commit 695fc71

Please sign in to comment.