code and layout refactoring

This commit is contained in:
2024-03-26 10:10:27 +01:00
parent 82ccb68a56
commit c6e48f4baa
11 changed files with 143 additions and 147 deletions

View File

@@ -207,7 +207,7 @@ const DownloadDialog: FC<Props> = ({ open, onClose, onDownloadStart }) => {
backgroundColor: (theme) => theme.palette.background.default, backgroundColor: (theme) => theme.palette.background.default,
minHeight: (theme) => `calc(99vh - ${theme.mixins.toolbar.minHeight}px)` minHeight: (theme) => `calc(99vh - ${theme.mixins.toolbar.minHeight}px)`
}}> }}>
<Container sx={{ my: 4 }} > <Container sx={{ my: 4 }}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<Paper <Paper

View File

@@ -4,10 +4,10 @@ import { loadingDownloadsState } from '../atoms/downloads'
import { listViewState } from '../atoms/settings' import { listViewState } from '../atoms/settings'
import { loadingAtom } from '../atoms/ui' import { loadingAtom } from '../atoms/ui'
import DownloadsCardView from './DownloadsCardView' import DownloadsCardView from './DownloadsCardView'
import DownloadsListView from './DownloadsTableView' import DownloadsTableView from './DownloadsTableView'
const Downloads: React.FC = () => { const Downloads: React.FC = () => {
const listView = useRecoilValue(listViewState) const tableView = useRecoilValue(listViewState)
const loadingDownloads = useRecoilValue(loadingDownloadsState) const loadingDownloads = useRecoilValue(loadingDownloadsState)
const [isLoading, setIsLoading] = useRecoilState(loadingAtom) const [isLoading, setIsLoading] = useRecoilState(loadingAtom)
@@ -20,15 +20,9 @@ const Downloads: React.FC = () => {
setIsLoading(false) setIsLoading(false)
}, [loadingDownloads, isLoading]) }, [loadingDownloads, isLoading])
if (listView) { if (tableView) return <DownloadsTableView />
return (
<DownloadsListView />
)
}
return ( return <DownloadsCardView />
<DownloadsCardView />
)
} }
export default Downloads export default Downloads

View File

@@ -16,10 +16,10 @@ const DownloadsCardView: React.FC = () => {
const abort = (id: string) => client.kill(id) const abort = (id: string) => client.kill(id)
return ( return (
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}> <Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12, xl: 12 }} pt={2}>
{ {
downloads.map(download => ( downloads.map(download => (
<Grid item xs={4} sm={8} md={6} key={download.id}> <Grid item xs={4} sm={8} md={6} xl={4} key={download.id}>
<DownloadCard <DownloadCard
download={download} download={download}
onStop={() => abort(download.id)} onStop={() => abort(download.id)}

View File

@@ -45,89 +45,83 @@ const DownloadsTableView: React.FC = () => {
const abort = (id: string) => client.kill(id) const abort = (id: string) => client.kill(id)
return ( return (
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}> <TableContainer
<Grid item xs={12}> sx={{ minHeight: '80vh', mt: 4 }}
<TableContainer hidden={downloads.length === 0}
component={Paper} >
sx={{ minHeight: '80vh' }} <Table size="small">
elevation={2} <TableHead>
hidden={downloads.length === 0} <TableRow>
> <TableCell width={8}>
<Table size="small"> <Typography fontWeight={500} fontSize={13}>Status</Typography>
<TableHead> </TableCell>
<TableRow> <TableCell>
<TableCell width={8}> <Typography fontWeight={500} fontSize={13}>Title</Typography>
<Typography fontWeight={500} fontSize={13}>Status</Typography> </TableCell>
</TableCell> <TableCell align="right">
<Typography fontWeight={500} fontSize={13}>Speed</Typography>
</TableCell>
<TableCell align="center" width={200}>
<Typography fontWeight={500} fontSize={13}>Progress</Typography>
</TableCell>
<TableCell align="right">
<Typography fontWeight={500} fontSize={13}>Size</Typography>
</TableCell>
<TableCell align="right" width={180}>
<Typography fontWeight={500} fontSize={13}>Added on</Typography>
</TableCell>
<TableCell align="right" width={8}>
<Typography fontWeight={500} fontSize={13}>Actions</Typography>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{
downloads.map(download => (
<TableRow key={download.id}>
<TableCell> <TableCell>
<Typography fontWeight={500} fontSize={13}>Title</Typography> {download.progress.percentage === '-1'
? <DownloadDoneIcon color="primary" />
: <DownloadIcon color="primary" />
}
</TableCell>
<TableCell>{download.info.title}</TableCell>
<TableCell align="right">{formatSpeedMiB(download.progress.speed)}</TableCell>
<TableCell align="right">
<LinearProgressWithLabel
sx={{ height: '16px' }}
value={
download.progress.percentage === '-1'
? 100
: Number(download.progress.percentage.replace('%', ''))
}
variant={
download.progress.process_status === 0
? 'indeterminate'
: 'determinate'
}
color={download.progress.percentage === '-1' ? 'primary' : 'primary'}
/>
</TableCell>
<TableCell align="right">{formatSize(download.info.filesize_approx ?? 0)}</TableCell>
<TableCell align="right">
{new Date(download.info.created_at).toLocaleString()}
</TableCell> </TableCell>
<TableCell align="right"> <TableCell align="right">
<Typography fontWeight={500} fontSize={13}>Speed</Typography> <IconButton
</TableCell> size="small"
<TableCell align="center" width={200}> onClick={() => abort(download.id)}
<Typography fontWeight={500} fontSize={13}>Progress</Typography> >
</TableCell> {download.progress.percentage === '-1' ? <DeleteIcon /> : <StopCircleIcon />}
<TableCell align="right">
<Typography fontWeight={500} fontSize={13}>Size</Typography> </IconButton>
</TableCell>
<TableCell align="right" width={180}>
<Typography fontWeight={500} fontSize={13}>Added on</Typography>
</TableCell>
<TableCell align="right" width={8}>
<Typography fontWeight={500} fontSize={13}>Actions</Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableHead> ))
<TableBody> }
{ </TableBody>
downloads.map(download => ( </Table>
<TableRow key={download.id}> </TableContainer>
<TableCell>
{download.progress.percentage === '-1'
? <DownloadDoneIcon color="primary" />
: <DownloadIcon color="primary" />
}
</TableCell>
<TableCell>{download.info.title}</TableCell>
<TableCell align="right">{formatSpeedMiB(download.progress.speed)}</TableCell>
<TableCell align="right">
<LinearProgressWithLabel
sx={{ height: '16px' }}
value={
download.progress.percentage === '-1'
? 100
: Number(download.progress.percentage.replace('%', ''))
}
variant={
download.progress.process_status === 0
? 'indeterminate'
: 'determinate'
}
color={download.progress.percentage === '-1' ? 'primary' : 'primary'}
/>
</TableCell>
<TableCell align="right">{formatSize(download.info.filesize_approx ?? 0)}</TableCell>
<TableCell align="right">
{new Date(download.info.created_at).toLocaleString()}
</TableCell>
<TableCell align="right">
<IconButton
size="small"
onClick={() => abort(download.id)}
>
{download.progress.percentage === '-1' ? <DeleteIcon /> : <StopCircleIcon />}
</IconButton>
</TableCell>
</TableRow>
))
}
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
) )
} }

View File

@@ -1,4 +1,3 @@
import { Box, Container, Paper, Typography } from '@mui/material'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { useRecoilValue } from 'recoil' import { useRecoilValue } from 'recoil'
import { serverURL } from '../atoms/settings' import { serverURL } from '../atoms/settings'
@@ -7,14 +6,15 @@ import { useI18n } from '../hooks/useI18n'
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
const LogTerminal: React.FC = () => { const LogTerminal: React.FC = () => {
const [logBuffer, setLogBuffer] = useState<string[]>([])
const [isConnecting, setIsConnecting] = useState(true)
const boxRef = useRef<HTMLDivElement>(null)
const serverAddr = useRecoilValue(serverURL) const serverAddr = useRecoilValue(serverURL)
const { i18n } = useI18n() const { i18n } = useI18n()
const [logBuffer, setLogBuffer] = useState<string[]>([])
const boxRef = useRef<HTMLDivElement>(null)
const eventSource = useMemo( const eventSource = useMemo(
() => new EventSource(`${serverAddr}/log/sse?token=${token}`), () => new EventSource(`${serverAddr}/log/sse?token=${token}`),
[serverAddr] [serverAddr]
@@ -23,7 +23,7 @@ const LogTerminal: React.FC = () => {
useEffect(() => { useEffect(() => {
eventSource.addEventListener('log', event => { eventSource.addEventListener('log', event => {
const msg: string[] = JSON.parse(event.data) const msg: string[] = JSON.parse(event.data)
setLogBuffer(buff => [...buff, ...msg].slice(-100)) setLogBuffer(buff => [...buff, ...msg].slice(-500))
boxRef.current?.scrollTo(0, boxRef.current.scrollHeight) boxRef.current?.scrollTo(0, boxRef.current.scrollHeight)
}) })
@@ -32,6 +32,10 @@ const LogTerminal: React.FC = () => {
return () => eventSource.close() return () => eventSource.close()
}, [eventSource]) }, [eventSource])
useEffect(() => {
eventSource.onopen = () => setIsConnecting(false)
}, [eventSource])
const logEntryStyle = (data: string) => { const logEntryStyle = (data: string) => {
const sx = {} const sx = {}
@@ -46,41 +50,33 @@ const LogTerminal: React.FC = () => {
} }
return ( return (
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<Paper <div
sx={{ ref={boxRef}
p: 2.5, style={{
display: 'flex', fontFamily: 'Roboto Mono',
flexDirection: 'column', height: '70.5vh',
}} overflowY: 'auto',
> overflowX: 'auto',
<Typography py={1} variant="h5" color="primary"> fontSize: '13.5px',
{i18n.t('logsTitle')} fontWeight: '600',
</Typography> backgroundColor: 'black',
<Box color: 'white',
ref={boxRef} padding: '0.5rem',
sx={{ borderRadius: '0.25rem'
fontFamily: 'Roboto Mono', }}
height: '70.5vh', >
overflowY: 'auto', {isConnecting ? <div>{'Connecting...'}</div> : <div>{'Connected!'}</div>}
overflowX: 'auto',
fontSize: '13.5px', {logBuffer.length === 0 && <div>{i18n.t('awaitingLogs')}</div>}
fontWeight: '600',
backgroundColor: 'black', {logBuffer.map((log, idx) => (
color: 'white', <div key={idx} style={logEntryStyle(log)}>
padding: '0.5rem', {log}
borderRadius: '0.25rem' </div>
}} ))}
> </div>
{logBuffer.length === 0 && <Box >{i18n.t('awaitingLogs')}</Box>}
{logBuffer.map((log, idx) => (
<Box key={idx} sx={logEntryStyle(log)}>
{log}
</Box>
))}
</Box>
</Paper>
</Container >
) )
} }

View File

@@ -182,7 +182,7 @@ export default function Downloaded() {
return ( return (
<Container <Container
maxWidth="lg" maxWidth="xl"
sx={{ mt: 4, mb: 4, height: '100%' }} sx={{ mt: 4, mb: 4, height: '100%' }}
onClick={() => setShowMenu(false)} onClick={() => setShowMenu(false)}
> >

View File

@@ -8,7 +8,7 @@ import Splash from '../components/Splash'
export default function Home() { export default function Home() {
return ( return (
<Container maxWidth="lg" sx={{ mt: 2, mb: 8 }}> <Container maxWidth="xl" sx={{ mt: 2, mb: 8 }}>
<LoadingBackdrop /> <LoadingBackdrop />
<Splash /> <Splash />
<Downloads /> <Downloads />

View File

@@ -134,12 +134,12 @@ export default function Settings() {
} }
return ( return (
<Container maxWidth="lg" sx={{ mt: 4, mb: 8 }}> <Container maxWidth="xl" sx={{ mt: 4, mb: 8 }}>
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={12} md={12} lg={12}> <Grid item xs={12} md={12} lg={12}>
<Paper <Paper
sx={{ sx={{
p: 2, p: 2.5,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
minHeight: 240, minHeight: 240,

View File

@@ -1,8 +1,25 @@
import { Container, Paper, Typography } from '@mui/material'
import LogTerminal from '../components/LogTerminal' import LogTerminal from '../components/LogTerminal'
import { useI18n } from '../hooks/useI18n'
const Terminal: React.FC = () => { const Terminal: React.FC = () => {
const { i18n } = useI18n()
return ( return (
<LogTerminal /> <Container maxWidth="xl" sx={{ mt: 4, mb: 4 }}>
<Paper
sx={{
p: 2.5,
display: 'flex',
flexDirection: 'column',
}}
>
<Typography pb={2} variant="h5" color="primary">
{i18n.t('logsTitle')}
</Typography>
<LogTerminal />
</Paper>
</Container >
) )
} }

View File

@@ -105,8 +105,10 @@ func RunBlocking(cfg *RunConfig) {
go gracefulShutdown(srv, &mdb) go gracefulShutdown(srv, &mdb)
go autoPersist(time.Minute*5, &mdb, logger) go autoPersist(time.Minute*5, &mdb, logger)
network := "tcp" var (
address := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) network = "tcp"
address = fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
)
if strings.HasPrefix(cfg.Host, "/") { if strings.HasPrefix(cfg.Host, "/") {
network = "unix" network = "unix"
@@ -120,6 +122,7 @@ func RunBlocking(cfg *RunConfig) {
} }
logger.Info("yt-dlp-webui started", slog.String("address", address)) logger.Info("yt-dlp-webui started", slog.String("address", address))
if err := srv.Serve(listener); err != nil { if err := srv.Serve(listener); err != nil {
logger.Warn("http server stopped", slog.String("err", err.Error())) logger.Warn("http server stopped", slog.String("err", err.Error()))
} }

View File

@@ -1,8 +1,6 @@
package utils package utils
import ( import (
"crypto/sha256"
"encoding/hex"
"io/fs" "io/fs"
"regexp" "regexp"
"strings" "strings"
@@ -21,9 +19,3 @@ func IsValidEntry(d fs.DirEntry) bool {
!strings.HasSuffix(d.Name(), ".part") && !strings.HasSuffix(d.Name(), ".part") &&
!strings.HasSuffix(d.Name(), ".ytdl") !strings.HasSuffix(d.Name(), ".ytdl")
} }
func ShaSumString(path string) string {
h := sha256.New()
h.Write([]byte(path))
return hex.EncodeToString(h.Sum(nil))
}