mirror of
https://github.com/zoriya/flood.git
synced 2026-06-05 11:49:24 +00:00
client: convert "add torrents" modals to Functional Component
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import Dropzone from 'react-dropzone';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {FC, useEffect, useState} from 'react';
|
||||
|
||||
import CloseIcon from '../../icons/Close';
|
||||
import FileIcon from '../../icons/File';
|
||||
import FilesIcon from '../../icons/Files';
|
||||
import {FormRowItem} from '../../../ui';
|
||||
|
||||
export type ProcessedFiles = Array<{name: string; data: string}>;
|
||||
|
||||
interface FileDropzoneProps {
|
||||
onFilesChanged: (files: ProcessedFiles) => void;
|
||||
}
|
||||
|
||||
const FileDropzone: FC<FileDropzoneProps> = ({onFilesChanged}: FileDropzoneProps) => {
|
||||
const [files, setFiles] = useState<ProcessedFiles>([]);
|
||||
|
||||
useEffect(() => {
|
||||
onFilesChanged(files);
|
||||
}, [files]);
|
||||
|
||||
return (
|
||||
<FormRowItem>
|
||||
<label className="form__element__label">
|
||||
<FormattedMessage id="torrents.add.torrents.label" />
|
||||
</label>
|
||||
{files.length > 0 ? (
|
||||
<ul
|
||||
className="dropzone__selected-files interactive-list"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
}}>
|
||||
{files.map((file, index) => (
|
||||
<li className="dropzone__selected-files__file interactive-list__item" key={file.name} title={file.name}>
|
||||
<span className="interactive-list__icon">
|
||||
<FileIcon />
|
||||
</span>
|
||||
<span className="interactive-list__label">{file.name}</span>
|
||||
<span
|
||||
className="interactive-list__icon interactive-list__icon--action interactive-list__icon--action--warning"
|
||||
onClick={() => {
|
||||
const newArray = files.slice();
|
||||
newArray.splice(index, 1);
|
||||
setFiles(newArray);
|
||||
}}>
|
||||
<CloseIcon />
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
<Dropzone
|
||||
onDrop={(addedFiles: Array<File>) => {
|
||||
addedFiles.forEach((file) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (e.target?.result != null && typeof e.target.result === 'string') {
|
||||
setFiles(
|
||||
files.concat({
|
||||
name: file.name,
|
||||
data: e.target.result.split('base64,')[1],
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}}>
|
||||
{({getRootProps, getInputProps, isDragActive}) => (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={`form__dropzone dropzone interactive-list ${isDragActive ? 'dropzone--is-dragging' : ''}`}>
|
||||
<input {...getInputProps()} />
|
||||
<div className="dropzone__copy">
|
||||
<div className="dropzone__icon">
|
||||
<FilesIcon />
|
||||
</div>
|
||||
<FormattedMessage id="torrents.add.tab.file.drop" />{' '}
|
||||
<span className="dropzone__browse-button">
|
||||
<FormattedMessage id="torrents.add.tab.file.browse" />
|
||||
</span>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
</FormRowItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileDropzone;
|
||||
@@ -1,53 +1,53 @@
|
||||
import {injectIntl, WrappedComponentProps} from 'react-intl';
|
||||
import * as React from 'react';
|
||||
import {FC} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import ModalActions from '../ModalActions';
|
||||
import SettingStore from '../../../stores/SettingStore';
|
||||
|
||||
import type {ModalAction} from '../../../stores/UIStore';
|
||||
|
||||
interface AddTorrentsActionsProps extends WrappedComponentProps {
|
||||
interface AddTorrentsActionsProps {
|
||||
isAddingTorrents: boolean;
|
||||
onAddTorrentsClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
class AddTorrentsActions extends React.PureComponent<AddTorrentsActionsProps> {
|
||||
getActions(): Array<ModalAction> {
|
||||
return [
|
||||
{
|
||||
checked: Boolean(SettingStore.floodSettings.startTorrentsOnLoad),
|
||||
clickHandler: null,
|
||||
content: this.props.intl.formatMessage({
|
||||
id: 'torrents.add.start.label',
|
||||
}),
|
||||
id: 'start',
|
||||
triggerDismiss: false,
|
||||
type: 'checkbox',
|
||||
},
|
||||
{
|
||||
clickHandler: null,
|
||||
content: this.props.intl.formatMessage({
|
||||
id: 'button.cancel',
|
||||
}),
|
||||
triggerDismiss: true,
|
||||
type: 'tertiary',
|
||||
},
|
||||
{
|
||||
clickHandler: this.props.onAddTorrentsClick,
|
||||
content: this.props.intl.formatMessage({
|
||||
id: 'torrents.add.button.add',
|
||||
}),
|
||||
isLoading: this.props.isAddingTorrents,
|
||||
submit: true,
|
||||
triggerDismiss: false,
|
||||
type: 'primary',
|
||||
},
|
||||
];
|
||||
}
|
||||
const AddTorrentsActions: FC<AddTorrentsActionsProps> = ({
|
||||
isAddingTorrents,
|
||||
onAddTorrentsClick,
|
||||
}: AddTorrentsActionsProps) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<ModalActions
|
||||
actions={[
|
||||
{
|
||||
checked: Boolean(SettingStore.floodSettings.startTorrentsOnLoad),
|
||||
clickHandler: null,
|
||||
content: intl.formatMessage({
|
||||
id: 'torrents.add.start.label',
|
||||
}),
|
||||
id: 'start',
|
||||
triggerDismiss: false,
|
||||
type: 'checkbox',
|
||||
},
|
||||
{
|
||||
clickHandler: null,
|
||||
content: intl.formatMessage({
|
||||
id: 'button.cancel',
|
||||
}),
|
||||
triggerDismiss: true,
|
||||
type: 'tertiary',
|
||||
},
|
||||
{
|
||||
clickHandler: onAddTorrentsClick,
|
||||
content: intl.formatMessage({
|
||||
id: 'torrents.add.button.add',
|
||||
}),
|
||||
isLoading: isAddingTorrents,
|
||||
submit: true,
|
||||
triggerDismiss: false,
|
||||
type: 'primary',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ModalActions actions={this.getActions()} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(AddTorrentsActions);
|
||||
export default AddTorrentsActions;
|
||||
|
||||
+102
-122
@@ -1,5 +1,5 @@
|
||||
import {Component} from 'react';
|
||||
import {injectIntl, WrappedComponentProps} from 'react-intl';
|
||||
import {FC, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import AddTorrentsActions from './AddTorrentsActions';
|
||||
import {Checkbox, Form, FormRow, Textbox} from '../../../ui';
|
||||
@@ -22,130 +22,110 @@ type AddTorrentsByCreationFormData = {
|
||||
tags: string;
|
||||
};
|
||||
|
||||
interface AddTorrentsByCreationStates {
|
||||
isCreatingTorrents: boolean;
|
||||
trackerTextboxes: Array<{id: number; value: string}>;
|
||||
}
|
||||
const AddTorrentsByCreation: FC = () => {
|
||||
const formRef = useRef<Form>(null);
|
||||
const intl = useIntl();
|
||||
const [isCreatingTorrents, setIsCreatingTorrents] = useState<boolean>(false);
|
||||
|
||||
class AddTorrentsByCreation extends Component<WrappedComponentProps, AddTorrentsByCreationStates> {
|
||||
formRef: Form | null = null;
|
||||
|
||||
constructor(props: WrappedComponentProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isCreatingTorrents: false,
|
||||
trackerTextboxes: [{id: 0, value: ''}],
|
||||
};
|
||||
}
|
||||
|
||||
handleAddTorrents = () => {
|
||||
if (this.formRef == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = this.formRef.getFormData() as Partial<AddTorrentsByCreationFormData>;
|
||||
this.setState({isCreatingTorrents: true});
|
||||
|
||||
if (formData.sourcePath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TorrentActions.createTorrent({
|
||||
name: formData.name,
|
||||
sourcePath: formData.sourcePath,
|
||||
trackers: getTextArray(formData, 'trackers'),
|
||||
comment: formData.comment,
|
||||
infoSource: formData.infoSource,
|
||||
isPrivate: formData.isPrivate || false,
|
||||
start: formData.start || false,
|
||||
tags: formData.tags != null ? formData.tags.split(',') : undefined,
|
||||
}).then(() => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start: formData.start, destination: formData.sourcePath});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {intl} = this.props;
|
||||
const {isCreatingTorrents, trackerTextboxes} = this.state;
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="inverse"
|
||||
ref={(ref) => {
|
||||
this.formRef = ref;
|
||||
}}>
|
||||
<FilesystemBrowserTextbox
|
||||
id="sourcePath"
|
||||
return (
|
||||
<Form className="inverse" ref={formRef}>
|
||||
<FilesystemBrowserTextbox
|
||||
id="sourcePath"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.source.path.label',
|
||||
})}
|
||||
/>
|
||||
<TextboxRepeater
|
||||
id="trackers"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.trackers.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.tracker.input.placeholder',
|
||||
})}
|
||||
defaultValues={[{id: 0, value: ''}]}
|
||||
/>
|
||||
<FormRow>
|
||||
<Textbox
|
||||
id="name"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.source.path.label',
|
||||
})}
|
||||
/>
|
||||
<TextboxRepeater
|
||||
id="trackers"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.trackers.label',
|
||||
id: 'torrents.create.base.name.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.tracker.input.placeholder',
|
||||
id: 'torrents.create.base.name.input.placeholder',
|
||||
})}
|
||||
defaultValues={trackerTextboxes}
|
||||
/>
|
||||
<FormRow>
|
||||
<Textbox
|
||||
id="name"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.base.name.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.base.name.input.placeholder',
|
||||
})}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<Textbox
|
||||
id="comment"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.comment.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.comment.input.placeholder',
|
||||
})}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<Textbox
|
||||
id="infoSource"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.info.source.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.info.source.input.placeholder',
|
||||
})}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<Checkbox grow={false} id="isPrivate">
|
||||
{intl.formatMessage({id: 'torrents.create.is.private.label'})}
|
||||
</Checkbox>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<TagSelect
|
||||
id="tags"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.tags',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.tags.input.placeholder',
|
||||
})}
|
||||
/>
|
||||
</FormRow>
|
||||
<AddTorrentsActions onAddTorrentsClick={this.handleAddTorrents} isAddingTorrents={isCreatingTorrents} />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<Textbox
|
||||
id="comment"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.comment.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.comment.input.placeholder',
|
||||
})}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<Textbox
|
||||
id="infoSource"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.create.info.source.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.info.source.input.placeholder',
|
||||
})}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<Checkbox grow={false} id="isPrivate">
|
||||
{intl.formatMessage({id: 'torrents.create.is.private.label'})}
|
||||
</Checkbox>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<TagSelect
|
||||
id="tags"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.tags',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.create.tags.input.placeholder',
|
||||
})}
|
||||
/>
|
||||
</FormRow>
|
||||
<AddTorrentsActions
|
||||
onAddTorrentsClick={() => {
|
||||
if (formRef.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
export default injectIntl(AddTorrentsByCreation, {forwardRef: true});
|
||||
const formData = formRef.current.getFormData() as Partial<AddTorrentsByCreationFormData>;
|
||||
setIsCreatingTorrents(true);
|
||||
|
||||
if (formData.sourcePath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TorrentActions.createTorrent({
|
||||
name: formData.name,
|
||||
sourcePath: formData.sourcePath,
|
||||
trackers: getTextArray(formData, 'trackers'),
|
||||
comment: formData.comment,
|
||||
infoSource: formData.infoSource,
|
||||
isPrivate: formData.isPrivate || false,
|
||||
start: formData.start || false,
|
||||
tags: formData.tags != null ? formData.tags.split(',') : undefined,
|
||||
}).then(() => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start: formData.start, destination: formData.sourcePath});
|
||||
}}
|
||||
isAddingTorrents={isCreatingTorrents}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTorrentsByCreation;
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import {Component} from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl';
|
||||
import {FC, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import AddTorrentsActions from './AddTorrentsActions';
|
||||
import CloseIcon from '../../icons/Close';
|
||||
import FileIcon from '../../icons/File';
|
||||
import FilesIcon from '../../icons/Files';
|
||||
import FileDropzone from '../../general/form-elements/FileDropzone';
|
||||
import FilesystemBrowserTextbox from '../../general/filesystem/FilesystemBrowserTextbox';
|
||||
import {Form, FormRow, FormRowItem} from '../../../ui';
|
||||
import {Form, FormRow} from '../../../ui';
|
||||
import {saveAddTorrentsUserPreferences} from '../../../util/userPreferences';
|
||||
import TagSelect from '../../general/form-elements/TagSelect';
|
||||
import TorrentActions from '../../../actions/TorrentActions';
|
||||
import UIStore from '../../../stores/UIStore';
|
||||
|
||||
import type {ProcessedFiles} from '../../general/form-elements/FileDropzone';
|
||||
|
||||
interface AddTorrentsByFileFormData {
|
||||
destination: string;
|
||||
start: boolean;
|
||||
@@ -21,181 +20,76 @@ interface AddTorrentsByFileFormData {
|
||||
isCompleted: boolean;
|
||||
}
|
||||
|
||||
interface AddTorrentsByFileStates {
|
||||
errors: Record<string, unknown>;
|
||||
files: Array<{
|
||||
name: string;
|
||||
data: string;
|
||||
}>;
|
||||
isAddingTorrents: boolean;
|
||||
}
|
||||
const AddTorrentsByFile: FC = () => {
|
||||
const filesRef = useRef<ProcessedFiles>([]);
|
||||
const formRef = useRef<Form>(null);
|
||||
const intl = useIntl();
|
||||
const [isAddingTorrents, setIsAddingTorrents] = useState<boolean>(false);
|
||||
|
||||
class AddTorrentsByFile extends Component<WrappedComponentProps, AddTorrentsByFileStates> {
|
||||
formRef: Form | null = null;
|
||||
|
||||
constructor(props: WrappedComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
errors: {},
|
||||
files: [],
|
||||
isAddingTorrents: false,
|
||||
};
|
||||
}
|
||||
|
||||
getFileDropzone() {
|
||||
const {files} = this.state;
|
||||
|
||||
const fileContent =
|
||||
files.length > 0 ? (
|
||||
<ul
|
||||
className="dropzone__selected-files interactive-list"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
}}>
|
||||
{files.map((file, index) => (
|
||||
<li className="dropzone__selected-files__file interactive-list__item" key={file.name} title={file.name}>
|
||||
<span className="interactive-list__icon">
|
||||
<FileIcon />
|
||||
</span>
|
||||
<span className="interactive-list__label">{file.name}</span>
|
||||
<span
|
||||
className="interactive-list__icon interactive-list__icon--action interactive-list__icon--action--warning"
|
||||
onClick={() => this.handleFileRemove(index)}>
|
||||
<CloseIcon />
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<FormRowItem>
|
||||
<label className="form__element__label">
|
||||
<FormattedMessage id="torrents.add.torrents.label" />
|
||||
</label>
|
||||
{fileContent}
|
||||
<Dropzone onDrop={this.handleFileDrop}>
|
||||
{({getRootProps, getInputProps, isDragActive}) => (
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={`form__dropzone dropzone interactive-list ${isDragActive ? 'dropzone--is-dragging' : ''}`}>
|
||||
<input {...getInputProps()} />
|
||||
<div className="dropzone__copy">
|
||||
<div className="dropzone__icon">
|
||||
<FilesIcon />
|
||||
</div>
|
||||
<FormattedMessage id="torrents.add.tab.file.drop" />{' '}
|
||||
<span className="dropzone__browse-button">
|
||||
<FormattedMessage id="torrents.add.tab.file.browse" />
|
||||
</span>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
</FormRowItem>
|
||||
);
|
||||
}
|
||||
|
||||
handleFileDrop = (files: Array<File>) => {
|
||||
const nextErrorsState = this.state.errors;
|
||||
|
||||
if (nextErrorsState.files != null) {
|
||||
delete nextErrorsState.files;
|
||||
}
|
||||
|
||||
files.forEach((file) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.setState((state) => {
|
||||
if (e.target?.result != null && typeof e.target.result === 'string') {
|
||||
return {
|
||||
errors: nextErrorsState,
|
||||
files: state.files.concat({
|
||||
name: file.name,
|
||||
data: e.target.result.split('base64,')[1],
|
||||
}),
|
||||
};
|
||||
}
|
||||
return {errors: nextErrorsState, files: state.files};
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
|
||||
handleFileRemove = (fileIndex: number) => {
|
||||
const {files} = this.state;
|
||||
files.splice(fileIndex, 1);
|
||||
this.setState({files});
|
||||
};
|
||||
|
||||
handleAddTorrents = () => {
|
||||
if (this.formRef == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = this.formRef.getFormData();
|
||||
this.setState({isAddingTorrents: true});
|
||||
|
||||
const {destination, start, tags, isBasePath, isCompleted} = formData as Partial<AddTorrentsByFileFormData>;
|
||||
|
||||
const filesData: Array<string> = [];
|
||||
this.state.files.forEach((file) => {
|
||||
filesData.push(file.data);
|
||||
});
|
||||
|
||||
if (filesData[0] == null || destination == null) {
|
||||
this.setState({isAddingTorrents: false});
|
||||
return;
|
||||
}
|
||||
|
||||
TorrentActions.addTorrentsByFiles({
|
||||
files: filesData as [string, ...string[]],
|
||||
destination,
|
||||
tags: tags != null ? tags.split(',') : undefined,
|
||||
isBasePath,
|
||||
isCompleted,
|
||||
start,
|
||||
}).then(() => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start, destination});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {intl} = this.props;
|
||||
const {isAddingTorrents} = this.state;
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="inverse"
|
||||
ref={(ref) => {
|
||||
this.formRef = ref;
|
||||
}}>
|
||||
<FormRow>{this.getFileDropzone()}</FormRow>
|
||||
<FilesystemBrowserTextbox
|
||||
id="destination"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.destination.label',
|
||||
})}
|
||||
selectable="directories"
|
||||
showBasePathToggle
|
||||
showCompletedToggle
|
||||
return (
|
||||
<Form className="inverse" ref={formRef}>
|
||||
<FormRow>
|
||||
<FileDropzone
|
||||
onFilesChanged={(files) => {
|
||||
filesRef.current = files;
|
||||
}}
|
||||
/>
|
||||
<FormRow>
|
||||
<TagSelect
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.tags',
|
||||
})}
|
||||
id="tags"
|
||||
/>
|
||||
</FormRow>
|
||||
<AddTorrentsActions onAddTorrentsClick={this.handleAddTorrents} isAddingTorrents={isAddingTorrents} />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
</FormRow>
|
||||
<FilesystemBrowserTextbox
|
||||
id="destination"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.destination.label',
|
||||
})}
|
||||
selectable="directories"
|
||||
showBasePathToggle
|
||||
showCompletedToggle
|
||||
/>
|
||||
<FormRow>
|
||||
<TagSelect
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.tags',
|
||||
})}
|
||||
id="tags"
|
||||
/>
|
||||
</FormRow>
|
||||
<AddTorrentsActions
|
||||
onAddTorrentsClick={() => {
|
||||
if (formRef.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
export default injectIntl(AddTorrentsByFile, {forwardRef: true});
|
||||
const formData = formRef.current?.getFormData();
|
||||
setIsAddingTorrents(true);
|
||||
|
||||
const {destination, start, tags, isBasePath, isCompleted} = formData as Partial<AddTorrentsByFileFormData>;
|
||||
|
||||
const filesData: Array<string> = [];
|
||||
filesRef.current.forEach((file) => {
|
||||
filesData.push(file.data);
|
||||
});
|
||||
|
||||
if (filesData.length === 0 || destination == null) {
|
||||
setIsAddingTorrents(false);
|
||||
return;
|
||||
}
|
||||
|
||||
TorrentActions.addTorrentsByFiles({
|
||||
files: filesData as [string, ...string[]],
|
||||
destination,
|
||||
tags: tags != null ? tags.split(',') : undefined,
|
||||
isBasePath,
|
||||
isCompleted,
|
||||
start,
|
||||
}).then(() => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start, destination});
|
||||
}}
|
||||
isAddingTorrents={isAddingTorrents}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTorrentsByFile;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Component} from 'react';
|
||||
import {injectIntl, WrappedComponentProps} from 'react-intl';
|
||||
import {FC, useRef, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import AddTorrentsActions from './AddTorrentsActions';
|
||||
import FilesystemBrowserTextbox from '../../general/filesystem/FilesystemBrowserTextbox';
|
||||
@@ -22,115 +22,95 @@ type AddTorrentsByURLFormData = {
|
||||
tags: string;
|
||||
};
|
||||
|
||||
interface AddTorrentsByURLStates {
|
||||
isAddingTorrents: boolean;
|
||||
urlTextboxes: Array<{id: number; value: string}>;
|
||||
}
|
||||
const AddTorrentsByURL: FC = () => {
|
||||
const formRef = useRef<Form>(null);
|
||||
const intl = useIntl();
|
||||
const [isAddingTorrents, setIsAddingTorrents] = useState<boolean>(false);
|
||||
|
||||
class AddTorrentsByURL extends Component<WrappedComponentProps, AddTorrentsByURLStates> {
|
||||
formRef: Form | null = null;
|
||||
|
||||
constructor(props: WrappedComponentProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isAddingTorrents: false,
|
||||
urlTextboxes: (UIStore.activeModal?.id === 'add-torrents' && UIStore.activeModal?.initialURLs) || [
|
||||
{id: 0, value: ''},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
handleAddTorrents = () => {
|
||||
if (this.formRef == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = this.formRef.getFormData() as Partial<AddTorrentsByURLFormData>;
|
||||
this.setState({isAddingTorrents: true});
|
||||
|
||||
const urls = getTextArray(formData, 'urls').filter((url) => url !== '');
|
||||
|
||||
if (urls[0] == null || formData.destination == null) {
|
||||
this.setState({isAddingTorrents: false});
|
||||
return;
|
||||
}
|
||||
|
||||
const cookies = getTextArray(formData, 'cookies');
|
||||
|
||||
// TODO: handle multiple domain names
|
||||
const firstDomain = urls[0].startsWith('http') && urls[0].split('/')[2];
|
||||
const processedCookies = firstDomain
|
||||
? {
|
||||
[firstDomain]: cookies,
|
||||
return (
|
||||
<Form className="inverse" ref={formRef}>
|
||||
<TextboxRepeater
|
||||
id="urls"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.torrents.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.add.tab.url.input.placeholder',
|
||||
})}
|
||||
defaultValues={
|
||||
(UIStore.activeModal?.id === 'add-torrents' && UIStore.activeModal?.initialURLs) || [{id: 0, value: ''}]
|
||||
}
|
||||
: undefined;
|
||||
|
||||
TorrentActions.addTorrentsByUrls({
|
||||
urls: urls as [string, ...string[]],
|
||||
cookies: processedCookies,
|
||||
destination: formData.destination,
|
||||
isBasePath: formData.isBasePath,
|
||||
isCompleted: formData.isCompleted,
|
||||
start: formData.start,
|
||||
tags: formData.tags != null ? formData.tags.split(',') : undefined,
|
||||
}).then(() => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start: formData.start, destination: formData.destination});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Form
|
||||
className="inverse"
|
||||
ref={(ref) => {
|
||||
this.formRef = ref;
|
||||
}}>
|
||||
<TextboxRepeater
|
||||
id="urls"
|
||||
label={this.props.intl.formatMessage({
|
||||
id: 'torrents.add.torrents.label',
|
||||
})}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'torrents.add.tab.url.input.placeholder',
|
||||
})}
|
||||
defaultValues={this.state.urlTextboxes}
|
||||
/>
|
||||
<TextboxRepeater
|
||||
id="cookies"
|
||||
label={this.props.intl.formatMessage({
|
||||
id: 'torrents.add.cookies.label',
|
||||
})}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'torrents.add.cookies.input.placeholder',
|
||||
/>
|
||||
<TextboxRepeater
|
||||
id="cookies"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.cookies.label',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'torrents.add.cookies.input.placeholder',
|
||||
})}
|
||||
/>
|
||||
<FilesystemBrowserTextbox
|
||||
id="destination"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.destination.label',
|
||||
})}
|
||||
selectable="directories"
|
||||
showBasePathToggle
|
||||
showCompletedToggle
|
||||
/>
|
||||
<FormRow>
|
||||
<TagSelect
|
||||
id="tags"
|
||||
label={intl.formatMessage({
|
||||
id: 'torrents.add.tags',
|
||||
})}
|
||||
/>
|
||||
<FilesystemBrowserTextbox
|
||||
id="destination"
|
||||
label={this.props.intl.formatMessage({
|
||||
id: 'torrents.add.destination.label',
|
||||
})}
|
||||
selectable="directories"
|
||||
showBasePathToggle
|
||||
showCompletedToggle
|
||||
/>
|
||||
<FormRow>
|
||||
<TagSelect
|
||||
id="tags"
|
||||
label={this.props.intl.formatMessage({
|
||||
id: 'torrents.add.tags',
|
||||
})}
|
||||
/>
|
||||
</FormRow>
|
||||
<AddTorrentsActions
|
||||
onAddTorrentsClick={this.handleAddTorrents}
|
||||
isAddingTorrents={this.state.isAddingTorrents}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
</FormRow>
|
||||
<AddTorrentsActions
|
||||
onAddTorrentsClick={() => {
|
||||
if (formRef.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
export default injectIntl(AddTorrentsByURL, {forwardRef: true});
|
||||
const formData = formRef.current.getFormData() as Partial<AddTorrentsByURLFormData>;
|
||||
setIsAddingTorrents(true);
|
||||
|
||||
const urls = getTextArray(formData, 'urls').filter((url) => url !== '');
|
||||
|
||||
if (urls.length === 0 || formData.destination == null) {
|
||||
setIsAddingTorrents(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const cookies = getTextArray(formData, 'cookies');
|
||||
|
||||
// TODO: handle multiple domain names
|
||||
const firstDomain = urls[0].startsWith('http') && urls[0].split('/')[2];
|
||||
const processedCookies = firstDomain
|
||||
? {
|
||||
[firstDomain]: cookies,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
TorrentActions.addTorrentsByUrls({
|
||||
urls: urls as [string, ...string[]],
|
||||
cookies: processedCookies,
|
||||
destination: formData.destination,
|
||||
isBasePath: formData.isBasePath,
|
||||
isCompleted: formData.isCompleted,
|
||||
start: formData.start,
|
||||
tags: formData.tags != null ? formData.tags.split(',') : undefined,
|
||||
}).then(() => {
|
||||
UIStore.dismissModal();
|
||||
});
|
||||
|
||||
saveAddTorrentsUserPreferences({start: formData.start, destination: formData.destination});
|
||||
}}
|
||||
isAddingTorrents={isAddingTorrents}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddTorrentsByURL;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {FC} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import * as React from 'react';
|
||||
|
||||
import AddTorrentsByFile from './AddTorrentsByFile';
|
||||
import AddTorrentsByURL from './AddTorrentsByURL';
|
||||
import Modal from '../Modal';
|
||||
import AddTorrentsByCreation from './AddTorrentsByCreation';
|
||||
|
||||
const AddTorrentsModal: React.FC = () => {
|
||||
const AddTorrentsModal: FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const tabs = {
|
||||
|
||||
Reference in New Issue
Block a user