import { FileUpload } from '@mui/icons-material' import CloseIcon from '@mui/icons-material/Close' import { Backdrop, Button, Checkbox, Container, FormControl, FormControlLabel, Grid, IconButton, InputAdornment, InputLabel, MenuItem, Paper, Select, TextField, styled } from '@mui/material' import AppBar from '@mui/material/AppBar' import Dialog from '@mui/material/Dialog' import Slide from '@mui/material/Slide' import Toolbar from '@mui/material/Toolbar' import Typography from '@mui/material/Typography' 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 { settingsState } from '../atoms/settings' import { connectedState } from '../atoms/status' import FormatsGrid from '../components/FormatsGrid' import { useI18n } from '../hooks/useI18n' import { useRPC } from '../hooks/useRPC' import { CliArguments } from '../lib/argsParser' import type { DLMetadata } from '../types' import { isValidURL, toFormatArgs } from '../utils' const Transition = forwardRef(function Transition( props: TransitionProps & { children: React.ReactElement }, ref: React.Ref, ) { return }) type Props = { open: boolean onClose: () => void onDownloadStart: () => void } export default function DownloadDialog({ open, onClose, onDownloadStart }: Props) { // recoil state const settings = useRecoilValue(settingsState) const [isConnected] = useRecoilState(connectedState) // ephemeral state const [downloadFormats, setDownloadFormats] = useState() const [pickedVideoFormat, setPickedVideoFormat] = useState('') const [pickedAudioFormat, setPickedAudioFormat] = useState('') const [pickedBestFormat, setPickedBestFormat] = useState('') const [customArgs, setCustomArgs] = useState('') const [downloadPath, setDownloadPath] = useState(0) const [availableDownloadPaths, setAvailableDownloadPaths] = useState([]) const [fileNameOverride, setFilenameOverride] = useState('') const [url, setUrl] = useState('') const [workingUrl, setWorkingUrl] = useState('') const [isPlaylist, setIsPlaylist] = useState(false) // memos const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs] ) // context const { i18n } = useI18n() const { client } = useRPC() // refs const urlInputRef = useRef(null) const customFilenameInputRef = useRef(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() /** * Retrive url from input, cli-arguments from checkboxes and emits via WebSocket */ const sendUrl = (immediate?: string) => { const codes = new Array() if (pickedVideoFormat !== '') codes.push(pickedVideoFormat) if (pickedAudioFormat !== '') codes.push(pickedAudioFormat) if (pickedBestFormat !== '') codes.push(pickedBestFormat) client.download( immediate || url || workingUrl, `${cliArgs.toString()} ${toFormatArgs(codes)} ${customArgs}`, availableDownloadPaths[downloadPath] ?? '', fileNameOverride, isPlaylist, ) setUrl('') setWorkingUrl('') setTimeout(() => { resetInput() setDownloadFormats(undefined) onDownloadStart() }, 250) } /** * Retrive url from input and display the formats selection view */ const sendUrlFormatSelection = () => { setWorkingUrl(url) setUrl('') setPickedAudioFormat('') setPickedVideoFormat('') setPickedBestFormat('') client.formats(url) ?.then(formats => { setDownloadFormats(formats.result) resetInput() }) } /** * Update the url state whenever the input value changes * @param e Input change event */ const handleUrlChange = (e: React.ChangeEvent) => { setUrl(e.target.value) } /** * Update the filename override state whenever the input value changes * @param e Input change event */ const handleFilenameOverrideChange = (e: React.ChangeEvent) => { setFilenameOverride(e.target.value) localStorage.setItem('last-filename-override', e.target.value) } /** * Update the custom args state whenever the input value changes * @param e Input change event */ const handleCustomArgsChange = (e: React.ChangeEvent) => { setCustomArgs(e.target.value) localStorage.setItem("last-input-args", e.target.value) } const parseUrlListFile = (event: any) => { const urlList = event.target.files const reader = new FileReader() reader.addEventListener('load', $event => { const base64 = $event.target?.result!.toString().split(',')[1] Buffer.from(base64!, 'base64') .toString() .trimEnd() .split('\n') .filter(_url => isValidURL(_url)) .forEach(_url => sendUrl(_url)) }) reader.readAsDataURL(urlList[0]) } const resetInput = () => { urlInputRef.current!.value = '' if (customFilenameInputRef.current) { customFilenameInputRef.current!.value = '' } } /* -------------------- styled components -------------------- */ const Input = styled('input')({ display: 'none', }) return (
theme.zIndex.drawer + 1 }} open={isPending} /> Download ), }} /> { settings.enableCustomArgs && } { settings.fileRenaming && } { settings.pathOverriding && {i18n.t('customPath')} } setIsPlaylist(state => !state)} />} checked={isPlaylist} label={i18n.t('playlistCheckbox')} /> {/* Format Selection grid */} {downloadFormats && { setPickedBestFormat(id) setPickedVideoFormat('') setPickedAudioFormat('') }} onVideoSelected={(id) => { setPickedVideoFormat(id) setPickedBestFormat('') }} onAudioSelected={(id) => { setPickedAudioFormat(id) setPickedBestFormat('') }} onClear={() => { setPickedAudioFormat('') setPickedVideoFormat('') setPickedBestFormat('') }} onSubmit={sendUrl} pickedBestFormat={pickedBestFormat} pickedVideoFormat={pickedVideoFormat} pickedAudioFormat={pickedAudioFormat} />}
) }