code refactoring

This commit is contained in:
2023-07-31 16:28:58 +02:00
parent b5731759b0
commit c0a424410e
18 changed files with 222 additions and 251 deletions

View File

@@ -26,15 +26,14 @@ import { TransitionProps } from '@mui/material/transitions'
import { Buffer } from 'buffer'
import {
forwardRef,
useEffect,
useMemo,
useRef,
useState,
useTransition
} from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { useRecoilValue } from 'recoil'
import { settingsState } from '../atoms/settings'
import { connectedState } from '../atoms/status'
import { availableDownloadPathsState, connectedState } from '../atoms/status'
import FormatsGrid from '../components/FormatsGrid'
import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
@@ -64,7 +63,8 @@ export default function DownloadDialog({
}: Props) {
// recoil state
const settings = useRecoilValue(settingsState)
const [isConnected] = useRecoilState(connectedState)
const isConnected = useRecoilValue(connectedState)
const availableDownloadPaths = useRecoilValue(availableDownloadPathsState)
// ephemeral state
const [downloadFormats, setDownloadFormats] = useState<DLMetadata>()
@@ -74,7 +74,6 @@ export default function DownloadDialog({
const [customArgs, setCustomArgs] = useState('')
const [downloadPath, setDownloadPath] = useState(0)
const [availableDownloadPaths, setAvailableDownloadPaths] = useState<string[]>([])
const [fileNameOverride, setFilenameOverride] = useState('')
@@ -96,19 +95,6 @@ export default function DownloadDialog({
const urlInputRef = useRef<HTMLInputElement>(null)
const customFilenameInputRef = useRef<HTMLInputElement>(null)
// effects
useEffect(() => {
client.directoryTree()
.then(data => {
setAvailableDownloadPaths(data.result)
})
}, [])
useEffect(() => {
setCustomArgs(localStorage.getItem('last-input-args') ?? '')
setFilenameOverride(localStorage.getItem('last-filename-override') ?? '')
}, [])
// transitions
const [isPending, startTransition] = useTransition()

View File

@@ -1,71 +1,31 @@
import { useEffect } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { activeDownloadsState } from '../atoms/downloads'
import { listViewState } from '../atoms/settings'
import { useRPC } from '../hooks/useRPC'
import { DownloadsCardView } from './DownloadsCardView'
import { DownloadsListView } from './DownloadsListView'
import { useEffect } from 'react'
import { connectedState, isDownloadingState } from '../atoms/status'
import { datetimeCompareFunc, isRPCResponse } from '../utils'
import { RPCResponse, RPCResult } from '../types'
import { loadingAtom } from '../atoms/ui'
import DownloadsCardView from './DownloadsCardView'
import DownloadsListView from './DownloadsListView'
const Downloads: React.FC = () => {
const [active, setActive] = useRecoilState(activeDownloadsState)
const isConnected = useRecoilValue(connectedState)
const listView = useRecoilValue(listViewState)
const active = useRecoilValue(activeDownloadsState)
const { client, socket$ } = useRPC()
const abort = (id?: string) => {
if (id) {
client.kill(id)
return
}
client.killAll()
}
useEffect(() => {
if (!isConnected) { return }
const sub = socket$.subscribe((event: RPCResponse<RPCResult[]>) => {
if (!isRPCResponse(event)) { return }
if (!Array.isArray(event.result)) { return }
setActive(
(event.result || [])
.filter(f => !!f.info.url)
.sort((a, b) => datetimeCompareFunc(
b.info.created_at,
a.info.created_at,
))
)
})
return () => sub.unsubscribe()
}, [socket$, isConnected])
const [, setIsDownloading] = useRecoilState(isDownloadingState)
const [, setIsLoading] = useRecoilState(loadingAtom)
useEffect(() => {
if (active) {
setIsDownloading(true)
setIsLoading(true)
}
}, [active?.length])
if (listView) {
return (
<DownloadsListView
downloads={active ?? []}
onStop={abort}
/>
<DownloadsListView />
)
}
return (
<DownloadsCardView
downloads={active ?? []}
onStop={abort}
/>
<DownloadsCardView />
)
}

View File

@@ -1,19 +1,21 @@
import { Grid } from "@mui/material"
import { Fragment } from "react"
import { useRecoilValue } from 'recoil'
import { activeDownloadsState } from '../atoms/downloads'
import { useToast } from "../hooks/toast"
import { useI18n } from '../hooks/useI18n'
import type { RPCResult } from "../types"
import { useRPC } from '../hooks/useRPC'
import { StackableResult } from "./StackableResult"
type Props = {
downloads: RPCResult[]
onStop: (id: string) => void
}
const DownloadsCardView: React.FC = () => {
const downloads = useRecoilValue(activeDownloadsState) ?? []
export function DownloadsCardView({ downloads, onStop }: Props) {
const { i18n } = useI18n()
const { client } = useRPC()
const { pushMessage } = useToast()
const abort = (id: string) => client.kill(id)
return (
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
{
@@ -25,7 +27,7 @@ export function DownloadsCardView({ downloads, onStop }: Props) {
title={download.info.title}
thumbnail={download.info.thumbnail}
percentage={download.progress.percentage}
onStop={() => onStop(download.id)}
onStop={() => abort(download.id)}
onCopy={() => pushMessage(i18n.t('clipboardAction'))}
resolution={download.info.resolution ?? ''}
speed={download.progress.speed}
@@ -38,4 +40,6 @@ export function DownloadsCardView({ downloads, onStop }: Props) {
}
</Grid>
)
}
}
export default DownloadsCardView

View File

@@ -11,15 +11,19 @@ import {
TableRow,
Typography
} from "@mui/material"
import { useRecoilValue } from 'recoil'
import { activeDownloadsState } from '../atoms/downloads'
import { useRPC } from '../hooks/useRPC'
import { ellipsis, formatSpeedMiB, roundMiB } from "../utils"
import type { RPCResult } from "../types"
type Props = {
downloads: RPCResult[]
onStop: (id: string) => void
}
export const DownloadsListView: React.FC<Props> = ({ downloads, onStop }) => {
const DownloadsListView: React.FC = () => {
const downloads = useRecoilValue(activeDownloadsState) ?? []
const { client } = useRPC()
const abort = (id: string) => client.kill(id)
return (
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
<Grid item xs={12}>
@@ -70,7 +74,7 @@ export const DownloadsListView: React.FC<Props> = ({ downloads, onStop }) => {
<Button
variant="contained"
size="small"
onClick={() => onStop(download.id)}
onClick={() => abort(download.id)}
>
{download.progress.percentage === '-1' ? 'Remove' : 'Stop'}
</Button>
@@ -84,4 +88,6 @@ export const DownloadsListView: React.FC<Props> = ({ downloads, onStop }) => {
</Grid>
</Grid>
)
}
}
export default DownloadsListView

View File

@@ -0,0 +1,29 @@
import StorageIcon from '@mui/icons-material/Storage'
import { useEffect, useState } from 'react'
import { formatGiB } from '../utils'
import { useRPC } from '../hooks/useRPC'
const FreeSpaceIndicator = () => {
const [freeSpace, setFreeSpace] = useState(0)
const { client } = useRPC()
useEffect(() => {
client.freeSpace().then(r => setFreeSpace(r.result))
}, [client])
return (
<div style={{
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
}}>
<StorageIcon />
<span>
&nbsp;{formatGiB(freeSpace)}&nbsp;
</span>
</div>
)
}
export default FreeSpaceIndicator

View File

@@ -0,0 +1,31 @@
import { useState } from 'react'
import { useRecoilState } from 'recoil'
import { loadingAtom } from '../atoms/ui'
import DownloadDialog from './DownloadDialog'
import HomeSpeedDial from './HomeSpeedDial'
const HomeActions: React.FC = () => {
const [, setIsLoading] = useRecoilState(loadingAtom)
const [openDialog, setOpenDialog] = useState(false)
return (
<>
<HomeSpeedDial
onOpen={() => setOpenDialog(true)}
/>
<DownloadDialog
open={openDialog}
onClose={() => {
setOpenDialog(false)
setIsLoading(true)
}}
onDownloadStart={() => {
setOpenDialog(false)
setIsLoading(true)
}}
/>
</>
)
}
export default HomeActions

View File

@@ -0,0 +1,18 @@
import { Backdrop, CircularProgress } from '@mui/material'
import { useRecoilValue } from 'recoil'
import { loadingAtom } from '../atoms/ui'
const LoadingBackdrop: React.FC = () => {
const isLoading = useRecoilValue(loadingAtom)
return (
<Backdrop
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
open={!isLoading}
>
<CircularProgress color="primary" />
</Backdrop>
)
}
export default LoadingBackdrop

View File

@@ -1,13 +1,15 @@
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
import LogoutIcon from '@mui/icons-material/Logout'
import { getHttpEndpoint } from '../utils'
import { useNavigate } from 'react-router-dom'
import { useRecoilValue } from 'recoil'
import { serverURL } from '../atoms/settings'
export default function Logout() {
const navigate = useNavigate()
const url = useRecoilValue(serverURL)
const logout = async () => {
const res = await fetch(`${getHttpEndpoint()}/auth/logout`)
const res = await fetch(`${url}/auth/logout`)
if (res.ok) {
navigate('/login')
}

View File

@@ -1,42 +1,62 @@
import { useEffect } from 'react'
import { useMemo } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import { connectedState } from '../atoms/status'
import { useRPC } from '../hooks/useRPC'
import { useToast } from '../hooks/toast'
import { interval, share, take } from 'rxjs'
import { activeDownloadsState } from '../atoms/downloads'
import { serverAddressAndPortState } from '../atoms/settings'
import { connectedState } from '../atoms/status'
import { useSubscription } from '../hooks/observable'
import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
import { datetimeCompareFunc, isRPCResponse } from '../utils'
interface Props extends React.HTMLAttributes<HTMLBaseElement> { }
const SocketSubscriber: React.FC<Props> = ({ children }) => {
const [isConnected, setIsConnected] = useRecoilState(connectedState)
const [, setIsConnected] = useRecoilState(connectedState)
const [, setActive] = useRecoilState(activeDownloadsState)
const serverAddressAndPort = useRecoilValue(serverAddressAndPortState)
const { i18n } = useI18n()
const { socket$ } = useRPC()
const { client } = useRPC()
const { pushMessage } = useToast()
useEffect(() => {
if (isConnected) { return }
const sharedSocket$ = useMemo(() => client.socket$.pipe(share()), [])
const socketOnce$ = useMemo(() => sharedSocket$.pipe(take(1)), [])
const sub = socket$.subscribe({
next: () => {
setIsConnected(true)
pushMessage(
`Connected to (${serverAddressAndPort})`,
"success"
)
},
error: (e) => {
console.error(e)
pushMessage(
`${i18n.t('rpcConnErr')} (${serverAddressAndPort})`,
"error"
)
}
})
return () => sub.unsubscribe()
}, [isConnected])
useSubscription(socketOnce$, () => {
setIsConnected(true)
pushMessage(
`${i18n.t('toastConnected')} (${serverAddressAndPort})`,
"success"
)
})
useSubscription(sharedSocket$,
(event) => {
if (!isRPCResponse(event)) { return }
if (!Array.isArray(event.result)) { return }
setActive(
(event.result || [])
.filter(f => !!f.info.url)
.sort((a, b) => datetimeCompareFunc(
b.info.created_at,
a.info.created_at,
))
)
},
(err) => {
console.error(err)
pushMessage(
`${i18n.t('rpcConnErr')} (${serverAddressAndPort})`,
"error"
)
}
)
useSubscription(interval(1000), () => client.running())
return (
<>{children}</>

View File

@@ -12,7 +12,6 @@ import {
Stack,
Typography
} from '@mui/material'
import { useEffect, useState } from 'react'
import { ellipsis, formatSpeedMiB, mapProcessStatus, roundMiB } from '../utils'
type Props = {
@@ -40,15 +39,11 @@ export function StackableResult({
onStop,
onCopy,
}: Props) {
const [isCompleted, setIsCompleted] = useState(false)
const isCompleted = () => percentage === '-1'
useEffect(() => {
if (percentage === '-1') {
setIsCompleted(true)
}
}, [percentage])
const percentageToNumber = () => isCompleted ? 100 : Number(percentage.replace('%', ''))
const percentageToNumber = () => isCompleted()
? 100
: Number(percentage.replace('%', ''))
const guessResolution = (xByY: string): any => {
if (!xByY) return null
@@ -82,12 +77,12 @@ export function StackableResult({
}
<Stack direction="row" spacing={1} py={2}>
<Chip
label={isCompleted ? 'Completed' : mapProcessStatus(status)}
label={isCompleted() ? 'Completed' : mapProcessStatus(status)}
color="primary"
size="small"
/>
<Typography>{!isCompleted ? percentage : ''}</Typography>
<Typography> {!isCompleted ? formatSpeedMiB(speed) : ''}</Typography>
<Typography>{!isCompleted() ? percentage : ''}</Typography>
<Typography> {!isCompleted() ? formatSpeedMiB(speed) : ''}</Typography>
<Typography>{roundMiB(size ?? 0)}</Typography>
{guessResolution(resolution)}
</Stack>
@@ -95,7 +90,7 @@ export function StackableResult({
<LinearProgress
variant="determinate"
value={percentageToNumber()}
color={isCompleted ? "secondary" : "primary"}
color={isCompleted() ? "secondary" : "primary"}
/> :
null
}
@@ -108,7 +103,7 @@ export function StackableResult({
color="primary"
onClick={onStop}
>
{isCompleted ? "Clear" : "Stop"}
{isCompleted() ? "Clear" : "Stop"}
</Button>
</CardActions>
</Card>