import AddCircleIcon from '@mui/icons-material/AddCircle' import DeleteForeverIcon from '@mui/icons-material/DeleteForever' import FormatListBulleted from '@mui/icons-material/FormatListBulleted' import { Alert, Backdrop, CircularProgress, Container, Snackbar, SpeedDial, SpeedDialAction, SpeedDialIcon } from '@mui/material' import { useContext, useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import DownloadDialog from '../components/DownloadDialog' import { DownloadsCardView } from '../components/DownloadsCardView' import { DownloadsListView } from '../components/DownloadsListView' import Splash from '../components/Splash' import { toggleListView } from '../features/settings/settingsSlice' import { connected, setFreeSpace } from '../features/status/statusSlice' 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 { datetimeCompareFunc, isRPCResponse } from '../utils' export default function Home() { // redux state const settings = useSelector((state: RootState) => state.settings) const status = useSelector((state: RootState) => state.status) const dispatch = useDispatch() // ephemeral state const [activeDownloads, setActiveDownloads] = useState() const [showBackdrop, setShowBackdrop] = useState(true) const [showToast, setShowToast] = useState(true) const [openDialog, setOpenDialog] = useState(false) const [socketHasError, setSocketHasError] = useState(false) // context const { i18n } = useContext(I18nContext) const { client } = useContext(RPCClientContext) /* -------------------- Effects -------------------- */ /* WebSocket connect event handler*/ useEffect(() => { if (status.connected) { return } const sub = socket$.subscribe({ next: () => { dispatch(connected()) }, error: () => { setSocketHasError(true) setShowBackdrop(false) }, complete: () => { setSocketHasError(true) setShowBackdrop(false) }, }) return () => sub.unsubscribe() }, [socket$, status.connected]) useEffect(() => { if (status.connected) { client.running() const interval = setInterval(() => client.running(), 1000) return () => clearInterval(interval) } }, [status.connected]) useEffect(() => { client.freeSpace().then(bytes => dispatch(setFreeSpace(bytes.result))) }, []) useEffect(() => { if (!status.connected) { return } const sub = socket$.subscribe((event: RPCResponse) => { if (!isRPCResponse(event)) { return } setActiveDownloads((event.result ?? []) .filter(f => !!f.info.url) .sort((a, b) => datetimeCompareFunc( b.info.created_at, a.info.created_at, ))) }) return () => sub.unsubscribe() }, [socket$, status.connected]) useEffect(() => { if (activeDownloads && activeDownloads.length >= 0) { setShowBackdrop(false) } }, [activeDownloads?.length]) /** * Abort a specific download if id's provided, other wise abort all running ones. * @param id The download id / pid * @returns void */ const abort = (id?: string) => { if (id) { client.kill(id) return } client.killAll() } return ( theme.zIndex.drawer + 1 }} open={showBackdrop} > {activeDownloads?.length === 0 && } { settings.listView ? : } setShowToast(false)} > {`Connected to (${settings.serverAddr}:${settings.serverPort})`} {`${i18n.t('rpcConnErr')} (${settings.serverAddr}:${settings.serverPort})`} } > } tooltipTitle={`Table view`} onClick={() => dispatch(toggleListView())} /> } tooltipTitle={i18n.t('abortAllButton')} onClick={() => abort()} /> } tooltipTitle={`New download`} onClick={() => setOpenDialog(true)} /> { setOpenDialog(false) setShowBackdrop(false) }} onDownloadStart={() => { setOpenDialog(false) setShowBackdrop(true) }} /> ) }