2024-11-02 15:14:02 -06:00
import React , { useState } from "react" ;
2025-04-17 17:02:39 -06:00
import { fetchVideoDownloaderConfig , getDownloaders , postDownload , putDownload } from "../api" ;
2025-05-05 12:56:24 -06:00
import {
APIButton ,
DirectorySearch ,
InfoHeader ,
InfoPopup ,
mergeDeep ,
RequiredAsterisk ,
useLocalStorage
} from "./Common" ;
2022-05-20 15:15:31 -06:00
import Icon from "semantic-ui-react/dist/commonjs/elements/Icon" ;
import Message from "semantic-ui-react/dist/commonjs/collections/Message" ;
2024-11-02 15:14:02 -06:00
import { Button , Form , FormInput , Header } from "./Theme" ;
import { Form as SForm , FormDropdown } from "semantic-ui-react" ;
2025-12-07 11:54:20 -07:00
import { Link } from "react-router" ;
2023-04-05 14:48:13 -06:00
import { TagsSelector } from "../Tags" ;
2023-08-30 20:19:32 -06:00
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid" ;
2025-05-05 12:56:24 -06:00
import {
commaSeparatedValidator ,
InputForm ,
NumberInputForm ,
ToggleForm ,
UrlInput ,
UrlsTextarea ,
useForm
} from "../hooks/useForm" ;
2024-11-02 15:14:02 -06:00
import {
channelFrequencyOptions ,
days30Option ,
defaultVideoFormatOption ,
defaultVideoResolutionOptions ,
Downloaders ,
downloadFormatOptions ,
downloadOrderOptions ,
downloadResolutionOptions ,
extendedFrequencyOptions ,
frequencyOptions ,
weeklyOption
} from "./Vars" ;
export function DepthInputForm ( { form , required , name = 'depth' , path = 'settings.depth' } ) {
return < NumberInputForm
form = { form }
required = { required }
name = { name }
path = { path }
label = 'Depth'
max = { 4 }
helpContent = 'Search the URLs provided, and any URLs they contain up to this depth. Warning: This can be exponential!'
/ >
}
2021-12-20 16:08:13 -07:00
2024-11-02 15:14:02 -06:00
export function MaximumPagesInputForm ( { form , required , name = 'max_pages' , path = 'settings.max_pages' } ) {
return < NumberInputForm
form = { form }
required = { required }
name = { name }
path = { path }
label = 'Maximum Pages'
max = { 100000 }
helpContent = 'Stop searching for files if this many pages have been searched.'
/ >
}
2021-12-20 16:08:13 -07:00
2024-11-02 15:14:02 -06:00
export function DestinationForm ( {
form ,
2025-01-24 14:28:04 -07:00
infoContent ,
2024-11-02 15:14:02 -06:00
label = 'Destination' ,
2024-12-03 12:44:38 -07:00
name = 'destination' ,
2024-11-02 15:14:02 -06:00
path = 'destination' ,
required = false ,
} ) {
2024-12-03 12:44:38 -07:00
const [ inputProps , inputAttrs ] = form . getCustomProps ( { name , path , required } ) ;
2024-11-02 15:14:02 -06:00
const { disabled , value , onChange } = inputProps ;
2025-01-24 14:28:04 -07:00
const infoPopup = infoContent ? < InfoPopup content = { infoContent } / > : null ;
2024-11-02 15:14:02 -06:00
return < SForm . Field >
2025-01-24 14:28:04 -07:00
< label > { label } { required && < RequiredAsterisk / > } { infoPopup } < / l a b e l >
2024-11-02 15:14:02 -06:00
< DirectorySearch
required = { required }
value = { value }
onSelect = { onChange }
disabled = { disabled }
2024-12-03 12:44:38 -07:00
id = 'destination_search_form'
2024-11-02 15:14:02 -06:00
/ >
< / S F o r m . F i e l d >
}
2021-12-20 16:08:13 -07:00
2024-11-02 15:14:02 -06:00
export function DownloadTagsSelector ( { form , limit , path = 'tag_names' , name = 'tag_names' } ) {
const [ inputProps , inputAttrs ] = form . getCustomProps ( { name , path } ) ;
const { value , onChange } = inputProps ;
return < TagsSelector
disabled = { form . disabled }
selectedTagNames = { value }
limit = { limit }
onChange = { onChange }
closeAfterAdd = { ! ! limit }
/ > ;
}
2021-12-20 16:08:13 -07:00
2024-11-02 15:14:02 -06:00
export function DownloadFrequencySelector ( {
form ,
freqOptions = frequencyOptions ,
name = 'frequency' ,
path = 'frequency' ,
} ) {
const [ inputProps , inputAttrs ] = form . getSelectionProps ( { name : 'frequency' , path } ) ;
return < FormDropdown
required
selection
label = 'Download Frequency'
placeholder = 'Frequency'
options = { freqOptions }
name = { name }
id = 'download_frequency_selector'
{ ... inputProps }
/ >
}
2021-12-20 16:08:13 -07:00
2024-11-02 15:14:02 -06:00
export function DownloaderSelector ( { form , name = 'sub_downloader' , path = 'sub_downloader' } ) {
const [ downloaders , setDownloaders ] = React . useState ( [ ] ) ;
const [ inputProps , inputAttrs ] = form . getSelectionProps ( { name , path , required : true } ) ;
2023-04-01 16:09:05 -06:00
2024-11-02 15:14:02 -06:00
const fetchDownloaders = async ( ) => {
let { downloaders : downloaders _ } = await getDownloaders ( ) ;
downloaders _ = downloaders _ . map ( ( i ) => {
return { key : i . name , text : i . pretty _name || i . name , value : i . name }
} )
setDownloaders ( downloaders _ ) ;
2023-04-02 16:30:01 -06:00
}
2024-11-02 15:14:02 -06:00
React . useEffect ( ( ) => {
fetchDownloaders ( ) ;
} , [ ] ) ;
2023-04-01 16:09:05 -06:00
2024-11-02 15:14:02 -06:00
return < FormDropdown selection required
label = 'Downloader'
placeholder = 'Select a downloader'
options = { downloaders }
2024-12-03 12:44:38 -07:00
id = 'downloader_selector'
2024-11-02 15:14:02 -06:00
{ ... inputProps }
/ >
}
2023-04-01 16:09:05 -06:00
2024-11-02 15:14:02 -06:00
export function ExcludedUrls ( { form , name = 'excluded_urls' , path = 'settings.excluded_urls' } ) {
return < InputForm
form = { form }
type = 'text'
name = { name }
path = { path }
helpContent = "Comma-separated list of keywords that will be ignored if they are in any link's URL"
label = 'Excluded URLs'
placeholder = 'prize,gift'
validator = { commaSeparatedValidator }
/ >
}
2023-04-03 09:34:42 -06:00
2024-11-02 15:14:02 -06:00
export function TitleInclusionInput ( { form , path = 'settings.title_include' } ) {
const [ inputProps , inputAttrs ] = form . getInputProps ( {
name : 'title_include' ,
path ,
validator : commaSeparatedValidator ,
} ) ;
return < >
2025-01-24 14:28:04 -07:00
< InfoHeader
2024-11-02 15:14:02 -06:00
headerSize = 'h4'
headerContent = 'Title Match Words'
popupContent = 'List of words, separated by commas, that titles must contain to be downloaded.'
/ >
< FormInput
placeholder = 'Shelter,Solar Power'
error = { inputProps . error }
>
< input { ... inputProps } / >
< / F o r m I n p u t >
< / >
}
export function TitleExclusionInput ( { form , path = 'settings.title_exclude' } ) {
const [ inputProps , inputAttrs ] = form . getInputProps ( {
name : 'title_exclude' ,
path ,
validator : commaSeparatedValidator ,
} ) ;
return < >
2025-01-24 14:28:04 -07:00
< InfoHeader
2024-11-02 15:14:02 -06:00
headerSize = 'h4'
headerContent = 'Title Exclusion Words'
popupContent = 'List of words, separated by commas, that may not appear in titles to be downloaded.'
/ >
< FormInput
placeholder = 'Giveaway,Prize'
error = { inputProps . error }
>
< input { ... inputProps } / >
< / F o r m I n p u t >
< / >
}
2022-11-09 08:33:55 -07:00
2024-11-02 15:14:02 -06:00
export function DownloadFormButtons ( { onCancel , form } ) {
return < Grid columns = { 2 } >
< Grid . Row >
< Grid . Column textAlign = 'left' >
< Button content = 'Cancel' onClick = { onCancel } type = 'button' / >
< / G r i d . C o l u m n >
< Grid . Column textAlign = 'right' >
2023-07-21 15:14:09 -06:00
< APIButton
2024-11-02 15:14:02 -06:00
disabled = { form . disabled || ! form . ready }
type = 'submit'
2023-07-21 15:14:09 -06:00
style = { { marginTop : '0.5em' } }
2024-11-02 15:14:02 -06:00
onClick = { form . onSubmit }
id = 'download_form_download_button'
2023-07-21 15:14:09 -06:00
> Download < / A P I B u t t o n >
2024-11-02 15:14:02 -06:00
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
}
export function EditDownloadFormButtons ( { onDelete , onCancel , form } ) {
return < Grid columns = { 2 } >
< Grid . Row >
< Grid . Column textAlign = 'left' >
< APIButton
confirmButton = 'Delete'
confirmContent = 'Delete this Download?'
confirmHeader = 'Delete'
color = 'red'
onClick = { onDelete }
> Delete < / A P I B u t t o n >
< / G r i d . C o l u m n >
< Grid . Column textAlign = 'right' >
< Button content = 'Cancel' onClick = { onCancel } / >
< APIButton
disabled = { form . disabled || ! form . ready }
type = 'submit'
style = { { marginTop : '0.5em' } }
onClick = { form . onSubmit }
> Save < / A P I B u t t o n >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
}
function VideoDownloadOrder ( { form , path = 'settings.download_order' } ) {
const [ inputProps , inputAttrs ] = form . getSelectionProps ( {
name : 'download_order' ,
path ,
} ) ;
return < FormDropdown selection
label = 'Download Order'
options = { downloadOrderOptions }
{ ... inputProps }
/ >
}
function VideoDownloadCountLimit ( { form , name = 'video_count_limit' , path = 'settings.video_count_limit' } ) {
return < NumberInputForm
form = { form }
helpContent = 'Stop downloading videos from this channel/playlist when this many have been downloaded.'
helpPosition = 'top right'
name = { name }
path = { path }
label = 'Video Count Limit'
placeholder = '100'
/ >
}
2025-12-06 14:49:46 -07:00
export function VideoResolutionSelectorForm ( {
form ,
name = 'video_resolutions' ,
path = 'settings.video_resolutions' ,
onChange
} ) {
2025-06-07 17:36:00 -06:00
const [ inputProps , inputAttrs ] = form . getSelectionProps ( {
name ,
path ,
type : 'array' ,
afterChange : onChange
} ) ;
2024-11-02 15:14:02 -06:00
return < >
2025-01-24 14:28:04 -07:00
< InfoHeader
2024-11-02 15:14:02 -06:00
headerSize = 'h5'
headerContent = 'Video Resolutions'
popupContent = 'Videos will be downloaded in the first available resolution from the list you select.'
for _ = 'video_resolutions_input'
/ >
< FormDropdown selection multiple
id = 'video_resolutions_input'
options = { downloadResolutionOptions }
{ ... inputProps }
/ >
< / >
}
function VideoFormatSelectorForm ( { form , name = 'video_format' , path = 'settings.video_format' } ) {
const [ inputProps , inputAttrs ] = form . getSelectionProps ( {
name ,
path ,
defaultValue : defaultVideoFormatOption ,
} ) ;
return < >
2025-01-24 14:28:04 -07:00
< InfoHeader
2024-11-02 15:14:02 -06:00
headerSize = 'h5'
headerContent = 'Video Format'
popupContent = 'Videos will be downloaded in this format, or transcoded if not available.'
for _ = 'video_format_input'
/ >
< FormDropdown selection
id = 'video_format_input'
options = { downloadFormatOptions }
{ ... inputProps }
/ >
< / >
}
function VideoDurationLimit ( { form , name , path , label , helpContent , placeholder , helpPosition } ) {
return < NumberInputForm
form = { form }
helpContent = { helpContent }
helpPosition = { helpPosition }
placeholder = { placeholder }
name = { name }
path = { path }
label = { label }
/ >
}
export function VideoTagsForm ( { form } ) {
return < >
2025-01-24 14:28:04 -07:00
< InfoHeader
2024-11-02 15:14:02 -06:00
headerSize = 'h4'
headerContent = 'Videos Tags'
popupContent = 'Tag all Videos with these Tags.'
/ >
< DownloadTagsSelector form = { form } / >
< / >
}
export function ChannelTagNameForm ( { form } ) {
return < >
2025-01-24 14:28:04 -07:00
< InfoHeader
2024-11-02 15:14:02 -06:00
headerSize = 'h4'
headerContent = 'Channel Tag'
popupContent = 'If the Channel is new, apply this Tag.'
/ >
< DownloadTagsSelector
form = { form }
limit = { 1 }
name = 'channel_tag_name'
path = 'settings.channel_tag_name'
/ >
< / >
2021-12-20 16:08:13 -07:00
}
2025-04-17 17:02:39 -06:00
export function UseBrowserProfile ( { form } ) {
const [ config , setConfig ] = useState ( null ) ;
const localFetchConfig = async ( ) => {
const result = await fetchVideoDownloaderConfig ( ) ;
setConfig ( result ) ;
2025-05-05 12:56:24 -06:00
if ( result ? . always _use _browser _profile === true ) {
// Enable the toggle if "always_use_browser_profile" is set.
form . setValue ( 'settings.use_browser_profile' , true ) ;
}
2025-04-17 17:02:39 -06:00
}
React . useEffect ( ( ) => {
localFetchConfig ( ) ;
} , [ ] ) ;
const popupContent = < > Use the browser profile to download videos .
This is useful for downloading videos that require a login . See : < i > Videos > Settings < /i></ > ;
const label = < InfoHeader
headerSize = 'h4'
headerContent = 'Use Browser Profile'
popupContent = { popupContent }
/ > ;
return < ToggleForm
form = { form }
label = { label }
disabled = { ! config ? . browser _profile }
name = 'use_browser_profile'
path = 'settings.use_browser_profile'
/ >
}
2025-12-06 14:49:46 -07:00
export function VideosDownloadForm ( {
singleDownload = true ,
onCancel ,
onSuccess : propOnSuccess ,
download ,
submitter : propSubmitter ,
actions
} ) {
2024-12-03 12:44:38 -07:00
const [ showMessage , setShowMessage ] = React . useState ( false ) ;
2025-06-07 17:36:00 -06:00
const [ userChangedResolutions , setUserChangedResolutions ] = React . useState ( false ) ;
const [ config , setConfig ] = React . useState ( null ) ;
2024-12-03 12:44:38 -07:00
2025-06-07 17:36:00 -06:00
// Keep video format in session to help user start downloads consistently.
2024-11-02 15:14:02 -06:00
const [ defaultVideoFormat , setDefaultVideoFormat ] = useLocalStorage ( 'video_format' , defaultVideoFormatOption ) ;
2025-06-07 17:36:00 -06:00
// Use config video resolutions if available, otherwise use default
const configResolutions = config && config . video _resolutions ? config . video _resolutions : defaultVideoResolutionOptions ;
2024-11-02 15:14:02 -06:00
const defaultFormData = {
urls : '' , // Textarea, one URL per line.
destination : '' ,
tag _names : [ ] ,
downloader : Downloaders . Video ,
settings : {
2025-04-17 17:02:39 -06:00
use _browser _profile : false ,
2024-11-02 15:14:02 -06:00
video _format : defaultVideoFormat ,
2025-06-07 17:36:00 -06:00
video _resolutions : configResolutions ,
2024-11-02 15:14:02 -06:00
}
2022-05-20 15:15:31 -06:00
}
2025-06-07 17:36:00 -06:00
const submitter = propSubmitter || ( async ( formData ) => {
2024-11-02 15:14:02 -06:00
const downloadData = {
destination : formData . destination ,
downloader : formData . downloader ,
settings : formData . settings ,
tag _names : formData . tag _names ,
urls : formData . urls . split ( /r?\n/ ) ,
}
await postDownload ( downloadData ) ;
2025-06-07 17:36:00 -06:00
} ) ;
2022-05-20 15:15:31 -06:00
2024-11-02 15:14:02 -06:00
const onSuccess = ( ) => {
2024-12-03 12:44:38 -07:00
setShowMessage ( true ) ;
2025-06-07 17:36:00 -06:00
if ( propOnSuccess ) {
propOnSuccess ( ) ;
} else {
form . reset ( ) ;
}
2022-05-20 15:15:31 -06:00
}
2024-12-03 12:44:38 -07:00
const form = useForm ( {
submitter ,
2025-06-07 17:36:00 -06:00
defaultFormData : download ? mergeDeep ( defaultFormData , download ) : defaultFormData ,
2024-12-03 12:44:38 -07:00
onSuccess ,
} ) ;
2024-11-02 15:14:02 -06:00
2025-06-07 17:36:00 -06:00
// Fetch video downloader config when the modal opens
2024-11-02 15:14:02 -06:00
React . useEffect ( ( ) => {
2025-06-07 17:36:00 -06:00
const fetchConfig = async ( ) => {
try {
const result = await fetchVideoDownloaderConfig ( ) ;
setConfig ( result ) ;
// Update form with video resolutions from config only if user hasn't changed them
// and if no download object was provided (this is a new download, not an edit)
if ( result && result . video _resolutions && ! userChangedResolutions && ! download ) {
form . setValue ( 'settings.video_resolutions' , result . video _resolutions ) ;
}
} catch ( error ) {
console . error ( 'Failed to fetch video downloader config:' , error ) ;
}
} ;
fetchConfig ( ) ;
} , [ ] ) ;
React . useEffect ( ( ) => {
const { video _format } = form . formData . settings ;
2024-11-02 15:14:02 -06:00
if ( video _format && video _format !== defaultVideoFormat ) {
setDefaultVideoFormat ( video _format ) ;
2022-05-20 15:15:31 -06:00
}
2024-11-02 15:14:02 -06:00
} , [ form . formData ] ) ;
2022-05-20 15:15:31 -06:00
2024-11-02 15:14:02 -06:00
const localOnCancel = ( e ) => {
if ( e ) e . preventDefault ( ) ;
if ( onCancel ) {
onCancel ( ) ;
2023-08-17 17:53:29 -06:00
}
}
2024-11-02 15:14:02 -06:00
// This form can handle a single Video download, or multiple video downloads.
const urlInput = singleDownload ?
< UrlInput required form = { form } path = 'urls' / >
: < UrlsTextarea required form = { form } / > ;
return < Form >
< Header as = 'h3' >
< Icon name = 'film' color = 'blue' / >
Videos
< / H e a d e r >
< p > Download each video at the URLs provided below . < / p >
< Grid stackable columns = { 1 } >
< Grid . Row >
< Grid . Column > { urlInput } < / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row >
< Grid . Row columns = { 1 } >
< Grid . Column >
< VideoTagsForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d . R o w >
< Grid . Row >
< Grid . Column >
< DestinationForm
form = { form }
helpContent = "Videos download into their Channel's directory, by default. If this is provided, then videos in this Channel/Playlist will download to this directory instead."
/ >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column width = { 11 } >
2025-06-07 17:36:00 -06:00
< VideoResolutionSelectorForm
form = { form }
onChange = { ( ) => setUserChangedResolutions ( true ) }
/ >
2024-11-02 15:14:02 -06:00
< / G r i d . C o l u m n >
< Grid . Column width = { 4 } >
< VideoFormatSelectorForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2025-04-17 17:02:39 -06:00
< Grid . Row >
< Grid . Column >
< UseBrowserProfile form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2024-11-02 15:14:02 -06:00
< Grid . Row columns = { 1 } >
< Grid . Column >
< ChannelTagNameForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2024-12-03 12:44:38 -07:00
{ showMessage && < SuccessfulDownloadSubmitMessage / > }
2024-11-02 15:14:02 -06:00
< Grid . Row >
< Grid . Column >
2025-12-06 14:49:46 -07:00
{ actions ? actions ( { onCancel : localOnCancel , form } ) :
< DownloadFormButtons onCancel = { localOnCancel } form = { form } / > }
2024-11-02 15:14:02 -06:00
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
< / F o r m >
2022-05-20 15:15:31 -06:00
}
2024-11-02 15:14:02 -06:00
export function VideoMinimumDurationForm ( { form } ) {
return < VideoDurationLimit
form = { form }
path = 'settings.minimum_duration'
label = 'Minimum Duration'
name = 'minimum_duration'
helpContent = 'Download only Videos this many seconds long, or greater.'
placeholder = '60'
/ >
}
2022-05-20 15:15:31 -06:00
2025-06-07 17:36:00 -06:00
export function EditVideosDownloadForm ( {
2025-12-06 14:49:46 -07:00
download ,
onCancel ,
onSuccess ,
onDelete ,
actions = EditDownloadFormButtons ,
} ) {
2025-06-07 17:36:00 -06:00
const submitter = async ( formData ) => {
// Create a copy of settings without channel_url and channel_id
const settings = { ... formData . settings } ;
// These settings are created when downloading the video, we can't submit them again.
delete settings . channel _url ;
delete settings . channel _id ;
const downloadData = {
destination : formData . destination ,
downloader : formData . downloader ,
settings : settings ,
tag _names : formData . tag _names ,
urls : formData . urls . split ( /r?\n/ ) ,
}
await putDownload ( download . id , downloadData ) ;
}
return < VideosDownloadForm
singleDownload = { true }
onCancel = { onCancel }
onSuccess = { onSuccess }
download = { download }
submitter = { submitter }
actions = { ( props ) => actions ( { ... props , onDelete } ) }
/ >
}
2024-11-02 15:14:02 -06:00
export function VideoMaximumDurationForm ( { form } ) {
return < VideoDurationLimit
form = { form }
path = 'settings.maximum_duration'
label = 'Maximum Duration'
name = 'maximum_duration'
helpContent = 'Do not download videos longer than this many seconds.'
placeholder = '3600'
/ >
}
2022-05-20 15:15:31 -06:00
2024-11-02 15:14:02 -06:00
export function ChannelDownloadForm ( {
download ,
onCancel ,
onSuccess ,
onDelete = async ( ) => {
} ,
submitter ,
actions = null ,
clearOnSuccess = true ,
channel _id = null ,
} ) {
2024-12-03 12:44:38 -07:00
const [ showMessage , setShowMessage ] = React . useState ( false ) ;
2025-06-07 17:36:00 -06:00
const [ config , setConfig ] = React . useState ( null ) ;
const [ isConfigLoaded , setIsConfigLoaded ] = React . useState ( false ) ;
const [ userChangedResolutions , setUserChangedResolutions ] = React . useState ( false ) ;
2024-11-02 15:14:02 -06:00
// May have received submitter from EditChannelDownloadForm.
submitter = submitter || ( async ( formData ) => {
const downloadData = {
destination : formData . destination ,
downloader : formData . downloader ,
frequency : formData . frequency ,
settings : formData . settings ,
sub _downloader : formData . sub _downloader ,
tag _names : formData . tag _names || [ ] ,
urls : [ formData . url , ] ,
2022-05-20 15:15:31 -06:00
}
2024-11-02 15:14:02 -06:00
await postDownload ( downloadData ) ;
} ) ;
2024-12-03 12:44:38 -07:00
const localOnSuccess = async ( ) => {
setShowMessage ( true ) ;
if ( onSuccess ) {
onSuccess ( ) ;
}
}
2025-06-07 17:36:00 -06:00
// Keep video format in session to help user start downloads consistently.
2024-11-02 15:14:02 -06:00
const [ defaultVideoFormat , setDefaultVideoFormat ] = useLocalStorage ( 'video_format' , defaultVideoFormatOption ) ;
2025-06-07 17:36:00 -06:00
// Use config video resolutions if available, otherwise use default
const configResolutions = config && config . video _resolutions ? config . video _resolutions : defaultVideoResolutionOptions ;
2024-11-02 15:14:02 -06:00
const emptyFormData = {
destination : '' ,
downloader : Downloaders . VideoChannel ,
frequency : days30Option . value ,
url : '' ,
settings : {
channel _id ,
channel _tag _name : [ ] ,
download _order : 'newest' ,
maximum _duration : null ,
minimum _duration : null ,
title _exclude : null ,
title _include : null ,
2025-04-17 17:02:39 -06:00
use _browser _profile : false ,
2024-11-02 15:14:02 -06:00
video _count _limit : null ,
video _format : defaultVideoFormat ,
2025-06-07 17:36:00 -06:00
video _resolutions : configResolutions ,
2024-11-02 15:14:02 -06:00
} ,
sub _downloader : Downloaders . Video ,
tag _names : [ ] ,
} ;
const form = useForm ( {
submitter ,
defaultFormData : mergeDeep ( emptyFormData , download ) ,
emptyFormData ,
2024-12-03 12:44:38 -07:00
onSuccess : localOnSuccess ,
2024-11-02 15:14:02 -06:00
clearOnSuccess ,
} ) ;
2025-06-07 17:36:00 -06:00
// Fetch video downloader config when the modal opens
2024-11-02 15:14:02 -06:00
React . useEffect ( ( ) => {
2025-06-07 17:36:00 -06:00
const fetchConfig = async ( ) => {
try {
const result = await fetchVideoDownloaderConfig ( ) ;
setConfig ( result ) ;
setIsConfigLoaded ( true ) ;
// Update form with video resolutions from config only if user hasn't changed them
// and if no download object was provided (this is a new download, not an edit)
if ( result && result . video _resolutions && ! userChangedResolutions && ! download ) {
form . setValue ( 'settings.video_resolutions' , result . video _resolutions ) ;
}
} catch ( error ) {
console . error ( 'Failed to fetch video downloader config:' , error ) ;
setIsConfigLoaded ( true ) ;
}
} ;
fetchConfig ( ) ;
} , [ ] ) ;
React . useEffect ( ( ) => {
const { video _format } = form . formData . settings ;
2024-11-02 15:14:02 -06:00
if ( video _format && video _format !== defaultVideoFormat ) {
setDefaultVideoFormat ( video _format ) ;
}
} , [ form . formData ] ) ;
const onceMessage = < Message >
< Message . Header > Download Once < / M e s s a g e . H e a d e r >
< Message . Content > You have selected a frequency of Once , this is useful when you want to download
all videos in a Playlist , and when you do not want to download any videos added to the playlist
in the future . < / M e s s a g e . C o n t e n t >
< / M e s s a g e > ;
// Default to "new" download buttons.
actions = actions || DownloadFormButtons ;
const actionsElm = actions ( { onDelete , onCancel , form } ) ;
return < Form >
< Header as = 'h3' > < Icon name = 'film' color = 'blue' / > Channel / Playlist < / H e a d e r >
< Grid stackable columns = { 1 } >
< Grid . Row >
< Grid . Column >
< UrlInput required form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 1 } >
< Grid . Column >
< VideoTagsForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column mobile = { 4 } tablet = { 4 } >
< DownloadFrequencySelector form = { form } freqOptions = { channelFrequencyOptions } / >
< / G r i d . C o l u m n >
< Grid . Column mobile = { 4 } tablet = { 12 } >
< DestinationForm
form = { form }
helpContent = 'Destination is not required. Videos will download into the automatically created Channel directory.'
/ >
< / G r i d . C o l u m n >
< / G r i d . R o w >
{ form . formData . frequency === 0 && < Grid . Row > < Grid . Column > { onceMessage } < / G r i d . C o l u m n > < / G r i d . R o w > }
< Grid . Row columns = { 2 } >
< Grid . Column >
< TitleInclusionInput form = { form } / >
< / G r i d . C o l u m n >
< Grid . Column >
< TitleExclusionInput form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column mobile = { 4 } tablet = { 5 } >
< VideoDownloadOrder form = { form } / >
< / G r i d . C o l u m n >
< Grid . Column mobile = { 4 } tablet = { 5 } >
< VideoDownloadCountLimit form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column width = { 11 } >
2025-06-07 17:36:00 -06:00
< VideoResolutionSelectorForm
form = { form }
onChange = { ( ) => setUserChangedResolutions ( true ) }
/ >
2024-11-02 15:14:02 -06:00
< / G r i d . C o l u m n >
< Grid . Column width = { 4 } >
< VideoFormatSelectorForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column tablet = { 8 } computer = { 4 } >
< VideoMinimumDurationForm form = { form } / >
< / G r i d . C o l u m n >
< Grid . Column tablet = { 8 } computer = { 4 } >
< VideoMaximumDurationForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 1 } >
< Grid . Column >
< ChannelTagNameForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2024-12-03 12:44:38 -07:00
{ showMessage && < SuccessfulDownloadSubmitMessage / > }
2024-11-02 15:14:02 -06:00
< Grid . Row >
< Grid . Column textAlign = 'right' >
{ actionsElm }
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
< / F o r m >
}
2021-12-20 16:08:13 -07:00
2024-11-02 15:14:02 -06:00
export function EditChannelDownloadForm ( {
download ,
onCancel ,
onSuccess ,
onDelete ,
withTags = true ,
actions = EditDownloadFormButtons ,
} ) {
const submitter = async ( formData ) => {
const downloadData = {
destination : formData . destination ,
downloader : formData . downloader ,
frequency : formData . frequency ,
settings : formData . settings ,
2024-12-03 12:44:38 -07:00
tag _names : formData . tag _names || [ ] ,
2024-11-02 15:14:02 -06:00
urls : [ formData . url , ] ,
}
await putDownload ( download . id , downloadData ) ;
2022-10-11 08:12:32 -06:00
}
2024-11-02 15:14:02 -06:00
return < ChannelDownloadForm
download = { download }
submitter = { submitter }
onCancel = { onCancel }
onSuccess = { onSuccess }
onDelete = { onDelete }
withTags = { withTags }
actions = { actions }
clearOnSuccess = { false }
/ >
}
function SuccessfulDownloadSubmitMessage ( ) {
2024-12-03 12:44:38 -07:00
return < Grid . Row >
< Grid . Column > < Message positive >
< Message . Header > Download Submitted < / M e s s a g e . H e a d e r >
< Message . Content >
< Link to = '/admin' > < Icon name = 'checkmark' / > View downloads < / L i n k >
< / M e s s a g e . C o n t e n t >
< / M e s s a g e >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2024-11-02 15:14:02 -06:00
}
2025-06-07 17:36:00 -06:00
export function EditArchiveDownloadForm ( {
2025-12-06 14:49:46 -07:00
download ,
onCancel ,
onSuccess ,
onDelete ,
actions = EditDownloadFormButtons ,
} ) {
2025-06-07 17:36:00 -06:00
const submitter = async ( formData ) => {
const downloadData = {
downloader : formData . downloader ,
tag _names : formData . tag _names ,
urls : formData . urls . split ( /\r?\n/ ) ,
}
await putDownload ( download . id , downloadData ) ;
}
return < ArchiveDownloadForm
download = { download }
onCancel = { onCancel }
onSuccess = { onSuccess }
submitter = { submitter }
actions = { ( props ) => actions ( { ... props , onDelete } ) }
/ >
}
export function ArchiveDownloadForm ( { download , onCancel , onSuccess : propOnSuccess , submitter : propSubmitter , actions } ) {
2024-11-02 15:14:02 -06:00
const [ showMessage , setShowMessage ] = React . useState ( false ) ;
2025-06-07 17:36:00 -06:00
const submitter = propSubmitter || ( async ( formData ) => {
2024-11-02 15:14:02 -06:00
const downloadData = {
downloader : formData . downloader ,
tag _names : formData . tag _names ,
urls : formData . urls . split ( /\r?\n/ ) ,
}
await postDownload ( downloadData ) ;
2025-06-07 17:36:00 -06:00
} ) ;
2024-11-02 15:14:02 -06:00
const emptyFormData = {
downloader : Downloaders . Archive ,
urls : '' ,
tag _names : [ ] ,
} ;
2025-06-07 17:36:00 -06:00
const onSuccess = ( ) => {
setShowMessage ( true ) ;
if ( propOnSuccess ) {
propOnSuccess ( ) ;
}
}
2024-11-02 15:14:02 -06:00
const form = useForm ( {
submitter ,
defaultFormData : mergeDeep ( emptyFormData , download ) ,
emptyFormData ,
2025-06-07 17:36:00 -06:00
clearOnSuccess : ! propOnSuccess ,
onSuccess ,
2024-11-02 15:14:02 -06:00
} ) ;
2025-06-07 17:36:00 -06:00
const localOnCancel = ( e ) => {
if ( e ) e . preventDefault ( ) ;
if ( onCancel ) {
onCancel ( ) ;
}
}
2024-11-02 15:14:02 -06:00
return < Form >
< Header as = 'h3' > < Icon name = 'file text' color = 'green' / > Archives < / H e a d e r >
< p > Create a Singlefile Archive for each of the URLs provided below . < / p >
< Grid stackable columns = { 1 } >
< Grid . Row >
< Grid . Column >
< UrlsTextarea required form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row >
< Grid . Column >
< DownloadTagsSelector form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2024-12-03 12:44:38 -07:00
{ showMessage && < SuccessfulDownloadSubmitMessage / > }
2024-11-02 15:14:02 -06:00
< Grid . Row >
< Grid . Column textAlign = 'right' >
2025-12-06 14:49:46 -07:00
{ actions ? actions ( { onCancel : localOnCancel , form } ) :
< DownloadFormButtons onCancel = { localOnCancel } form = { form } / > }
2024-11-02 15:14:02 -06:00
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
< / F o r m >
}
export function RSSDownloadForm ( { download , submitter , onDelete , onCancel , actions , clearOnSuccess = true } ) {
const [ showMessage , setShowMessage ] = React . useState ( false ) ;
2025-06-07 17:36:00 -06:00
const [ config , setConfig ] = React . useState ( null ) ;
const [ isConfigLoaded , setIsConfigLoaded ] = React . useState ( false ) ;
const [ userChangedResolutions , setUserChangedResolutions ] = React . useState ( false ) ;
2024-11-02 15:14:02 -06:00
const [ defaultVideoFormat , setDefaultVideoFormat ] = useLocalStorage ( 'video_format' , defaultVideoFormatOption ) ;
submitter = submitter || ( async ( formData ) => {
const downloadData = {
destination : formData . destination ,
downloader : formData . downloader ,
frequency : formData . frequency ,
settings : formData . settings ,
sub _downloader : formData . sub _downloader ,
2024-12-03 12:44:38 -07:00
tag _names : formData . tag _names || [ ] ,
2024-11-02 15:14:02 -06:00
urls : [ formData . url ] ,
}
await postDownload ( downloadData ) ;
} ) ;
2025-06-07 17:36:00 -06:00
// Use config video resolutions if available, otherwise use default
const configResolutions = config && config . video _resolutions ? config . video _resolutions : defaultVideoResolutionOptions ;
2024-11-02 15:14:02 -06:00
const emptyFormData = {
destination : null ,
downloader : Downloaders . RSS ,
frequency : weeklyOption . value ,
sub _downloader : null ,
settings : {
excluded _urls : null ,
title _exclude : null ,
title _include : null ,
2025-06-07 17:36:00 -06:00
video _resolutions : configResolutions ,
2024-11-02 15:14:02 -06:00
video _format : defaultVideoFormat ,
} ,
tag _names : [ ] ,
url : '' ,
} ;
2022-10-11 08:12:32 -06:00
2024-11-02 15:14:02 -06:00
const form = useForm ( {
submitter ,
defaultFormData : mergeDeep ( emptyFormData , download ) ,
emptyFormData ,
clearOnSuccess ,
onSuccess : async ( ) => setShowMessage ( true ) ,
} ) ;
2025-06-07 17:36:00 -06:00
// Fetch video downloader config when the modal opens
2024-11-02 15:14:02 -06:00
React . useEffect ( ( ) => {
2025-06-07 17:36:00 -06:00
const fetchConfig = async ( ) => {
try {
const result = await fetchVideoDownloaderConfig ( ) ;
setConfig ( result ) ;
setIsConfigLoaded ( true ) ;
// Update form with video resolutions from config only if user hasn't changed them
// and if no download object was provided (this is a new download, not an edit)
if ( result && result . video _resolutions && ! userChangedResolutions && ! download ) {
form . setValue ( 'settings.video_resolutions' , result . video _resolutions ) ;
}
} catch ( error ) {
console . error ( 'Failed to fetch video downloader config:' , error ) ;
setIsConfigLoaded ( true ) ;
}
} ;
fetchConfig ( ) ;
} , [ ] ) ;
React . useEffect ( ( ) => {
const { video _format } = form . formData . settings ;
2024-11-02 15:14:02 -06:00
if ( video _format && video _format !== defaultVideoFormat ) {
setDefaultVideoFormat ( video _format ) ;
}
} , [ form . formData ] ) ;
// Default to "new" download buttons.
actions = actions || DownloadFormButtons ;
const actionsElm = actions ( { onDelete , onCancel , form } ) ;
let downloaderRows ;
if ( form . formData . sub _downloader === Downloaders . Video ) {
downloaderRows = < >
< Grid . Row columns = { 1 } >
< Grid . Column >
< DestinationForm
form = { form }
helpContent = "Videos download into their Channel's directory, by default. If this is provided, then videos in this feed will download to this directory instead."
2022-10-11 08:12:32 -06:00
/ >
2024-11-02 15:14:02 -06:00
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column mobile = { 16 } computer = { 8 } >
< TitleInclusionInput form = { form } / >
< / G r i d . C o l u m n >
< Grid . Column mobile = { 16 } computer = { 8 } >
< TitleExclusionInput form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column mobile = { 16 } computer = { 11 } >
2025-06-07 17:36:00 -06:00
< VideoResolutionSelectorForm
form = { form }
onChange = { ( ) => setUserChangedResolutions ( true ) }
/ >
2024-11-02 15:14:02 -06:00
< / G r i d . C o l u m n >
< Grid . Column mobile = { 8 } computer = { 3 } >
< VideoFormatSelectorForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column tablet = { 8 } computer = { 4 } >
< VideoMinimumDurationForm form = { form } / >
< / G r i d . C o l u m n >
< Grid . Column tablet = { 8 } computer = { 4 } >
< VideoMaximumDurationForm form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / > ;
} else if ( form . formData . sub _downloader === Downloaders . Archive ) {
downloaderRows = < Grid . Row >
< Grid . Column >
< ExcludedUrls form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w > ;
2021-12-20 16:08:13 -07:00
}
2024-11-02 15:14:02 -06:00
return < Form >
< Header as = 'h3' > < Icon name = 'rss' color = 'orange' / > RSS Feed < / H e a d e r >
< p > Download each link provided by this RSS feed using the selected downloader . < / p >
< Grid columns = { 1 } >
< Grid . Row >
< Grid . Column >
< UrlInput required form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row >
< Grid . Column >
< DownloadTagsSelector form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column width = { 8 } >
< DownloadFrequencySelector form = { form } freqOptions = { extendedFrequencyOptions } / >
< / G r i d . C o l u m n >
< Grid . Column width = { 8 } >
< DownloaderSelector form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
{ downloaderRows }
2024-12-03 12:44:38 -07:00
{ showMessage && < SuccessfulDownloadSubmitMessage / > }
2024-11-02 15:14:02 -06:00
< Grid . Row >
< Grid . Column textAlign = 'right' >
{ actionsElm }
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
< / F o r m >
2021-12-20 16:08:13 -07:00
}
2022-05-20 15:15:31 -06:00
2024-11-02 15:14:02 -06:00
export function EditRSSDownloadForm ( { download , onDelete , onCancel , actions = EditDownloadFormButtons } ) {
2023-08-30 20:19:32 -06:00
2024-11-02 15:14:02 -06:00
const submitter = async ( formData ) => {
const downloadData = {
2024-12-03 12:44:38 -07:00
destination : formData . destination ,
2024-11-02 15:14:02 -06:00
downloader : formData . downloader ,
frequency : formData . frequency ,
settings : formData . settings ,
sub _downloader : formData . sub _downloader ,
2024-12-03 12:44:38 -07:00
tag _names : formData . tag _names || [ ] ,
2024-11-02 15:14:02 -06:00
urls : [ formData . url , ] ,
}
await putDownload ( download . id , downloadData ) ;
2023-08-30 20:19:32 -06:00
}
2024-11-02 15:14:02 -06:00
return < RSSDownloadForm
submitter = { submitter }
onDelete = { onDelete }
onCancel = { onCancel }
download = { download }
actions = { actions }
clearOnSuccess = { false }
/ >
}
export function EditZimDownloadForm ( { download , onDelete , onCancel , actions = EditDownloadFormButtons } ) {
2023-08-30 20:19:32 -06:00
2024-11-02 15:14:02 -06:00
const submitter = async ( formData ) => {
const downloadData = {
downloader : formData . downloader ,
frequency : formData . frequency ,
settings : formData . settings ,
sub _downloader : formData . sub _downloader ,
tag _names : formData . tag _names ,
urls : [ formData . url , ] ,
2023-08-30 20:19:32 -06:00
}
2024-11-02 15:14:02 -06:00
await putDownload ( download . id , downloadData ) ;
2023-08-30 20:19:32 -06:00
}
2024-11-02 15:14:02 -06:00
const [ showMessage , setShowMessage ] = React . useState ( false ) ;
const emptyFormData = {
downloader : Downloaders . RSS ,
frequency : weeklyOption . value ,
sub _downloader : null ,
tag _names : [ ] ,
url : '' ,
} ;
2023-08-30 20:19:32 -06:00
2024-11-02 15:14:02 -06:00
const form = useForm ( {
submitter ,
defaultFormData : mergeDeep ( emptyFormData , download ) ,
emptyFormData ,
onSuccess : async ( ) => setShowMessage ( true ) ,
} ) ;
// Default to "new" download buttons.
actions = actions || DownloadFormButtons ;
const actionsElm = actions ( { onDelete , onCancel , form } ) ;
return < Form >
< Header as = 'h3' > Zim File < / H e a d e r >
< Grid stackable columns = { 1 } >
< Grid . Row >
< Grid . Column >
< UrlInput required form = { form } disabled = { true } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column width = { 8 } >
< DownloadFrequencySelector form = { form } longFrequenciesAvailable = { true } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2024-12-03 12:44:38 -07:00
{ showMessage && < SuccessfulDownloadSubmitMessage / > }
2024-11-02 15:14:02 -06:00
< Grid . Row >
< Grid . Column textAlign = 'right' >
{ actionsElm }
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
< / F o r m >
}
export function FilesDownloadForm ( {
download ,
submitter ,
onCancel ,
actions = DownloadFormButtons ,
clearOnSuccess = true ,
} ) {
const [ showMessage , setShowMessage ] = React . useState ( false ) ;
submitter = submitter || ( async ( formData ) => {
const downloadData = {
downloader : Downloaders . File ,
tag _names : formData . tag _names ,
destination : formData . destination ,
urls : formData . urls . split ( /\r?\n/ ) ,
}
await postDownload ( downloadData ) ;
} ) ;
const emptyFormData = {
destination : null ,
tag _names : [ ] ,
urls : '' ,
} ;
const form = useForm ( {
submitter ,
defaultFormData : mergeDeep ( emptyFormData , download ) ,
emptyFormData ,
clearOnSuccess ,
onSuccess : async ( ) => setShowMessage ( true ) ,
} ) ;
const actionsElm = actions ( { onCancel , form } ) ;
return < Form >
< Header as = 'h3' > < Icon name = 'file' / > Files < / H e a d e r >
< p > Download each file at the URLs provided below . < / p >
< Grid >
< Grid . Row >
< Grid . Column >
< UrlsTextarea form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row >
< Grid . Column >
< DownloadTagsSelector form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row >
< Grid . Column >
< DestinationForm required form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2024-12-03 12:44:38 -07:00
{ showMessage && < SuccessfulDownloadSubmitMessage / > }
2024-11-02 15:14:02 -06:00
< Grid . Row >
< Grid . Column textAlign = 'right' >
{ actionsElm }
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
< / F o r m >
}
const suffixValidator = ( value ) => {
const error = commaSeparatedValidator ( value ) ;
if ( error ) {
return error ;
2023-08-30 20:19:32 -06:00
}
2024-11-02 15:14:02 -06:00
const suffixes = value . split ( ',' ) ;
for ( const suffix of suffixes ) {
if ( ! suffix . startsWith ( '.' ) ) {
return 'Suffix must start with .' ;
}
if ( suffix . length === 1 ) {
return 'Suffix must have characters after .' ;
}
}
}
export function SuffixFormInput ( { form , name = 'suffix' , path = 'settings.suffix' } ) {
return < InputForm
form = { form }
label = 'File Suffixes'
helpContent = 'Comma-separated list of file suffixes that should be downloaded'
required = { true }
placeholder = '.pdf,.mp4'
name = { name }
path = { path }
validator = { suffixValidator }
/ >
}
export function ScrapeFilesDownloadForm ( {
download ,
submitter ,
clearOnSuccess ,
2024-12-03 12:44:38 -07:00
onDelete ,
2024-11-02 15:14:02 -06:00
onCancel ,
2024-12-03 12:44:38 -07:00
onSuccess ,
2024-11-02 15:14:02 -06:00
actions = DownloadFormButtons ,
2024-12-03 12:44:38 -07:00
singleDownload = false ,
2024-11-02 15:14:02 -06:00
} ) {
const [ showMessage , setShowMessage ] = React . useState ( false ) ;
submitter = submitter || ( async ( formData ) => {
const downloadData = {
downloader : Downloaders . ScrapeHtml ,
sub _downloader : Downloaders . File ,
2024-12-03 12:44:38 -07:00
tag _names : formData . tag _names || [ ] ,
2024-11-02 15:14:02 -06:00
destination : formData . destination ,
urls : formData . urls . split ( /\r?\n/ ) ,
settings : formData . settings ,
}
await postDownload ( downloadData ) ;
} ) ;
const emptyFormData = {
destination : null ,
tag _names : [ ] ,
urls : '' ,
settings : {
depth : 1 ,
max _pages : 1 ,
suffix : '' ,
}
} ;
2024-12-03 12:44:38 -07:00
const localOnSuccess = async ( ) => {
setShowMessage ( true ) ;
if ( onSuccess ) {
onSuccess ( ) ;
}
}
2024-11-02 15:14:02 -06:00
const form = useForm ( {
submitter ,
defaultFormData : mergeDeep ( emptyFormData , download ) ,
emptyFormData ,
clearOnSuccess ,
2024-12-03 12:44:38 -07:00
onSuccess : localOnSuccess ,
2024-11-02 15:14:02 -06:00
} ) ;
2024-12-03 12:44:38 -07:00
const urlInput = singleDownload ?
< UrlInput required form = { form } path = 'url' / >
: < UrlsTextarea required form = { form } / > ;
// Default to "new" download buttons.
actions = actions || DownloadFormButtons ;
const actionsElm = actions ( { onDelete , onCancel , form } ) ;
2024-11-02 15:14:02 -06:00
return < Form >
< Header as = 'h3' > < Icon name = 'file alternate' color = 'red' / > Scrape Files < / H e a d e r >
< p > Search each of the URLs for files matching the suffix ( . pdf , etc . ) . < / p >
< Grid >
< Grid . Row >
< Grid . Column >
2024-12-03 12:44:38 -07:00
{ urlInput }
2024-11-02 15:14:02 -06:00
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column width = { 10 } >
< DestinationForm required = { true } form = { form } / >
< / G r i d . C o l u m n >
< Grid . Column width = { 6 } >
< SuffixFormInput required = { true } form = { form } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
< Grid . Row columns = { 2 } >
< Grid . Column width = { 4 } >
< DepthInputForm form = { form } required = { true } / >
< / G r i d . C o l u m n >
< Grid . Column width = { 4 } >
< MaximumPagesInputForm form = { form } required = { true } / >
< / G r i d . C o l u m n >
< / G r i d . R o w >
2024-12-03 12:44:38 -07:00
{ showMessage && < SuccessfulDownloadSubmitMessage / > }
2024-11-02 15:14:02 -06:00
< Grid . Row >
< Grid . Column >
{ actionsElm }
< / G r i d . C o l u m n >
< / G r i d . R o w >
< / G r i d >
< / F o r m >
2023-08-30 20:19:32 -06:00
}
2024-12-03 12:44:38 -07:00
export function EditScrapeFilesDownloadForm ( { download , onDelete , onCancel , onSuccess } ) {
const submitter = async ( formData ) => {
const downloadData = {
downloader : Downloaders . ScrapeHtml ,
sub _downloader : Downloaders . File ,
tag _names : formData . tag _names || [ ] ,
destination : formData . destination ,
urls : [ formData . url ] ,
settings : formData . settings ,
}
await postDownload ( downloadData ) ;
} ;
return < ScrapeFilesDownloadForm
download = { download }
submitter = { submitter }
onDelete = { onDelete }
onCancel = { onCancel }
onSuccess = { onSuccess }
actions = { EditDownloadFormButtons }
singleDownload = { true }
/ >
}
2022-12-23 16:04:09 -07:00
export function DownloadMenu ( { onOpen , disabled } ) {
2022-05-20 15:15:31 -06:00
const [ downloader , setDownloader ] = useState ( ) ;
2022-08-06 05:22:07 +00:00
const localOnOpen = ( name ) => {
setDownloader ( name ) ;
2023-05-03 08:56:35 -06:00
if ( onOpen ) {
onOpen ( name ) ;
}
2022-08-06 05:22:07 +00:00
}
2022-09-29 16:36:39 -06:00
let body = ( < >
2022-12-23 16:04:09 -07:00
< Button
color = 'blue'
content = 'Videos'
disabled = { disabled }
onClick = { ( ) => localOnOpen ( 'video' ) }
style = { { marginBottom : '1em' } }
/ >
< Button
color = 'green'
2023-04-18 22:03:45 -06:00
content = 'Archives'
2022-12-23 16:04:09 -07:00
disabled = { disabled }
onClick = { ( ) => localOnOpen ( 'archive' ) }
style = { { marginBottom : '1em' } }
/ >
< Button
color = 'blue'
content = 'Channel/Playlist'
disabled = { disabled }
onClick = { ( ) => localOnOpen ( 'video_channel' ) }
style = { { marginBottom : '1em' } }
/ >
< Button
content = 'RSS Feed'
disabled = { disabled }
onClick = { ( ) => localOnOpen ( 'rss' ) }
style = { { marginBottom : '1em' } }
/ >
2023-04-18 22:03:45 -06:00
< Button
color = 'black'
2023-11-02 10:07:49 -06:00
content = 'Files'
2023-04-18 22:03:45 -06:00
disabled = { disabled }
onClick = { ( ) => localOnOpen ( 'file' ) }
style = { { marginBottom : '1em' } }
/ >
2023-08-30 20:19:32 -06:00
< Button
color = 'red'
2023-08-31 08:31:19 -06:00
content = 'Scrape'
2023-08-30 20:19:32 -06:00
disabled = { disabled }
2023-08-31 08:31:19 -06:00
onClick = { ( ) => localOnOpen ( 'scrape' ) }
2023-08-30 20:19:32 -06:00
style = { { marginBottom : '1em' } }
/ >
2022-09-29 16:36:39 -06:00
< / > ) ;
2022-05-20 15:15:31 -06:00
function clearSelected ( ) {
2022-08-06 05:22:07 +00:00
localOnOpen ( null ) ;
2022-05-20 15:15:31 -06:00
body = null ;
}
const downloaders = {
2024-11-02 15:14:02 -06:00
archive : < ArchiveDownloadForm onCancel = { clearSelected } / > ,
video : < VideosDownloadForm singleDownload = { false } onCancel = { clearSelected } / > ,
video _channel : < ChannelDownloadForm onCancel = { clearSelected } / > ,
rss : < RSSDownloadForm onCancel = { clearSelected } / > ,
file : < FilesDownloadForm onCancel = { clearSelected } / > ,
scrape : < ScrapeFilesDownloadForm onCancel = { clearSelected } / >
2022-05-20 15:15:31 -06:00
} ;
2024-11-02 15:14:02 -06:00
if ( downloader in downloaders ) {
const downloaderForm = downloaders [ downloader ] ;
body = < >
{ downloaderForm }
< / >
}
2022-05-20 15:15:31 -06:00
2022-08-06 05:22:07 +00:00
return < >
{ body }
< / >
2022-05-20 15:15:31 -06:00
}