import { FileUpload } from '@mui/icons-material' import CloseIcon from '@mui/icons-material/Close' import { Autocomplete, Box, Button, Checkbox, Container, FormControl, FormControlLabel, Grid, IconButton, InputAdornment, MenuItem, Paper, Select, SelectChangeEvent, TextField } 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 { useAtom, useAtomValue } from 'jotai' import { FC, Suspense, forwardRef, useEffect, useRef, useState, useTransition } from 'react' import { customArgsState, downloadTemplateState, filenameTemplateState, savedTemplatesState } from '../atoms/downloadTemplate' import { settingsState } from '../atoms/settings' import { availableDownloadPathsState, connectedState } from '../atoms/status' import FormatsGrid from '../components/FormatsGrid' import { useToast } from '../hooks/toast' import { useI18n } from '../hooks/useI18n' import { useRPC } from '../hooks/useRPC' import type { DLMetadata } from '../types' import { toFormatArgs } from '../utils' import ExtraDownloadOptions from './ExtraDownloadOptions' import LoadingBackdrop from './LoadingBackdrop' const Transition = forwardRef(function Transition( props: TransitionProps & { children: React.ReactElement }, ref: React.Ref, ) { return }) type Props = { open: boolean onClose: () => void onDownloadStart: (url: string) => void } const DownloadDialog: FC = ({ open, onClose, onDownloadStart }) => { const settings = useAtomValue(settingsState) const isConnected = useAtomValue(connectedState) const availableDownloadPaths = useAtomValue(availableDownloadPathsState) const downloadTemplate = useAtomValue(downloadTemplateState) const savedTemplates = useAtomValue(savedTemplatesState) const [downloadFormats, setDownloadFormats] = useState() const [pickedVideoFormat, setPickedVideoFormat] = useState('') const [pickedAudioFormat, setPickedAudioFormat] = useState('') const [pickedBestFormat, setPickedBestFormat] = useState('') const [isFormatsLoading, setIsFormatsLoading] = useState(false) const [customArgs, setCustomArgs] = useAtom(customArgsState) const [downloadPath, setDownloadPath] = useState('') const [filenameTemplate, setFilenameTemplate] = useAtom( filenameTemplateState ) const [fileExtension, setFileExtension] = useState('.%(ext)s') const [url, setUrl] = useState('') const [isPlaylist, setIsPlaylist] = useState(false) const { i18n } = useI18n() const { client } = useRPC() const { pushMessage } = useToast() const urlInputRef = useRef(null) const customFilenameInputRef = useRef(null) const [isPending, startTransition] = useTransition() useEffect(() => { setCustomArgs('') }, [open]) /** * Retrive url from input, cli-arguments from checkboxes and emits via WebSocket */ const sendUrl = async (immediate?: string) => { for (const line of url.split('\n')) { const codes = new Array() if (pickedVideoFormat !== '') codes.push(pickedVideoFormat) if (pickedAudioFormat !== '') codes.push(pickedAudioFormat) if (pickedBestFormat !== '') codes.push(pickedBestFormat) await new Promise(r => setTimeout(r, 10)) client.download({ url: immediate || line, args: `${toFormatArgs(codes)} ${downloadTemplate}`, pathOverride: downloadPath ?? '', renameTo: settings.fileRenaming ? filenameTemplate + (settings.autoFileExtension ? fileExtension : '') : '', playlist: isPlaylist, }) setTimeout(() => { resetInput() setDownloadFormats(undefined) onDownloadStart(immediate || line) }, 100) } setUrl('') } /** * Retrive url from input and display the formats selection view */ const sendUrlFormatSelection = () => { setPickedAudioFormat('') setPickedVideoFormat('') setPickedBestFormat('') if (isPlaylist) { pushMessage('Format selection on playlist is not supported', 'warning') resetInput() onClose() return } setIsFormatsLoading(true) client.formats(url) ?.then(formats => { if (formats.result._type === 'playlist') { pushMessage('Format selection on playlist is not supported. Downloading as playlist.', 'info') resetInput() onClose() return } setDownloadFormats(formats.result) resetInput() }) .then(() => setIsFormatsLoading(false)) } const handleUrlChange = (e: React.ChangeEvent) => { setUrl(e.target.value) } const handleFilenameTemplateChange = (e: React.ChangeEvent) => { setFilenameTemplate(e.target.value) } const handleFileExtensionChange = (e: SelectChangeEvent) => { setFileExtension(e.target.value) } const handleCustomArgsChange = (e: React.ChangeEvent) => { setCustomArgs(e.target.value) } const parseUrlListFile = async (e: React.ChangeEvent) => { const files = e.currentTarget.files if (!files || files.length < 1) { return } const file = await files[0].text() file .split('\n') .forEach(u => sendUrl(u)) } const resetInput = () => { urlInputRef.current!.value = '' if (customFilenameInputRef.current) { customFilenameInputRef.current!.value = '' } } return ( Download theme.palette.background.default, minHeight: (theme) => `calc(99vh - ${theme.mixins.toolbar.minHeight}px)` }}> ), }} /> { settings.enableCustomArgs && } { settings.fileRenaming && } { settings.autoFileExtension && } { settings.pathOverriding && ({ label: dir, dir }))} autoHighlight getOptionLabel={(option) => option.label} onChange={(_, value) => { setDownloadPath(value?.dir!) }} renderOption={(props, option) => ( img': { mr: 2, flexShrink: 0 } }} {...props}> {option.label} )} sx={{ width: '100%', mt: 1 }} renderInput={(params) => } /> } {savedTemplates.length > 0 && } 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} />} ) } export default DownloadDialog