code refactoring

This commit is contained in:
2023-06-23 11:41:55 +02:00
parent e9df173aef
commit 765b36cc98
9 changed files with 229 additions and 133 deletions

View File

@@ -32,8 +32,10 @@ import AppBar from './components/AppBar'
import Drawer from './components/Drawer' import Drawer from './components/Drawer'
import Logout from './components/Logout' import Logout from './components/Logout'
import { formatGiB } from './utils'
import ThemeToggler from './components/ThemeToggler' import ThemeToggler from './components/ThemeToggler'
import I18nProvider from './providers/i18nProvider'
import RPCCLientProvider from './providers/rpcClientProvider'
import { formatGiB } from './utils'
export default function Layout() { export default function Layout() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@@ -59,123 +61,131 @@ export default function Layout() {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<Box sx={{ display: 'flex' }}> <I18nProvider>
<CssBaseline /> <RPCCLientProvider>
<AppBar position="absolute" open={open}> <Box sx={{ display: 'flex' }}>
<Toolbar sx={{ pr: '24px' }}> <CssBaseline />
<IconButton <AppBar position="absolute" open={open}>
edge="start" <Toolbar sx={{ pr: '24px' }}>
color="inherit" <IconButton
aria-label="open drawer" edge="start"
onClick={toggleDrawer} color="inherit"
sx={{ aria-label="open drawer"
marginRight: '36px', onClick={toggleDrawer}
...(open && { display: 'none' }), sx={{
}} marginRight: '36px',
> ...(open && { display: 'none' }),
<Menu /> }}
</IconButton> >
<Typography <Menu />
component="h1" </IconButton>
variant="h6" <Typography
color="inherit" component="h1"
noWrap variant="h6"
sx={{ flexGrow: 1 }} color="inherit"
> noWrap
yt-dlp WebUI sx={{ flexGrow: 1 }}
</Typography> >
{ yt-dlp WebUI
status.freeSpace ? </Typography>
{
status.freeSpace ?
<div style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
}}>
<Storage />
<span>
&nbsp;{formatGiB(status.freeSpace)}&nbsp;
</span>
</div>
: null
}
<div style={{ <div style={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
flexWrap: 'wrap', flexWrap: 'wrap',
}}> }}>
<Storage /> <SettingsEthernet />
<span>&nbsp;{formatGiB(status.freeSpace)}&nbsp;</span> <span>
&nbsp;{status.connected ? settings.serverAddr : 'not connected'}
</span>
</div> </div>
: null </Toolbar>
} </AppBar>
<div style={{ <Drawer variant="permanent" open={open}>
display: 'flex', <Toolbar
alignItems: 'center', sx={{
flexWrap: 'wrap', display: 'flex',
}}> alignItems: 'center',
<SettingsEthernet /> justifyContent: 'flex-end',
<span>&nbsp;{status.connected ? settings.serverAddr : 'not connected'}</span> px: [1],
</div> }}
</Toolbar> >
</AppBar> <IconButton onClick={toggleDrawer}>
<Drawer variant="permanent" open={open}> <ChevronLeft />
<Toolbar </IconButton>
sx={{ </Toolbar>
display: 'flex', <Divider />
alignItems: 'center', <List component="nav">
justifyContent: 'flex-end', <Link to={'/'} style={
px: [1], {
}} textDecoration: 'none',
> color: mode === 'dark' ? '#ffffff' : '#000000DE'
<IconButton onClick={toggleDrawer}> }
<ChevronLeft /> }>
</IconButton> <ListItemButton disabled={status.downloading}>
</Toolbar> <ListItemIcon>
<Divider /> <Dashboard />
<List component="nav"> </ListItemIcon>
<Link to={'/'} style={ <ListItemText primary="Home" />
{ </ListItemButton>
textDecoration: 'none', </Link>
color: mode === 'dark' ? '#ffffff' : '#000000DE' <Link to={'/archive'} style={
} {
}> textDecoration: 'none',
<ListItemButton disabled={status.downloading}> color: mode === 'dark' ? '#ffffff' : '#000000DE'
<ListItemIcon> }
<Dashboard /> }>
</ListItemIcon> <ListItemButton disabled={status.downloading}>
<ListItemText primary="Home" /> <ListItemIcon>
</ListItemButton> <DownloadIcon />
</Link> </ListItemIcon>
<Link to={'/archive'} style={ <ListItemText primary="Archive" />
{ </ListItemButton>
textDecoration: 'none', </Link>
color: mode === 'dark' ? '#ffffff' : '#000000DE' <Link to={'/settings'} style={
} {
}> textDecoration: 'none',
<ListItemButton disabled={status.downloading}> color: mode === 'dark' ? '#ffffff' : '#000000DE'
<ListItemIcon> }
<DownloadIcon /> }>
</ListItemIcon> <ListItemButton disabled={status.downloading}>
<ListItemText primary="Archive" /> <ListItemIcon>
</ListItemButton> <SettingsIcon />
</Link> </ListItemIcon>
<Link to={'/settings'} style={ <ListItemText primary="Settings" />
{ </ListItemButton>
textDecoration: 'none', </Link>
color: mode === 'dark' ? '#ffffff' : '#000000DE' <ThemeToggler />
} <Logout />
}> </List>
<ListItemButton disabled={status.downloading}> </Drawer>
<ListItemIcon> <Box
<SettingsIcon /> component="main"
</ListItemIcon> sx={{
<ListItemText primary="Settings" /> flexGrow: 1,
</ListItemButton> height: '100vh',
</Link> overflow: 'auto',
<ThemeToggler /> }}
<Logout /> >
</List> <Toolbar />
</Drawer> <Outlet />
<Box </Box>
component="main" </Box>
sx={{ </RPCCLientProvider>
flexGrow: 1, </I18nProvider>
height: '100vh',
overflow: 'auto',
}}
>
<Toolbar />
<Outlet />
</Box>
</Box>
</ThemeProvider> </ThemeProvider>
) )
} }

View File

@@ -29,6 +29,8 @@ languages:
customArgs: Enable custom yt-dlp args (great power = great responsabilities) customArgs: Enable custom yt-dlp args (great power = great responsabilities)
customArgsInput: Custom yt-dlp arguments customArgsInput: Custom yt-dlp arguments
rpcConnErr: Error while conencting to RPC server rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
italian: italian:
urlInput: URL di YouTube o di qualsiasi altro servizio supportato urlInput: URL di YouTube o di qualsiasi altro servizio supportato
statusTitle: Stato statusTitle: Stato
@@ -57,6 +59,8 @@ languages:
customArgs: Enable custom yt-dlp args (great power = great responsabilities) customArgs: Enable custom yt-dlp args (great power = great responsabilities)
customArgsInput: Custom yt-dlp arguments customArgsInput: Custom yt-dlp arguments
rpcConnErr: Error nella connessione al server RPC rpcConnErr: Error nella connessione al server RPC
splashText: Nessun download attivo
archiveTitle: Archivio
chinese: chinese:
urlInput: YouTube 或其他受支持服务的视频网址 urlInput: YouTube 或其他受支持服务的视频网址
statusTitle: 状态 statusTitle: 状态
@@ -85,6 +89,8 @@ languages:
customArgs: 启用自定义 yt-dlp 参数(能力越大 = 责任越大) customArgs: 启用自定义 yt-dlp 参数(能力越大 = 责任越大)
customArgsInput: 自定义 yt-dlp 参数 customArgsInput: 自定义 yt-dlp 参数
rpcConnErr: Error while conencting to RPC server rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
spanish: spanish:
urlInput: URL de YouTube u otro servicio compatible urlInput: URL de YouTube u otro servicio compatible
statusTitle: Estado statusTitle: Estado
@@ -113,6 +119,8 @@ languages:
customArgs: Habilitar los argumentos yt-dlp personalizados (un gran poder conlleva una gran responsabilidad) customArgs: Habilitar los argumentos yt-dlp personalizados (un gran poder conlleva una gran responsabilidad)
customArgsInput: Argumentos yt-dlp personalizados customArgsInput: Argumentos yt-dlp personalizados
rpcConnErr: Error al conectarse al servidor RPC rpcConnErr: Error al conectarse al servidor RPC
splashText: No active downloads
archiveTitle: Archive
russian: russian:
urlInput: YouTube or other supported service video url urlInput: YouTube or other supported service video url
statusTitle: Status statusTitle: Status
@@ -141,6 +149,8 @@ languages:
customArgs: Enable custom yt-dlp args (great power = great responsabilities) customArgs: Enable custom yt-dlp args (great power = great responsabilities)
customArgsInput: Custom yt-dlp arguments customArgsInput: Custom yt-dlp arguments
rpcConnErr: Error while conencting to RPC server rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
korean: korean:
urlInput: YouTube나 다른 지원되는 사이트의 URL urlInput: YouTube나 다른 지원되는 사이트의 URL
statusTitle: 상태 statusTitle: 상태
@@ -169,6 +179,8 @@ languages:
customArgs: Enable custom yt-dlp args (great power = great responsabilities) customArgs: Enable custom yt-dlp args (great power = great responsabilities)
customArgsInput: Custom yt-dlp arguments customArgsInput: Custom yt-dlp arguments
rpcConnErr: Error while conencting to RPC server rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
japanese: japanese:
urlInput: YouTubeまたはサポート済み動画のURL urlInput: YouTubeまたはサポート済み動画のURL
statusTitle: 状態 statusTitle: 状態
@@ -198,6 +210,8 @@ languages:
customArgs: yt-dlpのオプションの有効化 (最適設定にする場合) customArgs: yt-dlpのオプションの有効化 (最適設定にする場合)
customArgsInput: yt-dlpのオプション customArgsInput: yt-dlpのオプション
rpcConnErr: Error while conencting to RPC server rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
catalan: catalan:
urlInput: URL de YouTube o d'un altre servei compatible urlInput: URL de YouTube o d'un altre servei compatible
statusTitle: Estat statusTitle: Estat
@@ -226,3 +240,5 @@ languages:
customArgs: Habilitar els arguments yt-dlp personalitzats (un gran poder comporta una gran responsabilitat) customArgs: Habilitar els arguments yt-dlp personalitzats (un gran poder comporta una gran responsabilitat)
customArgsInput: Arguments yt-dlp personalitzats customArgsInput: Arguments yt-dlp personalitzats
rpcConnErr: Error en connectar-se al servidor RPC rpcConnErr: Error en connectar-se al servidor RPC
splashText: No active downloads
archiveTitle: Archive

View File

@@ -21,12 +21,12 @@ import Toolbar from '@mui/material/Toolbar'
import { TransitionProps } from '@mui/material/transitions' import { TransitionProps } from '@mui/material/transitions'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { Buffer } from 'buffer' import { Buffer } from 'buffer'
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react' import { forwardRef, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import FormatsGrid from '../components/FormatsGrid' import FormatsGrid from '../components/FormatsGrid'
import { CliArguments } from '../lib/argsParser' import { CliArguments } from '../lib/argsParser'
import I18nBuilder from '../lib/intl' import { I18nContext } from '../providers/i18nProvider'
import { RPCClient } from '../lib/rpcClient' import { RPCClientContext } from '../providers/rpcClientProvider'
import { RootState } from '../stores/store' import { RootState } from '../stores/store'
import type { DLMetadata } from '../types' import type { DLMetadata } from '../types'
import { isValidURL, toFormatArgs } from '../utils' import { isValidURL, toFormatArgs } from '../utils'
@@ -67,10 +67,12 @@ export default function DownloadDialog({ open, onClose }: Props) {
const [workingUrl, setWorkingUrl] = useState('') const [workingUrl, setWorkingUrl] = useState('')
// memos // memos
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
const client = useMemo(() => new RPCClient(), [settings.serverAddr, settings.serverPort])
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs]) const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
// context
const { i18n } = useContext(I18nContext)
const { client } = useContext(RPCClientContext)
// refs // refs
const urlInputRef = useRef<HTMLInputElement>(null) const urlInputRef = useRef<HTMLInputElement>(null)
const customFilenameInputRef = useRef<HTMLInputElement>(null) const customFilenameInputRef = useRef<HTMLInputElement>(null)

View File

@@ -1,5 +1,7 @@
import CloudDownloadIcon from '@mui/icons-material/CloudDownload' import CloudDownloadIcon from '@mui/icons-material/CloudDownload'
import { Container, SvgIcon, Typography, styled } from '@mui/material' import { Container, SvgIcon, Typography, styled } from '@mui/material'
import { useContext } from 'react'
import { I18nContext } from '../providers/i18nProvider'
const FlexContainer = styled(Container)({ const FlexContainer = styled(Container)({
display: 'flex', display: 'flex',
@@ -19,6 +21,8 @@ const Title = styled(Typography)({
}) })
export default function Splash() { export default function Splash() {
const { i18n } = useContext(I18nContext)
return ( return (
<FlexContainer> <FlexContainer>
<Title fontWeight={'500'} fontSize={72} color={'gray'}> <Title fontWeight={'500'} fontSize={72} color={'gray'}>
@@ -27,7 +31,7 @@ export default function Splash() {
</SvgIcon> </SvgIcon>
</Title> </Title>
<Title fontWeight={'500'} fontSize={36} color={'gray'}> <Title fontWeight={'500'} fontSize={36} color={'gray'}>
No active downloads {i18n.t('splashText')}
</Title> </Title>
</FlexContainer> </FlexContainer>
) )

View File

@@ -0,0 +1,30 @@
import { createContext, useMemo } from 'react'
import { useSelector } from 'react-redux'
import I18nBuilder from '../lib/intl'
import { RootState, store } from '../stores/store'
type Props = {
children: React.ReactNode
}
interface Context {
i18n: I18nBuilder
}
export const I18nContext = createContext<Context>({
i18n: new I18nBuilder(store.getState().settings.language)
})
export default function I18nProvider({ children }: Props) {
const settings = useSelector((state: RootState) => state.settings)
const i18n = useMemo(() => new I18nBuilder(
settings.language
), [settings.language])
return (
<I18nContext.Provider value={{ i18n }}>
{children}
</I18nContext.Provider>
)
}

View File

@@ -0,0 +1,31 @@
import { createContext, useMemo } from 'react'
import { useSelector } from 'react-redux'
import { RPCClient } from '../lib/rpcClient'
import { RootState } from '../stores/store'
type Props = {
children: React.ReactNode
}
interface Context {
client: RPCClient
}
export const RPCClientContext = createContext<Context>({
client: new RPCClient()
})
export default function RPCCLientProvider({ children }: Props) {
const settings = useSelector((state: RootState) => state.settings)
const client = useMemo(() => new RPCClient(), [
settings.serverAddr,
settings.serverPort,
])
return (
<RPCClientContext.Provider value={{ client }}>
{children}
</RPCClientContext.Provider>
)
}

View File

@@ -27,7 +27,7 @@ import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'
import VideoFileIcon from '@mui/icons-material/VideoFile' import VideoFileIcon from '@mui/icons-material/VideoFile'
import { Buffer } from 'buffer' import { Buffer } from 'buffer'
import { useEffect, useMemo, useState, useTransition } from 'react' import { useContext, useEffect, useMemo, useState, useTransition } from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs' import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs'
import { useObservable } from '../hooks/observable' import { useObservable } from '../hooks/observable'
@@ -36,11 +36,14 @@ import { DeleteRequest, DirectoryEntry } from '../types'
import { roundMiB } from '../utils' import { roundMiB } from '../utils'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { ffetch } from '../lib/httpClient' import { ffetch } from '../lib/httpClient'
import { I18nContext } from '../providers/i18nProvider'
export default function Downloaded() { export default function Downloaded() {
const settings = useSelector((state: RootState) => state.settings) const settings = useSelector((state: RootState) => state.settings)
const navigate = useNavigate() const navigate = useNavigate()
const { i18n } = useContext(I18nContext)
const [openDialog, setOpenDialog] = useState(false) const [openDialog, setOpenDialog] = useState(false)
const serverAddr = const serverAddr =
@@ -160,7 +163,7 @@ export default function Downloaded() {
flexDirection: 'column', flexDirection: 'column',
}}> }}>
<Typography py={1} variant="h5" color="primary"> <Typography py={1} variant="h5" color="primary">
{'Archive'} {i18n.t('archiveTitle')}
</Typography> </Typography>
<List sx={{ width: '100%', bgcolor: 'background.paper' }}> <List sx={{ width: '100%', bgcolor: 'background.paper' }}>
{selectable.length === 0 && 'No files found'} {selectable.length === 0 && 'No files found'}

View File

@@ -12,7 +12,7 @@ import {
SpeedDialIcon, SpeedDialIcon,
styled styled
} from '@mui/material' } from '@mui/material'
import { useEffect, useMemo, useState } from 'react' import { useContext, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import DownloadDialog from '../components/DownloadDialog' import DownloadDialog from '../components/DownloadDialog'
import { DownloadsCardView } from '../components/DownloadsCardView' import { DownloadsCardView } from '../components/DownloadsCardView'
@@ -20,8 +20,9 @@ import { DownloadsListView } from '../components/DownloadsListView'
import Splash from '../components/Splash' import Splash from '../components/Splash'
import { toggleListView } from '../features/settings/settingsSlice' import { toggleListView } from '../features/settings/settingsSlice'
import { connected, setFreeSpace } from '../features/status/statusSlice' import { connected, setFreeSpace } from '../features/status/statusSlice'
import I18nBuilder from '../lib/intl' import { socket$ } from '../lib/rpcClient'
import { RPCClient, socket$ } from '../lib/rpcClient' import { I18nContext } from '../providers/i18nProvider'
import { RPCClientContext } from '../providers/rpcClientProvider'
import { RootState } from '../stores/store' import { RootState } from '../stores/store'
import type { RPCResponse, RPCResult } from '../types' import type { RPCResponse, RPCResult } from '../types'
import { dateTimeComparatorFunc } from '../utils' import { dateTimeComparatorFunc } from '../utils'
@@ -41,9 +42,9 @@ export default function Home() {
const [openDialog, setOpenDialog] = useState(false) const [openDialog, setOpenDialog] = useState(false)
const [socketHasError, setSocketHasError] = useState(false) const [socketHasError, setSocketHasError] = useState(false)
// memos // context
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language]) const { i18n } = useContext(I18nContext)
const client = useMemo(() => new RPCClient(), [settings.serverAddr, settings.serverPort]) const { client } = useContext(RPCClientContext)
/* -------------------- Effects -------------------- */ /* -------------------- Effects -------------------- */
@@ -120,7 +121,6 @@ export default function Home() {
client.killAll() client.killAll()
} }
/* -------------------- styled components -------------------- */ /* -------------------- styled components -------------------- */
const Input = styled('input')({ const Input = styled('input')({

View File

@@ -17,7 +17,7 @@ import {
TextField, TextField,
Typography Typography
} from '@mui/material' } from '@mui/material'
import { useEffect, useMemo, useState } from 'react' import { useContext, useEffect, useMemo, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { import {
Subject, Subject,
@@ -26,9 +26,6 @@ import {
map, map,
takeWhile takeWhile
} from 'rxjs' } from 'rxjs'
import { CliArguments } from '../lib/argsParser'
import I18nBuilder from '../lib/intl'
import { RPCClient } from '../lib/rpcClient'
import { import {
LanguageUnion, LanguageUnion,
ThemeUnion, ThemeUnion,
@@ -43,6 +40,9 @@ import {
setTheme setTheme
} from '../features/settings/settingsSlice' } from '../features/settings/settingsSlice'
import { updated } from '../features/status/statusSlice' import { updated } from '../features/status/statusSlice'
import { CliArguments } from '../lib/argsParser'
import { I18nContext } from '../providers/i18nProvider'
import { RPCClientContext } from '../providers/rpcClientProvider'
import { RootState } from '../stores/store' import { RootState } from '../stores/store'
import { validateDomain, validateIP } from '../utils' import { validateDomain, validateIP } from '../utils'
@@ -54,9 +54,9 @@ export default function Settings() {
const [invalidIP, setInvalidIP] = useState(false); const [invalidIP, setInvalidIP] = useState(false);
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language]) const { i18n } = useContext(I18nContext)
const { client } = useContext(RPCClientContext)
const client = useMemo(() => new RPCClient(), [])
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), []) const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [])
const serverAddr$ = useMemo(() => new Subject<string>(), []) const serverAddr$ = useMemo(() => new Subject<string>(), [])