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()); const [messageMap, setMessageMap] = useState(new Map()); const [downloadInfoMap, setDownloadInfoMap] = useState(new Map()); 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) => { 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 ( theme.zIndex.drawer + 1 }} open={showBackdrop} > { /*Super big brain flatMap moment*/ Array .from(messageMap) .filter(flattened => [...flattened][0]) .filter(flattened => [...flattened][1].toString() !== 'Done!') .flatMap(message => ( { /* Message[0] => key, the pid which is shared with the progress and download Maps Message[1] => value, the actual formatted message sent from server */ } abort(message[0])} /> )) } dispatch(disconnected())} /> ); }