mirror of
https://github.com/zoriya/flood.git
synced 2025-12-20 06:05:15 +00:00
Add tabs in AddTorrent modal
This commit is contained in:
@@ -33,6 +33,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-fulfilled {
|
||||||
|
background: $textbox--fulfilled--background;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@@ -89,14 +92,14 @@
|
|||||||
&__label {
|
&__label {
|
||||||
color: $form--label--foreground;
|
color: $form--label--foreground;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.1em;
|
font-size: 0.8em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row {
|
&__row {
|
||||||
|
|
||||||
& + .form__row {
|
& + .form__row {
|
||||||
margin-top: 20px;
|
margin-top: $spacing-unit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
outline: none;
|
outline: none;
|
||||||
margin-right: 8px;
|
margin-right: 6px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: background 0.25s, box-shadow 0.25s;
|
transition: background 0.25s, box-shadow 0.25s;
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
$modal--border-radius: 3px;
|
||||||
|
$modal-content--padding--horizontal: $spacing-unit;
|
||||||
|
$modal-content--padding--vertical: $modal-content--padding--horizontal * 1/2;
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
background: $modal--overlay;
|
background: $modal--overlay;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -8,21 +12,78 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
background: $modal--heading--background;
|
||||||
|
border-radius: $modal--border-radius $modal--border-radius 0 0;
|
||||||
|
box-shadow: inset 0 -1px 0 $modal--heading--border;
|
||||||
|
color: $modal--heading--foreground;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: $modal-content--padding--vertical $modal-content--padding--horizontal 0 $modal-content--padding--horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tabs {
|
||||||
|
color: $modal--tab--foreground;
|
||||||
|
font-size: 0.7em;
|
||||||
|
margin: 0 $spacing-unit * -1/5;
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
|
||||||
|
&__tab {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: $spacing-unit * 1/4;
|
||||||
|
padding: $spacing-unit * 1/5;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
height: 1px;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
transition: background 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
color: $modal--tab--foreground--active;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
background: $modal--tab--border--active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
background: $modal--background;
|
flex: 1 1 auto;
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 1px $modal--content--border,
|
|
||||||
0 0 35px $modal--content--shadow;
|
|
||||||
left: 50%;
|
|
||||||
max-height: 80%;
|
|
||||||
max-width: 80%;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 30px;
|
padding: $modal-content--padding--vertical $modal-content--padding--horizontal;
|
||||||
position: absolute;
|
|
||||||
top: 10%;
|
&__wrapper {
|
||||||
transform: translate(-50%, 0);
|
background: $modal--background;
|
||||||
width: 500px;
|
border-radius: $modal--border-radius;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px $modal--content--border,
|
||||||
|
0 0 35px $modal--content--shadow;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
left: 50%;
|
||||||
|
max-height: 80%;
|
||||||
|
max-width: 80%;
|
||||||
|
position: absolute;
|
||||||
|
top: 10%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
&--align-center {
|
&--align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -31,6 +92,7 @@
|
|||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
margin-top: $spacing-unit;
|
margin-top: $spacing-unit;
|
||||||
|
padding: 0 0 $modal-content--padding--vertical 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__button-group {
|
&__button-group {
|
||||||
@@ -44,13 +106,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header {
|
|
||||||
color: $modal--header--foreground;
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__animation-enter {
|
&__animation-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,14 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
|
||||||
|
&__row {
|
||||||
|
|
||||||
|
& + .form__row {
|
||||||
|
margin-top: $spacing-unit * 1/2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,15 @@ $main-content--background: #e9eef2;
|
|||||||
$header--foreground: #313436;
|
$header--foreground: #313436;
|
||||||
|
|
||||||
// form elements
|
// form elements
|
||||||
$form--label--foreground: #53718a;
|
$form--label--foreground: #abbac7;
|
||||||
|
|
||||||
$textbox--background: rgba($main-content--background, 0.3);
|
$textbox--background: #e9eff5;
|
||||||
$textbox--foreground: $foreground;
|
$textbox--foreground: #53718a;
|
||||||
$textbox--placeholder: #abbac7;
|
$textbox--placeholder: #abbac7;
|
||||||
$textbox--border: $main-content--background;
|
$textbox--border: #d6e2ea;
|
||||||
$textbox--active--background: $textbox--background;
|
$textbox--fulfilled--background: #fdfefe;
|
||||||
$textbox--active--border: $main-content--background;
|
$textbox--active--background: #fdfefe;
|
||||||
|
$textbox--active--border: desaturate(darken($textbox--border, 5%), 5%);
|
||||||
$textbox--active--foreground: $blue;
|
$textbox--active--foreground: $blue;
|
||||||
$textbox--active--placeholder: $textbox--placeholder;
|
$textbox--active--placeholder: $textbox--placeholder;
|
||||||
|
|
||||||
@@ -138,7 +139,7 @@ $torrent-details--navigation--border: rgba(#040d13, 0.4);
|
|||||||
$torrent-details--navigation--item--background--active: rgba(#349cf4, 0.07);
|
$torrent-details--navigation--item--background--active: rgba(#349cf4, 0.07);
|
||||||
$torrent-details--navigation--item--foreground--active: #349cf4;
|
$torrent-details--navigation--item--foreground--active: #349cf4;
|
||||||
$torrent-details--navigation--item--border--active: #349cf4;
|
$torrent-details--navigation--item--border--active: #349cf4;
|
||||||
$torrent-details--navigation--background: transparent; // rgba(desaturate(#0c1b26, 15%), 0.1)
|
$torrent-details--navigation--background: transparent;
|
||||||
|
|
||||||
$torrent-details--content--background: rgba(desaturate(#0c1b26, 15%), 0.4);
|
$torrent-details--content--background: rgba(desaturate(#0c1b26, 15%), 0.4);
|
||||||
|
|
||||||
@@ -173,9 +174,14 @@ $dropdown--item--foreground--hover: darken($dropdown--foreground, 10%);
|
|||||||
$dropdown--item--foreground--active: $blue;
|
$dropdown--item--foreground--active: $blue;
|
||||||
|
|
||||||
// modal windows
|
// modal windows
|
||||||
$modal--background: #fff;
|
$modal--background: #f7fafc;
|
||||||
|
$modal--heading--background: #fff;
|
||||||
|
$modal--heading--foreground: $header--foreground;
|
||||||
|
$modal--heading--border: #dde7ed;
|
||||||
|
$modal--tab--foreground: #abbac7;
|
||||||
|
$modal--tab--foreground--active: $blue;
|
||||||
|
$modal--tab--border--active: $blue;
|
||||||
$modal--overlay: rgba($background, 0.5);
|
$modal--overlay: rgba($background, 0.5);
|
||||||
$modal--header--foreground: $header--foreground;
|
|
||||||
$modal--content--border: rgba($background, 0.1);
|
$modal--content--border: rgba($background, 0.1);
|
||||||
$modal--content--shadow: rgba($background, 0.3);
|
$modal--content--shadow: rgba($background, 0.3);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const METHODS_TO_BIND = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default class TextboxRepeater extends React.Component {
|
export default class TextboxRepeater extends React.Component {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -38,9 +37,13 @@ export default class TextboxRepeater extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inputClasses = classnames('textbox', {
|
||||||
|
'is-fulfilled': textbox.value && textbox.value !== ''
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="textbox__wrapper form__row" key={index}>
|
<div className="textbox__wrapper form__row" key={index}>
|
||||||
<input className="textbox"
|
<input className={inputClasses}
|
||||||
onChange={this.handleTextboxChange.bind(textbox, index)}
|
onChange={this.handleTextboxChange.bind(textbox, index)}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
value={textbox.value}
|
value={textbox.value}
|
||||||
@@ -66,5 +69,4 @@ export default class TextboxRepeater extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import _ from 'lodash';
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import AddTorrentsByFile from './AddTorrentsByFile';
|
||||||
|
import AddTorrentsByURL from './AddTorrentsByURL';
|
||||||
import EventTypes from '../../constants/EventTypes';
|
import EventTypes from '../../constants/EventTypes';
|
||||||
import LoadingIndicatorDots from '../icons/LoadingIndicatorDots';
|
import LoadingIndicatorDots from '../icons/LoadingIndicatorDots';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
@@ -11,16 +13,7 @@ import TorrentStore from '../../stores/TorrentStore';
|
|||||||
import UIActions from '../../actions/UIActions';
|
import UIActions from '../../actions/UIActions';
|
||||||
import UIStore from '../../stores/UIStore';
|
import UIStore from '../../stores/UIStore';
|
||||||
|
|
||||||
const METHODS_TO_BIND = [
|
const METHODS_TO_BIND = ['onAddTorrentSuccess'];
|
||||||
'getContent',
|
|
||||||
'handleDestinationChange',
|
|
||||||
'handleUrlAdd',
|
|
||||||
'handleUrlChange',
|
|
||||||
'handleUrlRemove',
|
|
||||||
'handleAddTorrents',
|
|
||||||
'onAddTorrentSuccess',
|
|
||||||
'onLatestTorrentLocationChange'
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class AddTorrents extends React.Component {
|
export default class AddTorrents extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -39,18 +32,12 @@ export default class AddTorrents extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.setState({destination: UIStore.getLatestTorrentLocation()});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
TorrentStore.listen(EventTypes.CLIENT_ADD_TORRENT_SUCCESS, this.onAddTorrentSuccess);
|
TorrentStore.listen(EventTypes.CLIENT_ADD_TORRENT_SUCCESS, this.onAddTorrentSuccess);
|
||||||
UIStore.listen(EventTypes.UI_LATEST_TORRENT_LOCATION_CHANGE, this.onLatestTorrentLocationChange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
TorrentStore.unlisten(EventTypes.CLIENT_ADD_TORRENT_SUCCESS, this.onAddTorrentSuccess);
|
TorrentStore.unlisten(EventTypes.CLIENT_ADD_TORRENT_SUCCESS, this.onAddTorrentSuccess);
|
||||||
UIStore.unlisten(EventTypes.UI_LATEST_TORRENT_LOCATION_CHANGE, this.onLatestTorrentLocationChange);
|
|
||||||
UIStore.fetchLatestTorrentLocation();
|
UIStore.fetchLatestTorrentLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,10 +56,6 @@ export default class AddTorrents extends React.Component {
|
|||||||
this.dismissModal();
|
this.dismissModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
onLatestTorrentLocationChange() {
|
|
||||||
this.setState({destination: UIStore.getLatestTorrentLocation()});
|
|
||||||
}
|
|
||||||
|
|
||||||
getActions() {
|
getActions() {
|
||||||
let icon = null;
|
let icon = null;
|
||||||
let primaryButtonText = 'Add Torrent';
|
let primaryButtonText = 'Add Torrent';
|
||||||
@@ -104,80 +87,27 @@ export default class AddTorrents extends React.Component {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
getContent() {
|
getAddByFileContent() {
|
||||||
let error = null;
|
return <span>add by file</span>;
|
||||||
|
|
||||||
if (this.state.addTorrentsError) {
|
|
||||||
error = (
|
|
||||||
<div className="form__row">
|
|
||||||
{this.state.addTorrentsError}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="form">
|
|
||||||
{error}
|
|
||||||
<div className="form__row">
|
|
||||||
<TextboxRepeater placeholder="Torrent URL"
|
|
||||||
handleTextboxAdd={this.handleUrlAdd}
|
|
||||||
handleTextboxChange={this.handleUrlChange}
|
|
||||||
handleTextboxRemove={this.handleUrlRemove}
|
|
||||||
textboxes={this.state.urlTextboxes} />
|
|
||||||
</div>
|
|
||||||
<div className="form__row">
|
|
||||||
<input className="textbox"
|
|
||||||
onChange={this.handleDestinationChange}
|
|
||||||
placeholder="Destination"
|
|
||||||
value={this.state.destination}
|
|
||||||
type="text" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAddTorrents() {
|
|
||||||
this.setState({isAddingTorrents: true});
|
|
||||||
let torrentUrls = _.map(this.state.urlTextboxes, 'value');
|
|
||||||
TorrentActions.addTorrents(torrentUrls, this.state.destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDestinationChange(event) {
|
|
||||||
this.setState({
|
|
||||||
destination: event.target.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMenuWrapperClick(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUrlRemove(index) {
|
|
||||||
let urlTextboxes = Object.assign([], this.state.urlTextboxes);
|
|
||||||
urlTextboxes.splice(index, 1);
|
|
||||||
this.setState({
|
|
||||||
urlTextboxes
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUrlAdd(index) {
|
|
||||||
let urlTextboxes = Object.assign([], this.state.urlTextboxes);
|
|
||||||
urlTextboxes.splice(index + 1, 0, {value: null});
|
|
||||||
this.setState({urlTextboxes});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUrlChange(index, value) {
|
|
||||||
let urlTextboxes = Object.assign([], this.state.urlTextboxes);
|
|
||||||
urlTextboxes[index].value = value;
|
|
||||||
this.setState({urlTextboxes});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let tabs = {
|
||||||
|
'by-url': {
|
||||||
|
content: <AddTorrentsByURL />,
|
||||||
|
label: 'By URL'
|
||||||
|
},
|
||||||
|
'by-file': {
|
||||||
|
content: <AddTorrentsByFile />,
|
||||||
|
label: 'By File'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal heading="Add Torrents"
|
<Modal heading="Add Torrents"
|
||||||
content={this.getContent()}
|
|
||||||
actions={this.getActions()}
|
actions={this.getActions()}
|
||||||
dismiss={this.dismissModal} />
|
dismiss={this.dismissModal}
|
||||||
|
tabs={tabs} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
client/source/scripts/components/modals/AddTorrentsByFile.js
Normal file
35
client/source/scripts/components/modals/AddTorrentsByFile.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import Dropzone from 'react-dropzone';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const METHODS_TO_BIND = ['handleOpenClick'];
|
||||||
|
|
||||||
|
export default class AddTorrents extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
files: null
|
||||||
|
};
|
||||||
|
|
||||||
|
METHODS_TO_BIND.forEach((method) => {
|
||||||
|
this[method] = this[method].bind(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFileDrop(files) {
|
||||||
|
this.setState({files});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="form">
|
||||||
|
<Dropzone className="form__dropzone dropzone" ref="dropzone" onDrop={this.handleFileDrop}>
|
||||||
|
Drop some files here,
|
||||||
|
<span className="dropzone__browse-button">
|
||||||
|
or click to browse.
|
||||||
|
</span>
|
||||||
|
</Dropzone>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
132
client/source/scripts/components/modals/AddTorrentsByURL.js
Normal file
132
client/source/scripts/components/modals/AddTorrentsByURL.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
'handleDestinationChange',
|
||||||
|
'handleUrlAdd',
|
||||||
|
'handleUrlChange',
|
||||||
|
'handleUrlRemove',
|
||||||
|
'handleAddTorrents',
|
||||||
|
'onLatestTorrentLocationChange'
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class AddTorrents extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
addTorrentsError: null,
|
||||||
|
destination: null,
|
||||||
|
isAddingTorrents: false,
|
||||||
|
urlTextboxes: [{value: null}]
|
||||||
|
};
|
||||||
|
|
||||||
|
METHODS_TO_BIND.forEach((method) => {
|
||||||
|
this[method] = this[method].bind(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.setState({destination: UIStore.getLatestTorrentLocation()});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
UIStore.listen(EventTypes.UI_LATEST_TORRENT_LOCATION_CHANGE, this.onLatestTorrentLocationChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
UIStore.unlisten(EventTypes.UI_LATEST_TORRENT_LOCATION_CHANGE, this.onLatestTorrentLocationChange);
|
||||||
|
UIStore.fetchLatestTorrentLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissModal() {
|
||||||
|
UIActions.dismissModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddTorrentError() {
|
||||||
|
this.setState({
|
||||||
|
addTorrentsError: 'There was an error, but I have no idea what happened!',
|
||||||
|
isAddingTorrents: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onLatestTorrentLocationChange() {
|
||||||
|
this.setState({destination: UIStore.getLatestTorrentLocation()});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAddTorrents() {
|
||||||
|
this.setState({isAddingTorrents: true});
|
||||||
|
let torrentUrls = _.map(this.state.urlTextboxes, 'value');
|
||||||
|
TorrentActions.addTorrents(torrentUrls, this.state.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDestinationChange(event) {
|
||||||
|
this.setState({destination: event.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUrlRemove(index) {
|
||||||
|
let urlTextboxes = Object.assign([], this.state.urlTextboxes);
|
||||||
|
urlTextboxes.splice(index, 1);
|
||||||
|
this.setState({urlTextboxes});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUrlAdd(index) {
|
||||||
|
let urlTextboxes = Object.assign([], this.state.urlTextboxes);
|
||||||
|
urlTextboxes.splice(index + 1, 0, {value: null});
|
||||||
|
this.setState({urlTextboxes});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUrlChange(index, value) {
|
||||||
|
let urlTextboxes = Object.assign([], this.state.urlTextboxes);
|
||||||
|
urlTextboxes[index].value = value;
|
||||||
|
this.setState({urlTextboxes});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
if (this.state.addTorrentsError) {
|
||||||
|
error = (
|
||||||
|
<div className="form__row">
|
||||||
|
{this.state.addTorrentsError}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="form">
|
||||||
|
{error}
|
||||||
|
<div className="form__row">
|
||||||
|
<label className="form__label">
|
||||||
|
Torrents
|
||||||
|
</label>
|
||||||
|
<TextboxRepeater placeholder="Torrent URL"
|
||||||
|
handleTextboxAdd={this.handleUrlAdd}
|
||||||
|
handleTextboxChange={this.handleUrlChange}
|
||||||
|
handleTextboxRemove={this.handleUrlRemove}
|
||||||
|
textboxes={this.state.urlTextboxes} />
|
||||||
|
</div>
|
||||||
|
<div className="form__row">
|
||||||
|
<label className="form__label">
|
||||||
|
Destination
|
||||||
|
</label>
|
||||||
|
<input className="textbox"
|
||||||
|
onChange={this.handleDestinationChange}
|
||||||
|
placeholder="Destination"
|
||||||
|
value={this.state.destination}
|
||||||
|
type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,32 @@ import _ from 'lodash';
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import CustomScrollbars from '../ui/CustomScrollbars';
|
||||||
|
import ModalTabs from './ModalTabs';
|
||||||
|
|
||||||
|
const METHODS_TO_BIND = ['handleTabChange'];
|
||||||
|
|
||||||
export default class Modal extends React.Component {
|
export default class Modal extends React.Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
activeTabId: null
|
||||||
|
};
|
||||||
|
|
||||||
|
METHODS_TO_BIND.forEach((method) => {
|
||||||
|
this[method] = this[method].bind(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveTabId() {
|
||||||
|
if (this.state.activeTabId) {
|
||||||
|
return this.state.activeTabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(this.props.tabs)[0];
|
||||||
|
}
|
||||||
|
|
||||||
getModalButtons(actions) {
|
getModalButtons(actions) {
|
||||||
let buttons = actions.map((action, index) => {
|
let buttons = actions.map((action, index) => {
|
||||||
let classes = classnames('button', {
|
let classes = classnames('button', {
|
||||||
@@ -37,22 +62,41 @@ export default class Modal extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTabChange(tab) {
|
||||||
|
this.setState({activeTabId: tab.id});
|
||||||
|
}
|
||||||
|
|
||||||
handleMenuWrapperClick(event) {
|
handleMenuWrapperClick(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let contentClasses = classnames('modal__content',
|
let content = this.props.content;
|
||||||
`modal__content--align-${this.props.alignment}`);
|
let contentClasses = classnames('modal__content__wrapper',
|
||||||
|
`modal--align-${this.props.alignment}`);
|
||||||
|
let tabs = null;
|
||||||
|
|
||||||
|
if (this.props.tabs) {
|
||||||
|
let activeTabId = this.getActiveTabId();
|
||||||
|
|
||||||
|
content = this.props.tabs[activeTabId].content;
|
||||||
|
tabs = (
|
||||||
|
<ModalTabs activeTabId={activeTabId} onTabChange={this.handleTabChange}
|
||||||
|
tabs={this.props.tabs} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={contentClasses} onClick={this.handleMenuWrapperClick}>
|
<div className={contentClasses} onClick={this.handleMenuWrapperClick}>
|
||||||
<div className="modal__header">{this.props.heading}</div>
|
<div className="modal__header">
|
||||||
<div className="modal__content__container">
|
{this.props.heading}
|
||||||
{this.props.content}
|
{tabs}
|
||||||
</div>
|
</div>
|
||||||
<div className="modal__footer">
|
<div className="modal__content">
|
||||||
{this.getModalButtons(this.props.actions)}
|
{content}
|
||||||
|
<div className="modal__footer">
|
||||||
|
{this.getModalButtons(this.props.actions)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
38
client/source/scripts/components/modals/ModalTabs.js
Normal file
38
client/source/scripts/components/modals/ModalTabs.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import classnames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default class ModalTabs extends React.Component {
|
||||||
|
handleTabClick(tab) {
|
||||||
|
if (this.props.onTabChange) {
|
||||||
|
this.props.onTabChange(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let tabs = Object.keys(this.props.tabs).map((tabId, index) => {
|
||||||
|
let currentTab = this.props.tabs[tabId];
|
||||||
|
|
||||||
|
currentTab.id = tabId;
|
||||||
|
|
||||||
|
let classes = classnames('modal__tab', {
|
||||||
|
'is-active': tabId === this.props.activeTabId
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className={classes} key={index}
|
||||||
|
onClick={this.handleTabClick.bind(this, currentTab)}>
|
||||||
|
{currentTab.label}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<ul className="modal__tabs">
|
||||||
|
{tabs}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalTabs.defaultProps = {
|
||||||
|
tabs: []
|
||||||
|
};
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
"react-addons-css-transition-group": "^0.14.7",
|
"react-addons-css-transition-group": "^0.14.7",
|
||||||
"react-custom-scrollbars": "^3.0.0",
|
"react-custom-scrollbars": "^3.0.0",
|
||||||
"react-dom": "^0.14.7",
|
"react-dom": "^0.14.7",
|
||||||
|
"react-dropzone": "^3.3.2",
|
||||||
"sax": "^0.6.1",
|
"sax": "^0.6.1",
|
||||||
"serve-favicon": "~2.2.0",
|
"serve-favicon": "~2.2.0",
|
||||||
"xmlbuilder": "^2.6.2",
|
"xmlbuilder": "^2.6.2",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -484,9 +484,9 @@ th {
|
|||||||
outline: none; }
|
outline: none; }
|
||||||
|
|
||||||
.textbox {
|
.textbox {
|
||||||
background: rgba(233, 238, 242, 0.3);
|
background: #e9eff5;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #e9eef2;
|
border: 1px solid #d6e2ea;
|
||||||
color: #53718a;
|
color: #53718a;
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
@@ -515,8 +515,8 @@ th {
|
|||||||
-webkit-transition: color 0.25s;
|
-webkit-transition: color 0.25s;
|
||||||
transition: color 0.25s; }
|
transition: color 0.25s; }
|
||||||
.textbox:focus {
|
.textbox:focus {
|
||||||
background: rgba(233, 238, 242, 0.3);
|
background: #fdfefe;
|
||||||
border-color: #e9eef2;
|
border-color: #c7d6df;
|
||||||
color: #258de5; }
|
color: #258de5; }
|
||||||
.textbox:focus::-webkit-input-placeholder {
|
.textbox:focus::-webkit-input-placeholder {
|
||||||
color: #abbac7; }
|
color: #abbac7; }
|
||||||
@@ -526,6 +526,8 @@ th {
|
|||||||
color: #abbac7; }
|
color: #abbac7; }
|
||||||
.textbox:focus::placeholder {
|
.textbox:focus::placeholder {
|
||||||
color: #abbac7; }
|
color: #abbac7; }
|
||||||
|
.textbox.is-fulfilled {
|
||||||
|
background: #fdfefe; }
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -560,12 +562,13 @@ th {
|
|||||||
background: #1a80d7; }
|
background: #1a80d7; }
|
||||||
|
|
||||||
.form__label {
|
.form__label {
|
||||||
color: #53718a;
|
color: #abbac7;
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.1em; }
|
font-size: 0.8em;
|
||||||
|
margin-bottom: 0.5em; }
|
||||||
|
|
||||||
.form__row + .form__row {
|
.form__row + .form__row {
|
||||||
margin-top: 20px; }
|
margin-top: 25px; }
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
@@ -1140,7 +1143,7 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
outline: none;
|
outline: none;
|
||||||
margin-right: 8px;
|
margin-right: 6px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
-webkit-transition: background 0.25s, box-shadow 0.25s;
|
-webkit-transition: background 0.25s, box-shadow 0.25s;
|
||||||
@@ -1267,33 +1270,81 @@ body {
|
|||||||
transition: opacity 0.5s;
|
transition: opacity 0.5s;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 100; }
|
z-index: 100; }
|
||||||
.modal__content {
|
.modal__header {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 5px;
|
border-radius: 3px 3px 0 0;
|
||||||
box-shadow: 0 0 0 1px rgba(26, 47, 61, 0.1), 0 0 35px rgba(26, 47, 61, 0.3);
|
box-shadow: inset 0 -1px 0 #dde7ed;
|
||||||
left: 50%;
|
color: #313436;
|
||||||
max-height: 80%;
|
-webkit-box-flex: 0;
|
||||||
max-width: 80%;
|
-webkit-flex: 0 0 auto;
|
||||||
|
-ms-flex: 0 0 auto;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 400;
|
||||||
|
padding: 12.5px 25px 0 25px; }
|
||||||
|
.modal__tabs {
|
||||||
|
color: #abbac7;
|
||||||
|
font-size: 0.7em;
|
||||||
|
margin: 0 -5px; }
|
||||||
|
.modal__tabs .modal__tab {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 6.25px;
|
||||||
|
padding: 5px;
|
||||||
|
position: relative; }
|
||||||
|
.modal__tabs .modal__tab:after {
|
||||||
|
bottom: 0;
|
||||||
|
content: '';
|
||||||
|
height: 1px;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
-webkit-transition: background 0.25s;
|
||||||
|
transition: background 0.25s; }
|
||||||
|
.modal__tabs .modal__tab:last-child {
|
||||||
|
margin-right: 0; }
|
||||||
|
.modal__tabs .modal__tab.is-active {
|
||||||
|
color: #258de5;
|
||||||
|
font-weight: 600; }
|
||||||
|
.modal__tabs .modal__tab.is-active:after {
|
||||||
|
background: #258de5; }
|
||||||
|
.modal__content {
|
||||||
|
-webkit-box-flex: 1;
|
||||||
|
-webkit-flex: 1 1 auto;
|
||||||
|
-ms-flex: 1 1 auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 30px;
|
padding: 12.5px 25px; }
|
||||||
position: absolute;
|
.modal__content__wrapper {
|
||||||
top: 10%;
|
background: #f7fafc;
|
||||||
-webkit-transform: translate(-50%, 0);
|
border-radius: 3px;
|
||||||
transform: translate(-50%, 0);
|
box-shadow: 0 0 0 1px rgba(26, 47, 61, 0.1), 0 0 35px rgba(26, 47, 61, 0.3);
|
||||||
width: 500px; }
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
left: 50%;
|
||||||
|
max-height: 80%;
|
||||||
|
max-width: 80%;
|
||||||
|
position: absolute;
|
||||||
|
top: 10%;
|
||||||
|
-webkit-transform: translate(-50%, 0);
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
width: 500px; }
|
||||||
.modal__content--align-center {
|
.modal__content--align-center {
|
||||||
text-align: center; }
|
text-align: center; }
|
||||||
.modal__footer {
|
.modal__footer {
|
||||||
margin-top: 25px; }
|
margin-top: 25px;
|
||||||
|
padding: 0 0 12.5px 0; }
|
||||||
.modal__button-group {
|
.modal__button-group {
|
||||||
text-align: right; }
|
text-align: right; }
|
||||||
.modal__button-group .button + .button {
|
.modal__button-group .button + .button {
|
||||||
margin-left: 20px; }
|
margin-left: 20px; }
|
||||||
.modal__header {
|
|
||||||
color: #313436;
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-bottom: 20px; }
|
|
||||||
.modal__animation-enter {
|
.modal__animation-enter {
|
||||||
opacity: 0; }
|
opacity: 0; }
|
||||||
.modal__animation-enter-active {
|
.modal__animation-enter-active {
|
||||||
@@ -1582,6 +1633,9 @@ body {
|
|||||||
.textbox-repeater .textbox__wrapper {
|
.textbox-repeater .textbox__wrapper {
|
||||||
position: relative; }
|
position: relative; }
|
||||||
|
|
||||||
|
.textbox-repeater .form__row + .form__row {
|
||||||
|
margin-top: 12.5px; }
|
||||||
|
|
||||||
.application__panel--torrent-details {
|
.application__panel--torrent-details {
|
||||||
background: #0e2231; }
|
background: #0e2231; }
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user