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

View File

@@ -29,6 +29,8 @@ languages:
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
customArgsInput: Custom yt-dlp arguments
rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
italian:
urlInput: URL di YouTube o di qualsiasi altro servizio supportato
statusTitle: Stato
@@ -57,6 +59,8 @@ languages:
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
customArgsInput: Custom yt-dlp arguments
rpcConnErr: Error nella connessione al server RPC
splashText: Nessun download attivo
archiveTitle: Archivio
chinese:
urlInput: YouTube 或其他受支持服务的视频网址
statusTitle: 状态
@@ -85,6 +89,8 @@ languages:
customArgs: 启用自定义 yt-dlp 参数(能力越大 = 责任越大)
customArgsInput: 自定义 yt-dlp 参数
rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
spanish:
urlInput: URL de YouTube u otro servicio compatible
statusTitle: Estado
@@ -113,6 +119,8 @@ languages:
customArgs: Habilitar los argumentos yt-dlp personalizados (un gran poder conlleva una gran responsabilidad)
customArgsInput: Argumentos yt-dlp personalizados
rpcConnErr: Error al conectarse al servidor RPC
splashText: No active downloads
archiveTitle: Archive
russian:
urlInput: YouTube or other supported service video url
statusTitle: Status
@@ -141,6 +149,8 @@ languages:
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
customArgsInput: Custom yt-dlp arguments
rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
korean:
urlInput: YouTube나 다른 지원되는 사이트의 URL
statusTitle: 상태
@@ -169,6 +179,8 @@ languages:
customArgs: Enable custom yt-dlp args (great power = great responsabilities)
customArgsInput: Custom yt-dlp arguments
rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
japanese:
urlInput: YouTubeまたはサポート済み動画のURL
statusTitle: 状態
@@ -198,6 +210,8 @@ languages:
customArgs: yt-dlpのオプションの有効化 (最適設定にする場合)
customArgsInput: yt-dlpのオプション
rpcConnErr: Error while conencting to RPC server
splashText: No active downloads
archiveTitle: Archive
catalan:
urlInput: URL de YouTube o d'un altre servei compatible
statusTitle: Estat
@@ -226,3 +240,5 @@ languages:
customArgs: Habilitar els arguments yt-dlp personalitzats (un gran poder comporta una gran responsabilitat)
customArgsInput: Arguments yt-dlp personalitzats
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 Typography from '@mui/material/Typography'
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 FormatsGrid from '../components/FormatsGrid'
import { CliArguments } from '../lib/argsParser'
import I18nBuilder from '../lib/intl'
import { RPCClient } from '../lib/rpcClient'
import { I18nContext } from '../providers/i18nProvider'
import { RPCClientContext } from '../providers/rpcClientProvider'
import { RootState } from '../stores/store'
import type { DLMetadata } from '../types'
import { isValidURL, toFormatArgs } from '../utils'
@@ -67,10 +67,12 @@ export default function DownloadDialog({ open, onClose }: Props) {
const [workingUrl, setWorkingUrl] = useState('')
// 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])
// context
const { i18n } = useContext(I18nContext)
const { client } = useContext(RPCClientContext)
// refs
const urlInputRef = useRef<HTMLInputElement>(null)
const customFilenameInputRef = useRef<HTMLInputElement>(null)

View File

@@ -1,5 +1,7 @@
import CloudDownloadIcon from '@mui/icons-material/CloudDownload'
import { Container, SvgIcon, Typography, styled } from '@mui/material'
import { useContext } from 'react'
import { I18nContext } from '../providers/i18nProvider'
const FlexContainer = styled(Container)({
display: 'flex',
@@ -19,6 +21,8 @@ const Title = styled(Typography)({
})
export default function Splash() {
const { i18n } = useContext(I18nContext)
return (
<FlexContainer>
<Title fontWeight={'500'} fontSize={72} color={'gray'}>
@@ -27,7 +31,7 @@ export default function Splash() {
</SvgIcon>
</Title>
<Title fontWeight={'500'} fontSize={36} color={'gray'}>
No active downloads
{i18n.t('splashText')}
</Title>
</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 { Buffer } from 'buffer'
import { useEffect, useMemo, useState, useTransition } from 'react'
import { useContext, useEffect, useMemo, useState, useTransition } from 'react'
import { useSelector } from 'react-redux'
import { BehaviorSubject, Subject, combineLatestWith, map, share } from 'rxjs'
import { useObservable } from '../hooks/observable'
@@ -36,11 +36,14 @@ import { DeleteRequest, DirectoryEntry } from '../types'
import { roundMiB } from '../utils'
import { useNavigate } from 'react-router-dom'
import { ffetch } from '../lib/httpClient'
import { I18nContext } from '../providers/i18nProvider'
export default function Downloaded() {
const settings = useSelector((state: RootState) => state.settings)
const navigate = useNavigate()
const { i18n } = useContext(I18nContext)
const [openDialog, setOpenDialog] = useState(false)
const serverAddr =
@@ -160,7 +163,7 @@ export default function Downloaded() {
flexDirection: 'column',
}}>
<Typography py={1} variant="h5" color="primary">
{'Archive'}
{i18n.t('archiveTitle')}
</Typography>
<List sx={{ width: '100%', bgcolor: 'background.paper' }}>
{selectable.length === 0 && 'No files found'}

View File

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

View File

@@ -17,7 +17,7 @@ import {
TextField,
Typography
} from '@mui/material'
import { useEffect, useMemo, useState } from 'react'
import { useContext, useEffect, useMemo, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import {
Subject,
@@ -26,9 +26,6 @@ import {
map,
takeWhile
} from 'rxjs'
import { CliArguments } from '../lib/argsParser'
import I18nBuilder from '../lib/intl'
import { RPCClient } from '../lib/rpcClient'
import {
LanguageUnion,
ThemeUnion,
@@ -43,6 +40,9 @@ import {
setTheme
} from '../features/settings/settingsSlice'
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 { validateDomain, validateIP } from '../utils'
@@ -54,9 +54,9 @@ export default function Settings() {
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 serverAddr$ = useMemo(() => new Subject<string>(), [])