194 lines
7.4 KiB
TypeScript
194 lines
7.4 KiB
TypeScript
import { Backdrop, Button, CircularProgress, Container, Grid, Paper, Snackbar, TextField, } from "@mui/material";
|
|
import React, { Fragment, useEffect, useState } from "react";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { Socket } from "socket.io-client";
|
|
import { StackableResult } from "./components/StackableResult";
|
|
import { connected, disconnected, downloading, finished } from "./features/status/statusSlice";
|
|
import { IDLInfo, IDLInfoBase, IMessage } from "./interfaces";
|
|
import { RootState } from "./stores/store";
|
|
import { updateInStateMap, } from "./utils";
|
|
|
|
type Props = {
|
|
socket: Socket
|
|
}
|
|
|
|
export default function Home({ socket }: Props) {
|
|
// redux state
|
|
const settings = useSelector((state: RootState) => state.settings)
|
|
const status = useSelector((state: RootState) => state.status)
|
|
const dispatch = useDispatch()
|
|
|
|
// ephemeral state
|
|
const [progressMap, setProgressMap] = useState(new Map<number, number>());
|
|
const [messageMap, setMessageMap] = useState(new Map<number, IMessage>());
|
|
const [downloadInfoMap, setDownloadInfoMap] = useState(new Map<number, IDLInfoBase>());
|
|
|
|
const [url, setUrl] = useState('');
|
|
const [showBackdrop, setShowBackdrop] = useState(false);
|
|
|
|
/* -------------------- Effects -------------------- */
|
|
/* WebSocket connect event handler*/
|
|
useEffect(() => {
|
|
socket.on('connect', () => {
|
|
dispatch(connected());
|
|
socket.emit('fetch-jobs')
|
|
socket.emit('disk-space')
|
|
socket.emit('retrieve-jobs');
|
|
})
|
|
return () => {
|
|
socket.disconnect()
|
|
}
|
|
}, [])
|
|
|
|
|
|
/* Ask server for pending jobs / background jobs */
|
|
useEffect(() => {
|
|
socket.on('pending-jobs', (count: number) => {
|
|
count === 0 ? setShowBackdrop(false) : setShowBackdrop(true)
|
|
})
|
|
}, [])
|
|
|
|
/* Handle download information sent by server */
|
|
useEffect(() => {
|
|
socket.on('info', (data: IDLInfo) => {
|
|
setShowBackdrop(false)
|
|
dispatch(downloading())
|
|
updateInStateMap(data.pid, data.info, downloadInfoMap, setDownloadInfoMap);
|
|
})
|
|
}, [])
|
|
|
|
/* Handle per-download progress */
|
|
useEffect(() => {
|
|
socket.on('progress', (data: IMessage) => {
|
|
if (showBackdrop) {
|
|
setShowBackdrop(false)
|
|
}
|
|
if (!status.downloading) {
|
|
dispatch(downloading())
|
|
}
|
|
if (data.status === 'Done!' || data.status === 'Aborted') {
|
|
updateInStateMap(data.pid, 'Done!', messageMap, setMessageMap);
|
|
updateInStateMap(data.pid, 0, progressMap, setProgressMap);
|
|
socket.emit('disk-space')
|
|
dispatch(finished())
|
|
return;
|
|
}
|
|
updateInStateMap(data.pid, data, messageMap, setMessageMap);
|
|
if (data.progress) {
|
|
updateInStateMap(data.pid, Math.ceil(Number(data.progress.replace('%', ''))), progressMap, setProgressMap)
|
|
}
|
|
})
|
|
}, [])
|
|
|
|
/* -------------------- component functions -------------------- */
|
|
|
|
/**
|
|
* Retrive url from input, cli-arguments from checkboxes and emits via WebSocket
|
|
*/
|
|
const sendUrl = () => {
|
|
socket.emit('send-url', {
|
|
url: url,
|
|
params: settings.cliArgs.toString(),
|
|
})
|
|
setUrl('')
|
|
setTimeout(() => {
|
|
const input = document.getElementById('urlInput') as HTMLInputElement;
|
|
input.value = '';
|
|
setShowBackdrop(true)
|
|
}, 250);
|
|
}
|
|
|
|
/**
|
|
* Update the url state whenever the input value changes
|
|
* @param e Input change event
|
|
*/
|
|
const handleUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
setUrl(e.target.value)
|
|
}
|
|
|
|
/**
|
|
* 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?: number) => {
|
|
if (id) {
|
|
updateInStateMap(id, null, downloadInfoMap, setDownloadInfoMap, true)
|
|
socket.emit('abort', { pid: id })
|
|
return
|
|
}
|
|
socket.emit('abort-all')
|
|
}
|
|
|
|
return (
|
|
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
|
|
<Backdrop
|
|
sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
|
open={showBackdrop}
|
|
>
|
|
<CircularProgress color="primary" />
|
|
</Backdrop>
|
|
<Grid container spacing={2}>
|
|
<Grid item xs={12}>
|
|
<Paper
|
|
sx={{
|
|
p: 2,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}}
|
|
>
|
|
<TextField
|
|
id="urlInput"
|
|
label={settings.i18n.t('urlInput')}
|
|
variant="outlined"
|
|
onChange={handleUrlChange}
|
|
/>
|
|
<Grid container spacing={1} pt={2}>
|
|
<Grid item>
|
|
<Button variant="contained" onClick={() => sendUrl()} disabled={false}>{settings.i18n.t('startButton')}</Button>
|
|
</Grid>
|
|
<Grid item>
|
|
<Button variant="contained" onClick={() => abort()}>{settings.i18n.t('abortAllButton')}</Button>
|
|
|
|
</Grid>
|
|
</Grid>
|
|
</Paper>
|
|
</Grid>
|
|
</Grid>
|
|
<Grid container spacing={{ xs: 2, md: 2 }} columns={{ xs: 4, sm: 8, md: 12 }} pt={2}>
|
|
{ /*Super big brain flatMap moment*/
|
|
Array
|
|
.from(messageMap)
|
|
.filter(flattened => [...flattened][0])
|
|
.filter(flattened => [...flattened][1].toString() !== 'Done!')
|
|
.flatMap(message => (
|
|
<Grid item xs={4} sm={8} md={6} key={message[0]}>
|
|
{
|
|
/*
|
|
Message[0] => key, the pid which is shared with the progress and download Maps
|
|
Message[1] => value, the actual formatted message sent from server
|
|
*/
|
|
}
|
|
<Fragment>
|
|
<StackableResult
|
|
formattedLog={message[1]}
|
|
title={downloadInfoMap.get(message[0])?.title}
|
|
thumbnail={downloadInfoMap.get(message[0])?.thumbnail}
|
|
resolution={downloadInfoMap.get(message[0])?.resolution}
|
|
progress={progressMap.get(message[0])}
|
|
stopCallback={() => abort(message[0])}
|
|
/>
|
|
</Fragment>
|
|
</Grid>
|
|
))
|
|
}
|
|
</Grid>
|
|
<Snackbar
|
|
open={status.connected}
|
|
autoHideDuration={1500}
|
|
message="Connected"
|
|
onClose={() => dispatch(disconnected())}
|
|
/>
|
|
</Container>
|
|
);
|
|
} |