|
|
@@ -3,12 +3,15 @@ import { lazy, Suspense, useEffect, useRef, useState } from 'react'
|
|
|
|
import { useParams, useSearchParams } from 'react-router-dom'
|
|
|
|
import { useParams, useSearchParams } from 'react-router-dom'
|
|
|
|
import { api, Project, Repo, RepoCommit, RepoTreeEntry } from '../api'
|
|
|
|
import { api, Project, Repo, RepoCommit, RepoTreeEntry } from '../api'
|
|
|
|
import Autocomplete from '../components/Autocomplete'
|
|
|
|
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 ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
|
|
|
import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'
|
|
|
|
import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'
|
|
|
|
import FolderIcon from '@mui/icons-material/Folder'
|
|
|
|
import FolderIcon from '@mui/icons-material/Folder'
|
|
|
|
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'
|
|
|
|
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'
|
|
|
|
import FileDownloadIcon from '@mui/icons-material/FileDownload'
|
|
|
|
import FileDownloadIcon from '@mui/icons-material/FileDownload'
|
|
|
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
|
|
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
|
|
|
|
|
|
|
import TerminalIcon from '@mui/icons-material/Terminal'
|
|
|
|
import LazyCodeBlock from '../components/LazyCodeBlock'
|
|
|
|
import LazyCodeBlock from '../components/LazyCodeBlock'
|
|
|
|
import PageAlert from '../components/PageAlert'
|
|
|
|
import PageAlert from '../components/PageAlert'
|
|
|
|
import ContextToolbar from '../components/ContextToolbar'
|
|
|
|
import ContextToolbar from '../components/ContextToolbar'
|
|
|
@@ -167,37 +170,39 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, [repoId, ref, path, branches, treeReloadTick])
|
|
|
|
}, [repoId, ref, path, branches, treeReloadTick])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const clearReadme = () => {
|
|
|
|
|
|
|
|
setReadmeContent('')
|
|
|
|
|
|
|
|
setReadmeKind('')
|
|
|
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
useEffect(() => {
|
|
|
|
if (!repoId || !ref) {
|
|
|
|
if (!repoId || !ref) {
|
|
|
|
setReadmeContent('')
|
|
|
|
clearReadme()
|
|
|
|
setReadmeKind('')
|
|
|
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!repo) return
|
|
|
|
if (!repo) return
|
|
|
|
if (repo && repo.type && repo.type !== 'git') {
|
|
|
|
|
|
|
|
setReadmeContent('')
|
|
|
|
if (repo.type && repo.type !== 'git') {
|
|
|
|
setReadmeKind('')
|
|
|
|
clearReadme()
|
|
|
|
setReadmePath('')
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedFile) {
|
|
|
|
if (selectedFile) {
|
|
|
|
setReadmeContent('')
|
|
|
|
clearReadme()
|
|
|
|
setReadmeKind('')
|
|
|
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const entries = tree || []
|
|
|
|
const entries = tree || []
|
|
|
|
let readme = entries.find((entry) => entry.name.toLowerCase() === 'readme.md')
|
|
|
|
let readme = entries.find((entry) => entry.name.toLowerCase() === 'readme.md')
|
|
|
|
let kind: 'markdown' | 'text' | '' = readme ? 'markdown' : ''
|
|
|
|
let kind: 'markdown' | 'text' | '' = (readme? 'markdown' : '')
|
|
|
|
if (!readme) {
|
|
|
|
if (!readme) {
|
|
|
|
readme = entries.find((entry) => entry.name.toLowerCase() === 'readme.txt')
|
|
|
|
readme = entries.find((entry) => entry.name.toLowerCase() === 'readme.txt')
|
|
|
|
kind = readme ? 'text' : ''
|
|
|
|
kind = readme ? 'text' : ''
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!readme || readme.type !== 'file') {
|
|
|
|
if (!readme || readme.type !== 'file') {
|
|
|
|
setReadmeContent('')
|
|
|
|
clearReadme()
|
|
|
|
setReadmeKind('')
|
|
|
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (readmePath === readme.path && readmeKind === kind) return
|
|
|
|
if (readmePath === readme.path && readmeKind === kind) return
|
|
|
@@ -208,9 +213,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
setReadmePath(readme.path)
|
|
|
|
setReadmePath(readme.path)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
.catch(() => {
|
|
|
|
setReadmeContent('')
|
|
|
|
clearReadme()
|
|
|
|
setReadmeKind('')
|
|
|
|
|
|
|
|
setReadmePath('')
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, [repoId, ref, tree, selectedFile, readmePath, readmeKind])
|
|
|
|
}, [repoId, ref, tree, selectedFile, readmePath, readmeKind])
|
|
|
|
|
|
|
|
|
|
|
@@ -236,6 +239,7 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
setImagePreviewRef(ref)
|
|
|
|
setImagePreviewRef(ref)
|
|
|
|
if (!isImageFile(entry.path)) {
|
|
|
|
if (!isImageFile(entry.path)) {
|
|
|
|
try {
|
|
|
|
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)
|
|
|
|
const blob = await api.getRepoBlob(repoId, ref || undefined, entry.path)
|
|
|
|
setContent(blob.content)
|
|
|
|
setContent(blob.content)
|
|
|
|
} catch {
|
|
|
|
} catch {
|
|
|
@@ -539,59 +543,13 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
return match[1]
|
|
|
|
return match[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderClonePopover = () => {
|
|
|
|
|
|
|
|
if (!repo?.clone_url) return null
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<ProjectPageFrame
|
|
|
|
|
|
|
|
projectId={projectId ?? ''}
|
|
|
|
|
|
|
|
project={project}
|
|
|
|
|
|
|
|
currentLabel={repo?.name || 'Repository'}
|
|
|
|
|
|
|
|
contextToolbar={
|
|
|
|
|
|
|
|
<ContextToolbar
|
|
|
|
|
|
|
|
kind="Repository"
|
|
|
|
|
|
|
|
label={repo?.name || 'Repository'}
|
|
|
|
|
|
|
|
nav={projectId && repoId ? <RepoSubNav projectId={projectId} repoId={repoId} branchRef={ref} repoType={repo?.type} inline /> : null}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<Divider sx={{ mb: 1 }} />
|
|
|
|
|
|
|
|
{loadError ? (
|
|
|
|
|
|
|
|
<Typography variant="body2" color="error" sx={{ mb: 1 }}>
|
|
|
|
|
|
|
|
{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)}>
|
|
|
|
<IconButton onClick={(event) => setCodeAnchor(event.currentTarget)}>
|
|
|
|
Clone
|
|
|
|
<CodeIcon />
|
|
|
|
</Button>
|
|
|
|
</IconButton>
|
|
|
|
<Popover
|
|
|
|
<Popover
|
|
|
|
open={Boolean(codeAnchor)}
|
|
|
|
open={Boolean(codeAnchor)}
|
|
|
|
anchorEl={codeAnchor}
|
|
|
|
anchorEl={codeAnchor}
|
|
|
@@ -609,9 +567,12 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<Box sx={{ display: 'grid', gap: 1 }}>
|
|
|
|
<Box sx={{ display: 'grid', gap: 1 }}>
|
|
|
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
|
|
|
<TerminalIcon />
|
|
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
|
|
Clone
|
|
|
|
Clone
|
|
|
|
</Typography>
|
|
|
|
</Typography>
|
|
|
|
|
|
|
|
</Box>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 48 }}>
|
|
|
|
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 48 }}>
|
|
|
|
/git
|
|
|
|
/git
|
|
|
@@ -647,13 +608,11 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
</Popover>
|
|
|
|
</Popover>
|
|
|
|
</>
|
|
|
|
</>
|
|
|
|
) : null}
|
|
|
|
)
|
|
|
|
</Box>
|
|
|
|
}
|
|
|
|
<Divider sx={{ mb: 1 }} />
|
|
|
|
|
|
|
|
<SplitPanel defaultRatio={0.25} left={(
|
|
|
|
const renderPathBreadcrumb = () => {
|
|
|
|
<TintedPanel>
|
|
|
|
return (<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0 }}>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
|
|
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 0 }}>
|
|
|
|
|
|
|
|
<IconButton
|
|
|
|
<IconButton
|
|
|
|
size="small"
|
|
|
|
size="small"
|
|
|
|
onClick={() => handleBreadcrumb('')}
|
|
|
|
onClick={() => handleBreadcrumb('')}
|
|
|
@@ -684,8 +643,67 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
</Box>
|
|
|
|
</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>
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
</Box>)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<ProjectPageFrame
|
|
|
|
|
|
|
|
projectId={projectId ?? ''}
|
|
|
|
|
|
|
|
project={project}
|
|
|
|
|
|
|
|
currentLabel={repo?.name || 'Repository'}
|
|
|
|
|
|
|
|
contextToolbar={
|
|
|
|
|
|
|
|
<ContextToolbar
|
|
|
|
|
|
|
|
kind="Repository"
|
|
|
|
|
|
|
|
label={repo?.name || 'Repository'}
|
|
|
|
|
|
|
|
nav={projectId && repoId ? <RepoSubNav projectId={projectId} repoId={repoId} branchRef={ref} repoType={repo?.type} inline /> : null}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<Divider sx={{ mb: 1 }} />
|
|
|
|
|
|
|
|
{loadError ? (
|
|
|
|
|
|
|
|
<Typography variant="body2" color="error" sx={{ mb: 1 }}>
|
|
|
|
|
|
|
|
{loadError}
|
|
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<SplitPanel defaultRatio={0.25} left={(
|
|
|
|
|
|
|
|
<TintedPanel>
|
|
|
|
|
|
|
|
<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>
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
|
|
<PageAlert severity="warning" message={treeError} onClose={() => setTreeError(null)} sx={{ mb: 1 }} />
|
|
|
|
<PageAlert severity="warning" message={treeError} onClose={() => setTreeError(null)} sx={{ mb: 1 }} />
|
|
|
|
<List dense>
|
|
|
|
<List dense>
|
|
|
|
<Box sx={{ px: 1, pb: 1 }}>
|
|
|
|
<Box sx={{ px: 1, pb: 1 }}>
|
|
|
@@ -721,7 +739,6 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
<ListItemButton onClick={() => handleOpen(entry)}>
|
|
|
|
<ListItemButton onClick={() => handleOpen(entry)}>
|
|
|
|
<ListItemText
|
|
|
|
<ListItemText
|
|
|
|
primary={
|
|
|
|
primary={
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 1 }}>
|
|
|
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
|
|
<Tooltip title={entry.type === 'dir' ? 'directory' : 'file'}>
|
|
|
|
<Tooltip title={entry.type === 'dir' ? 'directory' : 'file'}>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
|
@@ -734,8 +751,6 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
</Tooltip>
|
|
|
|
</Tooltip>
|
|
|
|
<Typography variant="body2">{entry.name}</Typography>
|
|
|
|
<Typography variant="body2">{entry.name}</Typography>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
<Box />
|
|
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</ListItemButton>
|
|
|
|
</ListItemButton>
|
|
|
@@ -745,6 +760,9 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
</TintedPanel>
|
|
|
|
</TintedPanel>
|
|
|
|
)} right={(
|
|
|
|
)} right={(
|
|
|
|
<TintedPanel>
|
|
|
|
<TintedPanel>
|
|
|
|
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
|
|
|
|
|
|
|
{renderPathBreadcrumb()}
|
|
|
|
|
|
|
|
</Box>
|
|
|
|
{selectedFile ? (
|
|
|
|
{selectedFile ? (
|
|
|
|
<Box sx={{ mt: 0 }}>
|
|
|
|
<Box sx={{ mt: 0 }}>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
|
@@ -837,9 +855,6 @@ export default function RepoGitDetailPage(props: RepoGitDetailPageProps) {
|
|
|
|
</>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) : null}
|
|
|
|
) : null}
|
|
|
|
<Typography variant="subtitle2" color="text.secondary">
|
|
|
|
|
|
|
|
{selectedFile}
|
|
|
|
|
|
|
|
</Typography>
|
|
|
|
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
</Box>
|
|
|
|
{previewTab === 'content' ? (
|
|
|
|
{previewTab === 'content' ? (
|
|
|
|