|
|
|
@@ -3,12 +3,15 @@ import { lazy, Suspense, useEffect, useRef, useState } from 'react'
|
|
|
|
|
import { useParams, useSearchParams } from 'react-router-dom'
|
|
|
|
|
import { api, Project, Repo, RepoCommit, RepoTreeEntry } from '../api'
|
|
|
|
|
import Autocomplete from '../components/Autocomplete'
|
|
|
|
|
import AccountTreeIcon from '@mui/icons-material/AccountTree'
|
|
|
|
|
import CodeIcon from '@mui/icons-material/Code'
|
|
|
|
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
|
|
|
|
import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'
|
|
|
|
|
import FolderIcon from '@mui/icons-material/Folder'
|
|
|
|
|
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'
|
|
|
|
|
import FileDownloadIcon from '@mui/icons-material/FileDownload'
|
|
|
|
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
|
|
|
|
import TerminalIcon from '@mui/icons-material/Terminal'
|
|
|
|
|
import LazyCodeBlock from '../components/LazyCodeBlock'
|
|
|
|
|
import PageAlert from '../components/PageAlert'
|
|
|
|
|
import ContextToolbar from '../components/ContextToolbar'
|
|
|
|
@@ -167,37 +170,39 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
|
})
|
|
|
|
|
}, [repoId, ref, path, branches, treeReloadTick])
|
|
|
|
|
|
|
|
|
|
const clearReadme = () => {
|
|
|
|
|
setReadmeContent('')
|
|
|
|
|
setReadmeKind('')
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!repoId || !ref) {
|
|
|
|
|
setReadmeContent('')
|
|
|
|
|
setReadmeKind('')
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
clearReadme()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!repo) return
|
|
|
|
|
if (repo && repo.type && repo.type !== 'git') {
|
|
|
|
|
setReadmeContent('')
|
|
|
|
|
setReadmeKind('')
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
|
|
|
|
|
if (repo.type && repo.type !== 'git') {
|
|
|
|
|
clearReadme()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectedFile) {
|
|
|
|
|
setReadmeContent('')
|
|
|
|
|
setReadmeKind('')
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
clearReadme()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const entries = tree || []
|
|
|
|
|
let readme = entries.find((entry) => entry.name.toLowerCase() === 'readme.md')
|
|
|
|
|
let kind: 'markdown' | 'text' | '' = readme ? 'markdown' : ''
|
|
|
|
|
let kind: 'markdown' | 'text' | '' = (readme? 'markdown' : '')
|
|
|
|
|
if (!readme) {
|
|
|
|
|
readme = entries.find((entry) => entry.name.toLowerCase() === 'readme.txt')
|
|
|
|
|
kind = readme ? 'text' : ''
|
|
|
|
|
}
|
|
|
|
|
if (!readme || readme.type !== 'file') {
|
|
|
|
|
setReadmeContent('')
|
|
|
|
|
setReadmeKind('')
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
clearReadme()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (readmePath === readme.path && readmeKind === kind) return
|
|
|
|
@@ -208,9 +213,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
|
setReadmePath(readme.path)
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
setReadmeContent('')
|
|
|
|
|
setReadmeKind('')
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
clearReadme()
|
|
|
|
|
})
|
|
|
|
|
}, [repoId, ref, tree, selectedFile, readmePath, readmeKind])
|
|
|
|
|
|
|
|
|
@@ -236,6 +239,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
|
setImagePreviewRef(ref)
|
|
|
|
|
if (!isImageFile(entry.path)) {
|
|
|
|
|
try {
|
|
|
|
|
// get the content of the select file and set it to the content view
|
|
|
|
|
const blob = await api.getRepoBlob(repoId, ref || undefined, entry.path)
|
|
|
|
|
setContent(blob.content)
|
|
|
|
|
} catch {
|
|
|
|
@@ -539,6 +543,119 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
|
return match[1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const renderClonePopover = () => {
|
|
|
|
|
if (!repo?.clone_url) return null
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<IconButton onClick={(event) => setCodeAnchor(event.currentTarget)}>
|
|
|
|
|
<CodeIcon />
|
|
|
|
|
</IconButton>
|
|
|
|
|
<Popover
|
|
|
|
|
open={Boolean(codeAnchor)}
|
|
|
|
|
anchorEl={codeAnchor}
|
|
|
|
|
onClose={() => setCodeAnchor(null)}
|
|
|
|
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
|
|
|
|
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
|
|
|
PaperProps={{
|
|
|
|
|
sx: {
|
|
|
|
|
p: '8px !important',
|
|
|
|
|
minWidth: 480,
|
|
|
|
|
border: '1px solid',
|
|
|
|
|
borderColor: 'divider',
|
|
|
|
|
boxShadow: 'none'
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Box sx={{ display: 'grid', gap: 1 }}>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<TerminalIcon />
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
|
|
|
Clone
|
|
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 48 }}>
|
|
|
|
|
/git
|
|
|
|
|
</Typography>
|
|
|
|
|
<TextField
|
|
|
|
|
value={buildCloneURL(repo.clone_url || '')}
|
|
|
|
|
size="small"
|
|
|
|
|
InputProps={{ readOnly: true }}
|
|
|
|
|
sx={{ flex: 1 }}
|
|
|
|
|
/>
|
|
|
|
|
<Tooltip title="Copy /git URL">
|
|
|
|
|
<IconButton onClick={() => copyText(buildCloneURL(repo.clone_url || ''))} size="small">
|
|
|
|
|
<ContentCopyIcon fontSize="small" />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Box>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 48 }}>
|
|
|
|
|
/git-id
|
|
|
|
|
</Typography>
|
|
|
|
|
<TextField
|
|
|
|
|
value={`${window.location.origin}/git-id/${repo.id}.git`}
|
|
|
|
|
size="small"
|
|
|
|
|
InputProps={{ readOnly: true }}
|
|
|
|
|
sx={{ flex: 1 }}
|
|
|
|
|
/>
|
|
|
|
|
<Tooltip title="Copy /git-id URL">
|
|
|
|
|
<IconButton onClick={() => copyText(`${window.location.origin}/git-id/${repo.id}.git`)} size="small">
|
|
|
|
|
<ContentCopyIcon fontSize="small" />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Box>
|
|
|
|
|
</Box>
|
|
|
|
|
</Popover>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const renderPathBreadcrumb = () => {
|
|
|
|
|
return (<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0 }}>
|
|
|
|
|
<IconButton
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={() => handleBreadcrumb('')}
|
|
|
|
|
aria-label="Root"
|
|
|
|
|
sx={{ p: 0.25 }}
|
|
|
|
|
>
|
|
|
|
|
<HomeOutlinedIcon fontSize="small" />
|
|
|
|
|
</IconButton>
|
|
|
|
|
<Typography variant="body2" color="text.secondary" sx={{ mx: 0.25 }}>
|
|
|
|
|
/
|
|
|
|
|
</Typography>
|
|
|
|
|
{pathParts.map((part, idx) => {
|
|
|
|
|
const nextPath = pathParts.slice(0, idx + 1).join('/')
|
|
|
|
|
return (
|
|
|
|
|
<Box key={nextPath} sx={{ display: 'flex', alignItems: 'center', gap: 0 }}>
|
|
|
|
|
{idx > 0 ? (
|
|
|
|
|
<Typography variant="body2" color="text.secondary" sx={{ mx: 0.25 }}>
|
|
|
|
|
/
|
|
|
|
|
</Typography>
|
|
|
|
|
) : null}
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={() => handleBreadcrumb(nextPath)}
|
|
|
|
|
sx={{ textTransform: 'none', minWidth: 0, px: 0.5 }}
|
|
|
|
|
>
|
|
|
|
|
{part}
|
|
|
|
|
</Button>
|
|
|
|
|
</Box>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
{selectedFile ? (
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0 }}>
|
|
|
|
|
<Typography variant="body2" color="text.secondary" sx={{ mx: 0.25 }}>
|
|
|
|
|
/
|
|
|
|
|
</Typography>
|
|
|
|
|
<Typography variant="body2" sx={{ px: 0.5 }}>
|
|
|
|
|
{selectedFile.split('/').pop()}
|
|
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
) : null}
|
|
|
|
|
</Box>)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<ProjectPageFrame
|
|
|
|
|
projectId={projectId ?? ''}
|
|
|
|
@@ -558,193 +675,94 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
|
{loadError}
|
|
|
|
|
</Typography>
|
|
|
|
|
) : null}
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'stretch', gap: 1, mb: 1 }}>
|
|
|
|
|
<Paper sx={{ p: 1, display: 'flex', alignItems: 'center', gap: 1, minWidth: 240, backgroundColor: 'transparent' }}>
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
|
|
|
Branch
|
|
|
|
|
</Typography>
|
|
|
|
|
{branches.length ? (
|
|
|
|
|
<Autocomplete
|
|
|
|
|
freeSolo
|
|
|
|
|
options={branches}
|
|
|
|
|
value={ref}
|
|
|
|
|
inputValue={ref}
|
|
|
|
|
onChange={(_, value) => setRef(String(value || ''))}
|
|
|
|
|
onInputChange={(_, value, reason) => {
|
|
|
|
|
if (reason === 'input') setRef(value)
|
|
|
|
|
if (reason === 'clear') setRef('')
|
|
|
|
|
}}
|
|
|
|
|
renderInput={(params) => <TextField {...params} size="small" placeholder="branch or ref" />}
|
|
|
|
|
slotProps={{
|
|
|
|
|
popper: { sx: { zIndex: 1400 } }
|
|
|
|
|
}}
|
|
|
|
|
sx={{ minWidth: 180 }}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{defaultBranch ? (
|
|
|
|
|
<Typography variant="caption" color="text.secondary">
|
|
|
|
|
HEAD: {defaultBranch}
|
|
|
|
|
</Typography>
|
|
|
|
|
) : null}
|
|
|
|
|
</Paper>
|
|
|
|
|
{repo?.clone_url ? (
|
|
|
|
|
<>
|
|
|
|
|
<Button size="small" variant="outlined" onClick={(event) => setCodeAnchor(event.currentTarget)}>
|
|
|
|
|
Clone
|
|
|
|
|
</Button>
|
|
|
|
|
<Popover
|
|
|
|
|
open={Boolean(codeAnchor)}
|
|
|
|
|
anchorEl={codeAnchor}
|
|
|
|
|
onClose={() => setCodeAnchor(null)}
|
|
|
|
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
|
|
|
|
|
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
|
|
|
|
|
PaperProps={{
|
|
|
|
|
sx: {
|
|
|
|
|
p: '8px !important',
|
|
|
|
|
minWidth: 480,
|
|
|
|
|
border: '1px solid',
|
|
|
|
|
borderColor: 'divider',
|
|
|
|
|
boxShadow: 'none'
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Box sx={{ display: 'grid', gap: 1 }}>
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
|
|
|
Clone
|
|
|
|
|
</Typography>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 48 }}>
|
|
|
|
|
/git
|
|
|
|
|
</Typography>
|
|
|
|
|
<TextField
|
|
|
|
|
value={buildCloneURL(repo.clone_url || '')}
|
|
|
|
|
size="small"
|
|
|
|
|
InputProps={{ readOnly: true }}
|
|
|
|
|
sx={{ flex: 1 }}
|
|
|
|
|
/>
|
|
|
|
|
<Tooltip title="Copy /git URL">
|
|
|
|
|
<IconButton onClick={() => copyText(buildCloneURL(repo.clone_url || ''))} size="small">
|
|
|
|
|
<ContentCopyIcon fontSize="small" />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Box>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 48 }}>
|
|
|
|
|
/git-id
|
|
|
|
|
</Typography>
|
|
|
|
|
<TextField
|
|
|
|
|
value={`${window.location.origin}/git-id/${repo.id}.git`}
|
|
|
|
|
size="small"
|
|
|
|
|
InputProps={{ readOnly: true }}
|
|
|
|
|
sx={{ flex: 1 }}
|
|
|
|
|
/>
|
|
|
|
|
<Tooltip title="Copy /git-id URL">
|
|
|
|
|
<IconButton onClick={() => copyText(`${window.location.origin}/git-id/${repo.id}.git`)} size="small">
|
|
|
|
|
<ContentCopyIcon fontSize="small" />
|
|
|
|
|
</IconButton>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</Box>
|
|
|
|
|
</Box>
|
|
|
|
|
</Popover>
|
|
|
|
|
</>
|
|
|
|
|
) : null}
|
|
|
|
|
</Box>
|
|
|
|
|
<Divider sx={{ mb: 1 }} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<SplitPanel defaultRatio={0.25} left={(
|
|
|
|
|
<TintedPanel>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0 }}>
|
|
|
|
|
<IconButton
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={() => handleBreadcrumb('')}
|
|
|
|
|
aria-label="Root"
|
|
|
|
|
sx={{ p: 0.25 }}
|
|
|
|
|
>
|
|
|
|
|
<HomeOutlinedIcon fontSize="small" />
|
|
|
|
|
</IconButton>
|
|
|
|
|
<Typography variant="body2" color="text.secondary" sx={{ mx: 0.25 }}>
|
|
|
|
|
/
|
|
|
|
|
</Typography>
|
|
|
|
|
{pathParts.map((part, idx) => {
|
|
|
|
|
const nextPath = pathParts.slice(0, idx + 1).join('/')
|
|
|
|
|
return (
|
|
|
|
|
<Box key={nextPath} sx={{ display: 'flex', alignItems: 'center', gap: 0 }}>
|
|
|
|
|
{idx > 0 ? (
|
|
|
|
|
<Typography variant="body2" color="text.secondary" sx={{ mx: 0.25 }}>
|
|
|
|
|
/
|
|
|
|
|
</Typography>
|
|
|
|
|
) : null}
|
|
|
|
|
<Button
|
|
|
|
|
size="small"
|
|
|
|
|
onClick={() => handleBreadcrumb(nextPath)}
|
|
|
|
|
sx={{ textTransform: 'none', minWidth: 0, px: 0.5 }}
|
|
|
|
|
>
|
|
|
|
|
{part}
|
|
|
|
|
</Button>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'stretch', gap: 1, mb: 1 }}>
|
|
|
|
|
<Paper sx={{ p: 1, display: 'flex', alignItems: 'center', gap: 1, minWidth: 240, backgroundColor: 'transparent' }}>
|
|
|
|
|
<AccountTreeIcon />
|
|
|
|
|
{branches.length ? (
|
|
|
|
|
<Autocomplete
|
|
|
|
|
freeSolo
|
|
|
|
|
options={branches}
|
|
|
|
|
value={ref}
|
|
|
|
|
inputValue={ref}
|
|
|
|
|
onChange={(_, value) => setRef(String(value || ''))}
|
|
|
|
|
onInputChange={(_, value, reason) => {
|
|
|
|
|
if (reason === 'input') setRef(value)
|
|
|
|
|
if (reason === 'clear') setRef('')
|
|
|
|
|
}}
|
|
|
|
|
renderInput={(params) => <TextField {...params} size="small" placeholder="branch or ref" />}
|
|
|
|
|
slotProps={{
|
|
|
|
|
popper: { sx: { zIndex: 1400 } }
|
|
|
|
|
}}
|
|
|
|
|
sx={{ minWidth: 180 }}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
|
|
|
|
{renderClonePopover()}
|
|
|
|
|
</Paper>
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
<PageAlert severity="warning" message={treeError} onClose={() => setTreeError(null)} sx={{ mb: 1 }} />
|
|
|
|
|
<List dense>
|
|
|
|
|
<Box sx={{ px: 1, pb: 1 }}>
|
|
|
|
|
<TextField
|
|
|
|
|
size="small"
|
|
|
|
|
placeholder="Search files"
|
|
|
|
|
value={fileQuery}
|
|
|
|
|
onChange={(event) => setFileQuery(event.target.value)}
|
|
|
|
|
fullWidth
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
|
|
|
|
{path ? (
|
|
|
|
|
<ListItem disablePadding>
|
|
|
|
|
<ListItemButton onClick={handleBack}>
|
|
|
|
|
<ListItemText
|
|
|
|
|
primary={
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<FolderIcon fontSize="small" color="info" />
|
|
|
|
|
<Typography variant="body2">..</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</Box>
|
|
|
|
|
</Box>
|
|
|
|
|
<PageAlert severity="warning" message={treeError} onClose={() => setTreeError(null)} sx={{ mb: 1 }} />
|
|
|
|
|
<List dense>
|
|
|
|
|
<Box sx={{ px: 1, pb: 1 }}>
|
|
|
|
|
<TextField
|
|
|
|
|
size="small"
|
|
|
|
|
placeholder="Search files"
|
|
|
|
|
value={fileQuery}
|
|
|
|
|
onChange={(event) => setFileQuery(event.target.value)}
|
|
|
|
|
fullWidth
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
|
|
|
|
{path ? (
|
|
|
|
|
<ListItem disablePadding>
|
|
|
|
|
<ListItemButton onClick={handleBack}>
|
|
|
|
|
<ListItemText
|
|
|
|
|
primary={
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<FolderIcon fontSize="small" color="info" />
|
|
|
|
|
<Typography variant="body2">..</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</ListItemButton>
|
|
|
|
|
</ListItem>
|
|
|
|
|
) : null}
|
|
|
|
|
</ListItemButton>
|
|
|
|
|
</ListItem>
|
|
|
|
|
) : null}
|
|
|
|
|
{[...filteredTree]
|
|
|
|
|
.sort((a, b) => {
|
|
|
|
|
if (a.type !== b.type) return a.type === 'dir' ? -1 : 1
|
|
|
|
|
return a.name.localeCompare(b.name)
|
|
|
|
|
})
|
|
|
|
|
.map((entry) => (
|
|
|
|
|
<ListItem key={entry.path} disablePadding>
|
|
|
|
|
<ListItemButton onClick={() => handleOpen(entry)}>
|
|
|
|
|
<ListItemText
|
|
|
|
|
primary={
|
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<Tooltip title={entry.type === 'dir' ? 'directory' : 'file'}>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
|
|
|
{entry.type === 'dir' ? (
|
|
|
|
|
<FolderIcon fontSize="small" color="info" />
|
|
|
|
|
) : (
|
|
|
|
|
<InsertDriveFileIcon fontSize="small" color="info" />
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
<Typography variant="body2">{entry.name}</Typography>
|
|
|
|
|
<ListItem key={entry.path} disablePadding>
|
|
|
|
|
<ListItemButton onClick={() => handleOpen(entry)}>
|
|
|
|
|
<ListItemText
|
|
|
|
|
primary={
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
<Tooltip title={entry.type === 'dir' ? 'directory' : 'file'}>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
|
|
|
{entry.type === 'dir' ? (
|
|
|
|
|
<FolderIcon fontSize="small" color="info" />
|
|
|
|
|
) : (
|
|
|
|
|
<InsertDriveFileIcon fontSize="small" color="info" />
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
<Box />
|
|
|
|
|
</Box>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</ListItemButton>
|
|
|
|
|
</ListItem>
|
|
|
|
|
))}
|
|
|
|
|
</List>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
<Typography variant="body2">{entry.name}</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</ListItemButton>
|
|
|
|
|
</ListItem>
|
|
|
|
|
))}
|
|
|
|
|
</List>
|
|
|
|
|
</TintedPanel>
|
|
|
|
|
)} right={(
|
|
|
|
|
<TintedPanel>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
|
|
|
|
{renderPathBreadcrumb()}
|
|
|
|
|
</Box>
|
|
|
|
|
{selectedFile ? (
|
|
|
|
|
<Box sx={{ mt: 0 }}>
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
|
|
@@ -837,9 +855,6 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
) : null}
|
|
|
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
|
|
|
{selectedFile}
|
|
|
|
|
</Typography>
|
|
|
|
|
</Box>
|
|
|
|
|
</Box>
|
|
|
|
|
{previewTab === 'content' ? (
|
|
|
|
|