Skip to content

Commit

Permalink
refactor(ui): ajust the structure of file blob view in code browser (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
liangfung authored Jul 3, 2024
1 parent f416bc3 commit 26e0842
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 116 deletions.
119 changes: 119 additions & 0 deletions ee/tabby-ui/app/files/components/blob-mode-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from 'react'

import useRouterStuff from '@/lib/hooks/use-router-stuff'
import { filename2prism } from '@/lib/language-utils'
import { cn } from '@/lib/utils'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ListSkeleton } from '@/components/skeleton'

import { BlobHeader } from './blob-header'
import { RawFileView } from './raw-file-view'
import { SourceCodeBrowserContext } from './source-code-browser'
import { TextFileView } from './text-file-view'
import { FileDisplayType } from './types'

interface BlobViewProps extends React.HTMLAttributes<HTMLDivElement> {
blob: Blob | undefined
contentLength: number | undefined
fileDisplayType?: FileDisplayType
loading?: boolean
}

type BlobModeViewContextValue = {
textValue?: string | undefined
}

export const BlobModeViewContext =
React.createContext<BlobModeViewContextValue>({} as BlobModeViewContextValue)

const BlobModeViewRenderer: React.FC<BlobViewProps> = ({
className,
blob,
contentLength,
fileDisplayType,
loading
}) => {
const { searchParams, updateUrlComponents } = useRouterStuff()
const { activePath } = React.useContext(SourceCodeBrowserContext)
const { textValue } = React.useContext(BlobModeViewContext)
const isRaw = fileDisplayType === 'raw' || fileDisplayType === 'image'

const detectedLanguage = activePath
? filename2prism(activePath)[0]
: undefined
const language = detectedLanguage ?? 'plain'
const isMarkdown = !!textValue && language === 'markdown'
const isPlain = searchParams.get('plain')?.toString() === '1'

const onToggleMarkdownView = (value: string) => {
if (value === '1') {
updateUrlComponents({
searchParams: {
set: {
plain: '1'
}
}
})
} else {
updateUrlComponents({
searchParams: {
del: 'plain'
}
})
}
}

return (
<div className={cn(className)}>
<BlobHeader blob={blob} contentLength={contentLength} canCopy={!isRaw}>
{isMarkdown && (
<Tabs
value={isPlain ? '1' : '0'}
onValueChange={onToggleMarkdownView}
>
<TabsList>
<TabsTrigger value="0">Preview</TabsTrigger>
<TabsTrigger value="1">Code</TabsTrigger>
</TabsList>
</Tabs>
)}
</BlobHeader>
{loading && !blob ? (
<ListSkeleton className="p-2" />
) : isRaw ? (
<RawFileView blob={blob} isImage={fileDisplayType === 'image'} />
) : (
<TextFileView />
)}
</div>
)
}

export const BlobModeView: React.FC<BlobViewProps> = props => {
const { blob, fileDisplayType, contentLength } = props
const [textValue, setTextValue] = React.useState<string | undefined>()
React.useEffect(() => {
const blob2Text = async (b: Blob) => {
try {
const v = await b.text()
setTextValue(v)
} catch (e) {
setTextValue('')
}
}

if (!!blob && fileDisplayType === 'text') {
blob2Text(blob)
}
}, [blob, fileDisplayType])

return (
<BlobModeViewContext.Provider value={{ textValue }}>
<BlobModeViewRenderer
blob={blob}
fileDisplayType={fileDisplayType}
contentLength={contentLength}
/>
</BlobModeViewContext.Provider>
)
}
10 changes: 3 additions & 7 deletions ee/tabby-ui/app/files/components/raw-file-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,23 @@ import React, { useContext } from 'react'

import { cn } from '@/lib/utils'

import { BlobHeader } from './blob-header'
import { SourceCodeBrowserContext } from './source-code-browser'
import { resolveFileNameFromPath } from './utils'

interface RawContentViewProps extends React.HTMLAttributes<HTMLDivElement> {
interface RawFileViewProps extends React.HTMLAttributes<HTMLDivElement> {
blob: Blob | undefined
contentLength: number | undefined
isImage?: boolean
}

export const RawFileView: React.FC<RawContentViewProps> = ({
export const RawFileView: React.FC<RawFileViewProps> = ({
className,
blob,
isImage,
contentLength
isImage
}) => {
const { activePath } = useContext(SourceCodeBrowserContext)

return (
<div className={cn(className)}>
<BlobHeader blob={blob} contentLength={contentLength} />
<div className="rounded-b-lg border border-t-0 p-2 text-center">
{isImage ? (
<img
Expand Down
64 changes: 24 additions & 40 deletions ee/tabby-ui/app/files/components/source-code-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import { ListSkeleton } from '@/components/skeleton'
import { useTopbarProgress } from '@/components/topbar-progress-indicator'

import { emitter, QuickActionEventPayload } from '../lib/event-emitter'
import { BlobModeView } from './blob-mode-view'
import { ChatSideBar } from './chat-side-bar'
import { ErrorView } from './error-view'
import { FileDirectoryBreadcrumb } from './file-directory-breadcrumb'
import { DirectoryView } from './file-directory-view'
import { mapToFileTree, sortFileTree, type TFileTreeNode } from './file-tree'
import { FileTreePanel } from './file-tree-panel'
import { RawFileView } from './raw-file-view'
import { TextFileView } from './text-file-view'
import { TreeModeView } from './tree-mode-view'
import type { FileDisplayType } from './types'
import {
CodeBrowserError,
generateEntryPath,
Expand Down Expand Up @@ -293,8 +293,6 @@ interface SourceCodeBrowserProps {
className?: string
}

type FileDisplayType = 'image' | 'text' | 'raw' | ''

const ENTRY_CONTENT_TYPE = 'application/vnd.directory+json'

const SourceCodeBrowserRenderer: React.FC<SourceCodeBrowserProps> = ({
Expand Down Expand Up @@ -331,8 +329,6 @@ const SourceCodeBrowserRenderer: React.FC<SourceCodeBrowserProps> = ({

const activeBasename = parsedEntryInfo?.basename

const [fileViewType, setFileViewType] = React.useState<FileDisplayType>()

const isBlobMode = activeEntryInfo?.viewMode === 'blob'
const shouldFetchTree =
!!isPathInitialized && !isEmpty(repoMap) && !!activePath
Expand Down Expand Up @@ -368,23 +364,29 @@ const SourceCodeBrowserRenderer: React.FC<SourceCodeBrowserProps> = ({
} = useSWR<{
blob?: Blob
contentLength?: number
fileDisplayType: FileDisplayType
}>(
isBlobMode && activeRepo
? toEntryRequestUrl(activeRepo, activeRepoRef?.name, activeBasename)
? [
toEntryRequestUrl(activeRepo, activeRepoRef?.name, activeBasename),
activeBasename
]
: null,
(url: string) =>
([url, basename]: [string, string]) =>
fetcher(url, {
responseFormatter: async response => {
const contentType = response.headers.get('Content-Type')
if (contentType === ENTRY_CONTENT_TYPE) {
throw new Error(CodeBrowserError.INVALID_URL)
}
const contentLength = toNumber(response.headers.get('Content-Length'))
// todo abort big size request and truncate
// FIXME(jueliang) abort big size request and truncate the response data
const blob = await response.blob()
const fileDisplayType = await getFileViewType(basename ?? '', blob)
return {
contentLength,
blob
blob,
fileDisplayType
}
},
errorHandler() {
Expand All @@ -399,18 +401,14 @@ const SourceCodeBrowserRenderer: React.FC<SourceCodeBrowserProps> = ({

const fileBlob = rawFileResponse?.blob
const contentLength = rawFileResponse?.contentLength
const fileDisplayType = rawFileResponse?.fileDisplayType
const error = rawFileError || entriesError

const showErrorView = !!error

const showDirectoryView =
const isTreeMode =
activeEntryInfo?.viewMode === 'tree' || !activeEntryInfo?.viewMode

const showTextFileView = isBlobMode && fileViewType === 'text'

const showRawFileView =
isBlobMode && (fileViewType === 'image' || fileViewType === 'raw')

const onPanelLayout = (sizes: number[]) => {
if (sizes?.[2]) {
setChatSideBarPanelSize(sizes[2])
Expand Down Expand Up @@ -522,19 +520,6 @@ const SourceCodeBrowserRenderer: React.FC<SourceCodeBrowserProps> = ({
}
}, [fetchingRawFile, fetchingTreeEntries])

React.useEffect(() => {
const calculateViewType = async () => {
const displayType = await getFileViewType(activePath ?? '', fileBlob)
setFileViewType(displayType)
}

if (isBlobMode) {
calculateViewType()
} else {
setFileViewType('')
}
}, [activePath, isBlobMode, fileBlob])

React.useEffect(() => {
if (chatSideBarVisible) {
chatSideBarPanelRef.current?.expand()
Expand Down Expand Up @@ -596,21 +581,19 @@ const SourceCodeBrowserRenderer: React.FC<SourceCodeBrowserProps> = ({
/>
) : (
<div>
{showDirectoryView && (
<DirectoryView
{isTreeMode && (
<TreeModeView
loading={fetchingTreeEntries}
initialized={initialized}
className={`rounded-lg border`}
/>
)}
{showTextFileView && (
<TextFileView blob={fileBlob} contentLength={contentLength} />
)}
{showRawFileView && (
<RawFileView
{isBlobMode && (
<BlobModeView
blob={fileBlob}
isImage={fileViewType === 'image'}
contentLength={contentLength}
fileDisplayType={fileDisplayType}
loading={fetchingRawFile || fetchingTreeEntries}
/>
)}
</div>
Expand Down Expand Up @@ -696,8 +679,9 @@ function isReadableTextFile(blob: Blob) {
async function getFileViewType(
path: string,
blob: Blob | undefined
): Promise<FileDisplayType> {
if (!blob) return ''
): Promise<FileDisplayType | undefined> {
if (!blob) return undefined

const mimeType = blob?.type
const detectedLanguage = filename2prism(path)?.[0]

Expand Down
Loading

0 comments on commit 26e0842

Please sign in to comment.