Add move torrents option to context menu

This commit is contained in:
John Furrow
2016-02-28 19:24:59 -08:00
parent 7e3403238e
commit 635b30afeb
27 changed files with 574 additions and 74 deletions

View File

@@ -0,0 +1,54 @@
import classnames from 'classnames';
import React from 'react';
import Checkmark from '../icons/Checkmark';
const METHODS_TO_BIND = ['handleCheckboxChange'];
export default class SearchBox extends React.Component {
constructor() {
super();
this.state = {checked: false};
METHODS_TO_BIND.forEach((method) => {
this[method] = this[method].bind(this);
});
}
componentDidMount() {
if (this.state.checked !== this.props.checked) {
this.setState({checked: this.props.checked});
}
}
handleCheckboxChange() {
let currentCheckedState = this.state.checked;
let newCheckedState = !currentCheckedState;
this.setState({checked: newCheckedState})
if (this.props.onChange) {
this.props.onChange(newCheckedState);
}
}
render() {
let classes = classnames('checkbox', {
'is-checked': this.state.checked
});
return (
<label className={classes}>
<input type="checkbox" checked={this.state.checked}
onChange={this.handleCheckboxChange} />
<span className="checkbox__decoy">
<Checkmark />
</span>
<span className="checkbox__label">
{this.props.children}
</span>
</label>
);
}
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import BaseIcon from './BaseIcon';
export default class Checkmark extends BaseIcon {
render() {
return (
<svg className={`icon icon--checkmark ${this.props.className}`}
xmlns={this.getXmlns()} viewBox={this.getViewBox()}>
<polygon points="55.5,18.6 46.1,8.7 24.4,31.5 13.9,20.4 4.5,30.3 24.4,51.3 24.4,51.3 24.4,51.3"/>
</svg>
);
}
}

View File

@@ -23,7 +23,11 @@ export default class AddTorrents extends React.Component {
}
componentWillMount() {
this.setState({destination: UIStore.getLatestTorrentLocation()});
let destination = UIStore.getLatestTorrentLocation();
if (this.props.suggested) {
destination = this.props.suggested;
}
this.setState({destination});
}
componentDidMount() {
@@ -46,6 +50,10 @@ export default class AddTorrents extends React.Component {
}
onLatestTorrentLocationChange() {
if (this.props.suggested) {
return;
}
let destination = UIStore.getLatestTorrentLocation();
if (this.props.onChange) {

View File

@@ -3,6 +3,7 @@ import CSSTransitionGroup from 'react-addons-css-transition-group';
import React from 'react';
import AddTorrents from './AddTorrents';
import MoveTorrents from './MoveTorrents';
import EventTypes from '../../constants/EventTypes';
import Modal from './Modal';
import UIActions from '../../actions/UIActions';
@@ -81,6 +82,11 @@ export default class Modals extends React.Component {
heading={modalOptions.heading} />
);
break;
case 'move-torrents':
modal = (
<MoveTorrents dismiss={this.dismissModal} />
);
break;
case 'add-torrents':
modal = (
<AddTorrents dismiss={this.dismissModal} />

View File

@@ -0,0 +1,152 @@
import _ from 'lodash';
import classnames from 'classnames';
import React from 'react';
import AddTorrentsDestination from './AddTorrentsDestination';
import AppDispatcher from '../../dispatcher/AppDispatcher';
import Checkbox from '../forms/Checkbox';
import EventTypes from '../../constants/EventTypes';
import LoadingIndicatorDots from '../icons/LoadingIndicatorDots';
import Modal from './Modal';
import ModalActions from './ModalActions';
import TorrentActions from '../../actions/TorrentActions';
import TorrentStore from '../../stores/TorrentStore';
const METHODS_TO_BIND = [
'confirmMoveTorrents',
'handleCheckboxChange',
'handleDestinationChange',
'handleTextboxChange',
'onMoveError'
];
export default class AddTorrents extends React.Component {
constructor() {
super();
this.state = {
moveTorrentsError: null,
destination: null,
isExpanded: false,
isSettingDownloadPath: false,
moveTorrents: false,
originalSource: null
};
METHODS_TO_BIND.forEach((method) => {
this[method] = this[method].bind(this);
});
}
componentWillMount() {
let filenames = TorrentStore.getSelectedTorrentsFilename();
let sources = TorrentStore.getSelectedTorrentsDownloadLocations();
if (sources.length === 1) {
let originalSource = this.removeTrailingFilename(sources[0], filenames[0]);
this.setState({originalSource, destination: originalSource});
}
}
componentDidMount() {
TorrentStore.listen(EventTypes.CLIENT_MOVE_TORRENTS_REQUEST_ERROR, this.onMoveError);
}
componentWillUnmount() {
TorrentStore.unlisten(EventTypes.CLIENT_MOVE_TORRENTS_REQUEST_ERROR, this.onMoveError);
}
onMoveError() {
this.setState({isSettingDownloadPath: false});
}
confirmMoveTorrents() {
let filenames = TorrentStore.getSelectedTorrentsFilename();
let sources = TorrentStore.getSelectedTorrentsDownloadLocations();
if (sources.length) {
this.setState({isSettingDownloadPath: true});
TorrentActions.moveTorrents(TorrentStore.getSelectedTorrents(), {
destination: this.state.destination,
filenames,
moveFiles: this.state.moveTorrents,
sources
});
}
}
getActions() {
let icon = null;
let primaryButtonText = 'Set Location';
if (this.state.isSettingDownloadPath) {
icon = <LoadingIndicatorDots viewBox="0 0 32 32" />;
primaryButtonText = 'Setting...';
}
return [
{
clickHandler: null,
content: 'Cancel',
triggerDismiss: true,
type: 'secondary'
},
{
clickHandler: this.confirmMoveTorrents,
content: (
<span>
{icon}
{primaryButtonText}
</span>
),
supplementalClassName: icon != null ? 'has-icon' : '',
triggerDismiss: false,
type: 'primary'
}
];
}
handleCheckboxChange(checkboxState) {
this.setState({moveTorrents: checkboxState});
}
handleDestinationChange(destination) {
this.setState({destination});
}
handleTextboxChange(event) {
let destination = event.target.value;
this.setState({destination});
}
getContent() {
return (
<div className="form">
<AddTorrentsDestination onChange={this.handleDestinationChange}
suggested={this.state.originalSource} />
<div className="form__row">
<Checkbox onChange={this.handleCheckboxChange}>Move data</Checkbox>
</div>
<ModalActions actions={this.getActions()} />
</div>
);
}
removeTrailingFilename(path, filename) {
let directoryPath = path.substring(0, path.length - filename.length);
if (directoryPath.charAt(directoryPath.length - 1) === '/' || directoryPath.charAt(directoryPath.length - 1) === '\\') {
directoryPath = directoryPath.substring(0, directoryPath.length - 1);
}
return directoryPath;
}
render() {
return (
<Modal heading="Set Download Location"
dismiss={this.props.dismiss}
content={this.getContent()} />
);
}
}

View File

@@ -18,6 +18,7 @@ import UIStore from '../../stores/UIStore';
const METHODS_TO_BIND = [
'onReceiveTorrentsError',
'onReceiveTorrentsSuccess',
'handleContextMenuItemClick',
'handleDetailsClick',
'handleRightClick',
'handleTorrentClick',
@@ -101,6 +102,10 @@ export default class TorrentListContainer extends React.Component {
action: 'remove',
clickHandler,
label: 'Remove'
}, {
action: 'move',
clickHandler,
label: 'Set Download Location'
}];
}
@@ -119,9 +124,16 @@ export default class TorrentListContainer extends React.Component {
case 'remove':
TorrentActions.deleteTorrents(selectedTorrents);
break;
case 'move':
this.handleContextMenuMoveClick(selectedTorrents);
break;
}
}
handleContextMenuMoveClick(hashes) {
UIActions.displayModal('move-torrents');
}
handleDetailsClick(torrent, event) {
UIActions.handleDetailsClick({
hash: torrent.hash,

View File

@@ -130,7 +130,7 @@ export default class ContextMenu extends React.Component {
}
ContextMenu.defaultProps = {
width: 150
width: 200
};
ContextMenu.propTypes = {