code refactoring
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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 />
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
29
frontend/src/components/FreeSpaceIndicator.tsx
Normal file
29
frontend/src/components/FreeSpaceIndicator.tsx
Normal 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>
|
||||
{formatGiB(freeSpace)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FreeSpaceIndicator
|
||||
31
frontend/src/components/HomeActions.tsx
Normal file
31
frontend/src/components/HomeActions.tsx
Normal 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
|
||||
18
frontend/src/components/LoadingBackdrop.tsx
Normal file
18
frontend/src/components/LoadingBackdrop.tsx
Normal 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
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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}</>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user