From dda0ce0e88122d5a098fbd8ec34d3a97231fbf9e Mon Sep 17 00:00:00 2001 From: John Furrow Date: Sat, 23 Apr 2016 14:33:40 -0700 Subject: [PATCH] Add option to add torrent without starting --- client/source/sass/base/_form-elements.scss | 18 +++-- client/source/sass/components/_modals.scss | 14 ++-- client/source/sass/components/_torrents.scss | 6 +- .../source/scripts/actions/ClientActions.js | 2 +- .../source/scripts/actions/TorrentActions.js | 9 +-- client/source/scripts/actions/UIActions.js | 15 +++- .../scripts/components/forms/Checkbox.js | 2 +- .../scripts/components/forms/Dropdown.js | 2 +- .../scripts/components/modals/AddTorrents.js | 55 +------------- .../components/modals/AddTorrentsActions.js | 50 +++++++++++++ .../components/modals/AddTorrentsByFile.js | 38 ++-------- .../components/modals/AddTorrentsByURL.js | 75 ++++++------------- .../scripts/components/modals/ModalActions.js | 16 +++- .../scripts/components/modals/Modals.js | 3 +- .../components/modals/SettingsSpeedLimit.js | 2 +- .../components/panels/TorrentDetailsView.js | 2 +- .../components/sidebar/TransferData.js | 2 +- .../torrent-details/NavigationList.js | 2 +- .../components/torrent-list/SortDropdown.js | 2 +- .../source/scripts/components/ui/LineChart.js | 3 +- .../scripts/stores/TorrentFilterStore.js | 2 +- client/source/scripts/stores/UIStore.js | 1 + server/models/ClientRequest.js | 9 ++- server/models/client.js | 3 +- server/models/uiSettings.js | 4 +- 25 files changed, 155 insertions(+), 182 deletions(-) create mode 100644 client/source/scripts/components/modals/AddTorrentsActions.js diff --git a/client/source/sass/base/_form-elements.scss b/client/source/sass/base/_form-elements.scss index 6c82e5f2..a19355e6 100644 --- a/client/source/sass/base/_form-elements.scss +++ b/client/source/sass/base/_form-elements.scss @@ -32,6 +32,12 @@ $button--deemphasized--border--hover: darken($button--deemphasized--border, 5%); $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%); +.textbox, +.button, +.checkbox { + font-size: 0.9em; +} + .textbox, .button { appearance: none; @@ -46,7 +52,6 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%); border: 1px solid $textbox--border; color: $textbox--foreground; display: block; - font-size: 0.9em; padding: 10px 15px; transition: background 0.25s, border 0.25s, color 0.25s; width: 100%; @@ -77,7 +82,6 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%); border: none; border-radius: 4px; cursor: pointer; - font-size: 0.9em; font-weight: 500; padding: 8px 22px; transition: background 0.25s; @@ -154,6 +158,7 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%); &__decoy { .icon { + display: block; fill: $blue; } } @@ -164,9 +169,10 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%); &__decoy { @extend .textbox; background: $white; + border-radius: 3px; display: inline-block; height: $spacing-unit * 3/5; - margin-right: $spacing-unit * 1.5/5; + margin-right: $spacing-unit * 3/10; margin-top: -2px; padding: 0; position: relative; @@ -175,13 +181,13 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%); .icon { fill: transparent; - height: 10px; + height: $spacing-unit * 2/5; left: 50%; position: absolute; top: 50%; transition: fill 0.25s; - transform: translate(-43%, -43%); - width: 10px; + transform: translate(-50%, -50%); + width: $spacing-unit * 2/5; } } diff --git a/client/source/sass/components/_modals.scss b/client/source/sass/components/_modals.scss index b2d70c0c..8a09e578 100644 --- a/client/source/sass/components/_modals.scss +++ b/client/source/sass/components/_modals.scss @@ -154,16 +154,20 @@ $modal--tabs--padding--vertical--left: $modal--padding--horizontal; } &__button-group { - text-align: right; + align-items: center; + display: flex; + justify-content: flex-end; .modal--align-center & { - text-align: center; + justify-content: center; } - .button { + .button, + .checkbox { - & + .button { - margin-left: 20px; + & + .button, + & + .checkbox { + margin-left: $spacing-unit * 3/5; } } } diff --git a/client/source/sass/components/_torrents.scss b/client/source/sass/components/_torrents.scss index 2fe9989c..1499903b 100644 --- a/client/source/sass/components/_torrents.scss +++ b/client/source/sass/components/_torrents.scss @@ -293,8 +293,12 @@ $torrent--background--error: #e95779; color: #fff; } + .is-stopped & { + color: $torrent--tertiary--foreground--stopped; + } + .is-selected.is-stopped & { - color: $torrent--tertiary--foreground--selected--stopped; + color: $torrent--tertiary--foreground--selected--stopped !important; } } diff --git a/client/source/scripts/actions/ClientActions.js b/client/source/scripts/actions/ClientActions.js index ae7eace6..ad2a28f2 100644 --- a/client/source/scripts/actions/ClientActions.js +++ b/client/source/scripts/actions/ClientActions.js @@ -45,7 +45,7 @@ const ClientActions = { type: ActionTypes.CLIENT_FETCH_TRANSFER_HISTORY_ERROR, error }); - }) + }); }, setThrottle: (direction, throttle) => { diff --git a/client/source/scripts/actions/TorrentActions.js b/client/source/scripts/actions/TorrentActions.js index b3662fc6..c0e87868 100644 --- a/client/source/scripts/actions/TorrentActions.js +++ b/client/source/scripts/actions/TorrentActions.js @@ -4,17 +4,14 @@ import AppDispatcher from '../dispatcher/AppDispatcher'; import ActionTypes from '../constants/ActionTypes'; const TorrentActions = { - addTorrentsByUrls: (urls, destination) => { + addTorrentsByUrls: (options) => { axios.post('/ui/torrent-location', { - destination + destination: options.destination }) .catch((error) => { console.log(error); }); - return axios.post('/client/add', { - urls, - destination - }) + return axios.post('/client/add', options) .then((json = {}) => { return json.data; }) diff --git a/client/source/scripts/actions/UIActions.js b/client/source/scripts/actions/UIActions.js index 2d8064bd..6581e8f6 100644 --- a/client/source/scripts/actions/UIActions.js +++ b/client/source/scripts/actions/UIActions.js @@ -27,10 +27,17 @@ const UIActions = { }, dismissModal: () => { - AppDispatcher.dispatchUIAction({ - type: ActionTypes.UI_DISPLAY_MODAL, - data: null - }); + try { + // if (AppDispatcher.isDispatching()) { + // AppDispatcher.waitFor([TorrentStore.dispatcherID]); + // } + AppDispatcher.dispatchUIAction({ + type: ActionTypes.UI_DISPLAY_MODAL, + data: null + }); + } catch (err) { + console.error(err); + } }, fetchSortProps: () => { diff --git a/client/source/scripts/components/forms/Checkbox.js b/client/source/scripts/components/forms/Checkbox.js index c93ba592..632ff055 100644 --- a/client/source/scripts/components/forms/Checkbox.js +++ b/client/source/scripts/components/forms/Checkbox.js @@ -26,7 +26,7 @@ export default class SearchBox extends React.Component { let currentCheckedState = this.state.checked; let newCheckedState = !currentCheckedState; - this.setState({checked: newCheckedState}) + this.setState({checked: newCheckedState}); if (this.props.onChange) { this.props.onChange(newCheckedState); diff --git a/client/source/scripts/components/forms/Dropdown.js b/client/source/scripts/components/forms/Dropdown.js index bc8c4ba1..7a1476ff 100644 --- a/client/source/scripts/components/forms/Dropdown.js +++ b/client/source/scripts/components/forms/Dropdown.js @@ -106,7 +106,7 @@ export default class Dropdown extends React.Component { let classes = classnames('dropdown__item menu__item', property.className, { 'is-selectable': property.selectable !== false, 'is-selected': property.selected - }) + }); let clickHandler = null; if (property.selectable !== false) { diff --git a/client/source/scripts/components/modals/AddTorrents.js b/client/source/scripts/components/modals/AddTorrents.js index 95293f4e..bf299e66 100644 --- a/client/source/scripts/components/modals/AddTorrents.js +++ b/client/source/scripts/components/modals/AddTorrents.js @@ -1,62 +1,13 @@ -import _ from 'lodash'; -import classnames from 'classnames'; import React from 'react'; import AddTorrentsByFile from './AddTorrentsByFile'; import AddTorrentsByURL from './AddTorrentsByURL'; -import EventTypes from '../../constants/EventTypes'; -import LoadingIndicatorDots from '../icons/LoadingIndicatorDots'; import Modal from './Modal'; -import TextboxRepeater from '../forms/TextboxRepeater'; -import TorrentActions from '../../actions/TorrentActions'; -import TorrentStore from '../../stores/TorrentStore'; import UIActions from '../../actions/UIActions'; -import UIStore from '../../stores/UIStore'; - -const METHODS_TO_BIND = ['onAddTorrentSuccess']; export default class AddTorrents extends React.Component { - constructor() { - super(); - - this.state = { - addTorrentsError: null, - destination: null, - isExpanded: false, - isAddingTorrents: false, - urlTextboxes: [{value: null}] - }; - - METHODS_TO_BIND.forEach((method) => { - this[method] = this[method].bind(this); - }); - } - - componentDidMount() { - TorrentStore.listen(EventTypes.CLIENT_ADD_TORRENT_SUCCESS, this.onAddTorrentSuccess); - } - - componentWillUnmount() { - TorrentStore.unlisten(EventTypes.CLIENT_ADD_TORRENT_SUCCESS, this.onAddTorrentSuccess); - } - dismissModal() { - this.props.dismiss(); - } - - onAddTorrentError() { - this.setState({ - addTorrentsError: 'There was an error, but I have no idea what happened!', - isAddingTorrents: false - }); - } - - onAddTorrentSuccess() { - this.dismissModal(); - } - - getAddByFileContent() { - return add by file; + UIActions.dismissModal(); } render() { @@ -72,9 +23,7 @@ export default class AddTorrents extends React.Component { }; return ( - + ); } } diff --git a/client/source/scripts/components/modals/AddTorrentsActions.js b/client/source/scripts/components/modals/AddTorrentsActions.js new file mode 100644 index 00000000..ca2e76c2 --- /dev/null +++ b/client/source/scripts/components/modals/AddTorrentsActions.js @@ -0,0 +1,50 @@ +import React from 'react'; + +import LoadingIndicatorDots from '../icons/LoadingIndicatorDots'; +import ModalActions from './ModalActions'; + +export default class AddTorrents extends React.Component { + getActions() { + let icon = null; + let primaryButtonText = 'Add Torrent'; + + if (this.props.isAddingTorrents) { + icon = ; + primaryButtonText = 'Adding...'; + } + + return [ + { + checked: true, + clickHandler: this.props.onStartTorrentsToggle, + content: 'Start Torrent', + triggerDismiss: false, + type: 'checkbox' + }, + { + clickHandler: null, + content: 'Cancel', + triggerDismiss: true, + type: 'secondary' + }, + { + clickHandler: this.props.onAddTorrentsClick, + content: ( + + {icon} + {primaryButtonText} + + ), + supplementalClassName: icon != null ? 'has-icon' : '', + triggerDismiss: false, + type: 'primary' + } + ]; + } + + render() { + return ( + + ); + } +} diff --git a/client/source/scripts/components/modals/AddTorrentsByFile.js b/client/source/scripts/components/modals/AddTorrentsByFile.js index 1f5e51ab..6d653837 100644 --- a/client/source/scripts/components/modals/AddTorrentsByFile.js +++ b/client/source/scripts/components/modals/AddTorrentsByFile.js @@ -2,6 +2,7 @@ import classnames from 'classnames'; import Dropzone from 'react-dropzone'; import React from 'react'; +import AddTorrentsActions from './AddTorrentsActions'; import AddTorrentsDestination from './AddTorrentsDestination'; import Close from '../icons/Close'; import File from '../icons/File'; @@ -45,37 +46,6 @@ export default class AddTorrents extends React.Component { event.stopPropagation(); } - getActions() { - let icon = null; - let primaryButtonText = 'Add Torrent'; - - if (this.state.isAddingTorrents) { - icon = ; - primaryButtonText = 'Adding...'; - } - - return [ - { - clickHandler: null, - content: 'Cancel', - triggerDismiss: true, - type: 'secondary' - }, - { - clickHandler: this.handleAddTorrents, - content: ( - - {icon} - {primaryButtonText} - - ), - supplementalClassName: icon != null ? 'has-icon' : '', - triggerDismiss: false, - type: 'primary' - } - ]; - } - getModalContent() { let dropzoneClasses = classnames('form__dropzone dropzone', { 'is-fulfilled': this.state.files && this.state.files.length > 0 @@ -134,6 +104,8 @@ export default class AddTorrents extends React.Component { return; } + this.setState({isAddingTorrents: true}); + let fileData = new FormData(); this.state.files.forEach((file) => { @@ -159,7 +131,9 @@ export default class AddTorrents extends React.Component { {this.getModalContent()} - + ); } diff --git a/client/source/scripts/components/modals/AddTorrentsByURL.js b/client/source/scripts/components/modals/AddTorrentsByURL.js index 02ef1dd6..c94ef29a 100644 --- a/client/source/scripts/components/modals/AddTorrentsByURL.js +++ b/client/source/scripts/components/modals/AddTorrentsByURL.js @@ -1,20 +1,17 @@ -import _ from 'lodash'; -import classnames from 'classnames'; import React from 'react'; +import AddTorrentsActions from './AddTorrentsActions'; import AddTorrentsDestination from './AddTorrentsDestination'; -import LoadingIndicatorDots from '../icons/LoadingIndicatorDots'; -import ModalActions from './ModalActions'; import TextboxRepeater from '../forms/TextboxRepeater'; import TorrentActions from '../../actions/TorrentActions'; -import UIActions from '../../actions/UIActions'; const METHODS_TO_BIND = [ + 'handleAddTorrents', + 'handleDestinationChange', + 'handleStartTorrentsToggle', 'handleUrlAdd', 'handleUrlChange', - 'handleUrlRemove', - 'handleDestinationChange', - 'handleAddTorrents' + 'handleUrlRemove' ]; export default class AddTorrents extends React.Component { @@ -25,7 +22,8 @@ export default class AddTorrents extends React.Component { addTorrentsError: null, destination: null, isAddingTorrents: false, - urlTextboxes: [{value: null}] + urlTextboxes: [{value: null}], + startTorrents: true }; METHODS_TO_BIND.forEach((method) => { @@ -33,58 +31,24 @@ export default class AddTorrents extends React.Component { }); } - dismissModal() { - UIActions.dismissModal(); - } - - onAddTorrentError() { - this.setState({ - addTorrentsError: 'There was an error, but I have no idea what happened!', - isAddingTorrents: false - }); - } - - getActions() { - let icon = null; - let primaryButtonText = 'Add Torrent'; - - if (this.state.isAddingTorrents) { - icon = ; - primaryButtonText = 'Adding...'; - } - - return [ - { - clickHandler: null, - content: 'Cancel', - triggerDismiss: true, - type: 'secondary' - }, - { - clickHandler: this.handleAddTorrents, - content: ( - - {icon} - {primaryButtonText} - - ), - supplementalClassName: icon != null ? 'has-icon' : '', - triggerDismiss: true, - type: 'primary' - } - ]; - } - handleAddTorrents() { this.setState({isAddingTorrents: true}); - let torrentUrls = _.map(this.state.urlTextboxes, 'value'); - TorrentActions.addTorrentsByUrls(torrentUrls, this.state.destination); + let torrentURLs = _.map(this.state.urlTextboxes, 'value'); + TorrentActions.addTorrentsByUrls({ + urls: torrentURLs, + destination: this.state.destination, + start: this.state.startTorrents + }); } handleDestinationChange(destination) { this.setState({destination}); } + handleStartTorrentsToggle(value) { + this.setState({startTorrents: value}); + } + handleUrlRemove(index) { let urlTextboxes = Object.assign([], this.state.urlTextboxes); urlTextboxes.splice(index, 1); @@ -128,7 +92,10 @@ export default class AddTorrents extends React.Component { textboxes={this.state.urlTextboxes} /> - + ); } diff --git a/client/source/scripts/components/modals/ModalActions.js b/client/source/scripts/components/modals/ModalActions.js index fcdeb9bf..7fe0a59a 100644 --- a/client/source/scripts/components/modals/ModalActions.js +++ b/client/source/scripts/components/modals/ModalActions.js @@ -1,6 +1,7 @@ import classnames from 'classnames'; import React from 'react'; +import Checkbox from '../forms/Checkbox'; import UIActions from '../../actions/UIActions'; export default class ModalActions extends React.Component { @@ -12,6 +13,15 @@ export default class ModalActions extends React.Component { 'button--primary': action.type === 'primary' }); + if (action.type === 'checkbox') { + return ( + + {action.content} + + ); + } + return (