mirror of
https://github.com/zoriya/flood.git
synced 2025-12-22 23:25:27 +00:00
Introduce client settings
This commit is contained in:
@@ -30,6 +30,11 @@ $checkbox--border--hover: $checkbox--border;
|
||||
|
||||
$modal--body--foreground: desaturate(lighten($foreground, 20%), 10%);
|
||||
|
||||
$form--section--heading--margin: $spacing-unit * 2/5;
|
||||
$form--section--margin: $spacing-unit;
|
||||
$form--row--margin: $spacing-unit * 3/5;
|
||||
$form--column--padding: $spacing-unit * 2/5;
|
||||
|
||||
.textbox,
|
||||
.button,
|
||||
.checkbox {
|
||||
@@ -191,17 +196,36 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%);
|
||||
|
||||
.form {
|
||||
|
||||
&__section {
|
||||
|
||||
&__heading {
|
||||
margin-bottom: $form--section--heading--margin;
|
||||
|
||||
& + .form__section__sub-heading {
|
||||
margin-bottom: $form--section--heading--margin;
|
||||
margin-top: $form--section--heading--margin * -1;
|
||||
}
|
||||
}
|
||||
|
||||
& + .form__section {
|
||||
margin-top: $form--section--margin;
|
||||
}
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
|
||||
& + .form__row {
|
||||
margin-top: $spacing-unit;
|
||||
margin-top: $form--row--margin;
|
||||
}
|
||||
}
|
||||
|
||||
&__column {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 0 $spacing-unit * 2/5;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding: 0 $form--column--padding;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
@@ -210,6 +234,38 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%);
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&--auto {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
&--half {
|
||||
max-width: 50%;
|
||||
|
||||
&:last-child {
|
||||
padding-right: $form--column--padding;
|
||||
}
|
||||
}
|
||||
|
||||
&--small {
|
||||
max-width: 125px;
|
||||
|
||||
// For small columns which are the only column in the row, keep the
|
||||
// column's padding.
|
||||
&:first-child {
|
||||
|
||||
&:last-child {
|
||||
padding-right: $form--column--padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--unlabled {
|
||||
justify-content: center;
|
||||
padding-top: $spacing-unit * 3/5;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
$modal--background: #12191f;
|
||||
|
||||
$modal--heading--background: #161c24;
|
||||
$modal--heading--foreground: #7d95ab;
|
||||
$modal--heading--border: #0f151b;
|
||||
|
||||
$modal--sub-heading--foreground: desaturate(darken($modal--heading--foreground, 23%), 2%);
|
||||
|
||||
$modal--transition--duration: 0.5s;
|
||||
$modal--transition--scale: 0.85;
|
||||
|
||||
@@ -106,15 +109,6 @@ $modal--tabs--in-body--background: #11171d;
|
||||
}
|
||||
}
|
||||
|
||||
&__tab {
|
||||
|
||||
&__introduction {
|
||||
color: $modal--heading--foreground;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: $spacing-unit * 3/4;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
background: $modal--heading--background;
|
||||
border-radius: $modal--border-radius $modal--border-radius 0 0;
|
||||
@@ -173,6 +167,7 @@ $modal--tabs--in-body--background: #11171d;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 $modal--padding--horizontal $modal--padding--vertical $modal--padding--horizontal;
|
||||
|
||||
.modal {
|
||||
@@ -283,6 +278,10 @@ $modal--tabs--in-body--background: #11171d;
|
||||
|
||||
&__content {
|
||||
flex: 1 0 auto;
|
||||
|
||||
& + .modal__footer {
|
||||
margin-top: $spacing-unit * 3/5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,4 +371,21 @@ $modal--tabs--in-body--background: #11171d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
|
||||
&__section {
|
||||
|
||||
&__heading {
|
||||
color: $modal--heading--foreground;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__sub-heading {
|
||||
color: $modal--sub-heading--foreground;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ $more-info--border: $textbox-repeater--button--border;
|
||||
|
||||
&--eta {
|
||||
opacity: 0;
|
||||
transition: opacity 1s, visibility 1s;
|
||||
transition: color 0.25s, opacity 1s, visibility 1s;
|
||||
visibility: hidden;
|
||||
|
||||
.torrent__details--segment {
|
||||
|
||||
@@ -1,9 +1,49 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import AppDispatcher from '../dispatcher/AppDispatcher';
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import AppDispatcher from '../dispatcher/AppDispatcher';
|
||||
|
||||
const ClientActions = {
|
||||
fetchSettings: (property) => {
|
||||
return axios.get('/client/settings', {params: {property}})
|
||||
.then((json = {}) => {
|
||||
return json.data;
|
||||
})
|
||||
.then((data) => {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS,
|
||||
data
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_SETTINGS_FETCH_REQUEST_ERROR,
|
||||
error
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
saveSettings: (settings, options) => {
|
||||
return axios.patch('/client/settings', settings)
|
||||
.then((json = {}) => {
|
||||
return json.data;
|
||||
})
|
||||
.then((data) => {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_SETTINGS_SAVE_SUCCESS,
|
||||
data,
|
||||
options
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_SETTINGS_SAVE_ERROR,
|
||||
error,
|
||||
options
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
setThrottle: (direction, throttle) => {
|
||||
return axios.put('/client/settings/speed-limits', {
|
||||
direction,
|
||||
|
||||
@@ -16,7 +16,6 @@ const SettingsActions = {
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.trace(error);
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.SETTINGS_FETCH_REQUEST_ERROR,
|
||||
error
|
||||
@@ -37,7 +36,6 @@ const SettingsActions = {
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.trace(error);
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.SETTINGS_SAVE_REQUEST_ERROR,
|
||||
error
|
||||
|
||||
@@ -93,7 +93,6 @@ const TorrentActions = {
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.trace(error);
|
||||
AppDispatcher.dispatchServerAction({
|
||||
type: ActionTypes.CLIENT_FETCH_TORRENTS_ERROR,
|
||||
data: {
|
||||
|
||||
@@ -13,7 +13,8 @@ import TorrentListView from './components/panels/TorrentListView';
|
||||
|
||||
class FloodApp extends React.Component {
|
||||
componentDidMount() {
|
||||
SettingsStore.fetchSettings();
|
||||
SettingsStore.fetchClientSettings();
|
||||
SettingsStore.fetchFloodSettings();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -20,7 +20,8 @@ export default class AddTorrentsActions extends React.Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let startTorrentsOnLoad = SettingsStore.getSettings('startTorrentsOnLoad');
|
||||
let startTorrentsOnLoad = SettingsStore.getFloodSettings(
|
||||
'startTorrentsOnLoad');
|
||||
if (startTorrentsOnLoad !== true) {
|
||||
this.setState({startTorrentsOnLoad: false});
|
||||
}
|
||||
@@ -65,7 +66,7 @@ export default class AddTorrentsActions extends React.Component {
|
||||
}
|
||||
|
||||
handleStartTorrentsToggle(value) {
|
||||
SettingsStore.saveSettings({id: 'startTorrentsOnLoad', data: value});
|
||||
SettingsStore.saveFloodSettings({id: 'startTorrentsOnLoad', data: value});
|
||||
if (!!this.props.onStartTorrentsToggle) {
|
||||
this.props.onStartTorrentsToggle(value);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ export default class AddTorrentsDestination extends React.Component {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let destination = SettingsStore.getSettings('torrentDestination') || '';
|
||||
let destination = SettingsStore.getFloodSettings('torrentDestination')
|
||||
|| '';
|
||||
if (this.props.suggested) {
|
||||
destination = this.props.suggested;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
import BandwidthTab from '../settings/BandwidthTab';
|
||||
import ConnectivityTab from '../settings/ConnectivityTab';
|
||||
import EventTypes from '../../constants/EventTypes';
|
||||
import LoadingIndicatorDots from '../icons/LoadingIndicatorDots';
|
||||
import Modal from './Modal';
|
||||
import SettingsStore from '../../stores/SettingsStore';
|
||||
import SpeedLimitTab from '../settings/SpeedLimitTab';
|
||||
import StorageTab from '../settings/StorageTab';
|
||||
|
||||
const METHODS_TO_BIND = [
|
||||
'handleSaveSettingsClick',
|
||||
'handleSaveSettingsError',
|
||||
'handleSettingsChange',
|
||||
'handleClientSettingsChange',
|
||||
'handleFloodSettingsChange',
|
||||
'handleSettingsStoreChange'
|
||||
];
|
||||
|
||||
@@ -20,7 +23,10 @@ export default class SettingsModal extends React.Component {
|
||||
|
||||
this.state = {
|
||||
isSavingSettings: false,
|
||||
settings: SettingsStore.getSettings()
|
||||
changedClientSettings: {},
|
||||
changedFloodSettings: {},
|
||||
clientSettings: SettingsStore.getClientSettings(),
|
||||
floodSettings: SettingsStore.getFloodSettings()
|
||||
};
|
||||
|
||||
METHODS_TO_BIND.forEach((method) => {
|
||||
@@ -33,12 +39,14 @@ export default class SettingsModal extends React.Component {
|
||||
this.handleSettingsStoreChange);
|
||||
SettingsStore.listen(EventTypes.SETTINGS_SAVE_REQUEST_ERROR,
|
||||
this.handleSaveSettingsError);
|
||||
SettingsStore.fetchSettings('speedLimits');
|
||||
SettingsStore.fetchFloodSettings('speedLimits');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
SettingsStore.unlisten(EventTypes.SETTINGS_CHANGE,
|
||||
this.handleSettingsStoreChange);
|
||||
SettingsStore.unlisten(EventTypes.SETTINGS_SAVE_REQUEST_ERROR,
|
||||
this.handleSaveSettingsError);
|
||||
}
|
||||
|
||||
getActions() {
|
||||
@@ -75,14 +83,22 @@ export default class SettingsModal extends React.Component {
|
||||
handleSaveSettingsClick() {
|
||||
this.setState({isSavingSettings: true});
|
||||
|
||||
let settingsToSave = Object.keys(this.state.settings).map((settingsKey) => {
|
||||
let floodSettings = Object.keys(this.state.changedFloodSettings).map((settingsKey) => {
|
||||
return {
|
||||
id: settingsKey,
|
||||
data: this.state.settings[settingsKey]
|
||||
data: this.state.changedFloodSettings[settingsKey]
|
||||
};
|
||||
});
|
||||
|
||||
SettingsStore.saveSettings(settingsToSave, {dismissModal: true, notify: true});
|
||||
let clientSettings = Object.keys(this.state.changedClientSettings).map((settingsKey) => {
|
||||
return {
|
||||
id: settingsKey,
|
||||
data: this.state.changedClientSettings[settingsKey]
|
||||
};
|
||||
});
|
||||
|
||||
SettingsStore.saveFloodSettings(floodSettings, {dismissModal: true, notify: true});
|
||||
SettingsStore.saveClientSettings(clientSettings, {dismissModal: true, notify: true});
|
||||
}
|
||||
|
||||
handleSaveSettingsError() {
|
||||
@@ -95,13 +111,23 @@ export default class SettingsModal extends React.Component {
|
||||
|
||||
handleSettingsStoreChange() {
|
||||
this.setState({
|
||||
settings: SettingsStore.getSettings()
|
||||
clientSettings: SettingsStore.getClientSettings(),
|
||||
floodSettings: SettingsStore.getFloodSettings()
|
||||
});
|
||||
}
|
||||
|
||||
handleSettingsChange(changedSettings) {
|
||||
let settings = this.mergeObjects(this.state.settings, changedSettings);
|
||||
this.setState({settings});
|
||||
handleFloodSettingsChange(changedSettings) {
|
||||
let floodSettings = this.mergeObjects(this.state.floodSettings, changedSettings);
|
||||
let changedFloodSettings = this.mergeObjects(this.state.changedFloodSettings, changedSettings);
|
||||
|
||||
this.setState({floodSettings, changedFloodSettings});
|
||||
}
|
||||
|
||||
handleClientSettingsChange(changedSettings) {
|
||||
let clientSettings = this.mergeObjects(this.state.clientSettings, changedSettings);
|
||||
let changedClientSettings = this.mergeObjects(this.state.changedClientSettings, changedSettings);
|
||||
|
||||
this.setState({clientSettings, changedClientSettings});
|
||||
}
|
||||
|
||||
mergeObjects(objA, objB) {
|
||||
@@ -123,13 +149,30 @@ export default class SettingsModal extends React.Component {
|
||||
|
||||
render() {
|
||||
let tabs = {
|
||||
'speed-limit': {
|
||||
content: SpeedLimitTab,
|
||||
bandwidth: {
|
||||
content: BandwidthTab,
|
||||
props: {
|
||||
onSettingsChange: this.handleSettingsChange,
|
||||
settings: this.state.settings
|
||||
onClientSettingsChange: this.handleClientSettingsChange,
|
||||
onSettingsChange: this.handleFloodSettingsChange,
|
||||
settings: this.mergeObjects(this.state.floodSettings, this.state.clientSettings)
|
||||
},
|
||||
label: 'Speed Limits'
|
||||
label: 'Bandwidth'
|
||||
},
|
||||
connectivity: {
|
||||
content: ConnectivityTab,
|
||||
props: {
|
||||
onClientSettingsChange: this.handleClientSettingsChange,
|
||||
settings: this.state.clientSettings
|
||||
},
|
||||
label: 'Connectivity'
|
||||
},
|
||||
storage: {
|
||||
content: StorageTab,
|
||||
props: {
|
||||
onClientSettingsChange: this.handleClientSettingsChange,
|
||||
settings: this.state.clientSettings
|
||||
},
|
||||
label: 'Storage'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
195
client/source/scripts/components/settings/BandwidthTab.js
Normal file
195
client/source/scripts/components/settings/BandwidthTab.js
Normal file
@@ -0,0 +1,195 @@
|
||||
import React from 'react';
|
||||
|
||||
import SettingsTab from './SettingsTab';
|
||||
|
||||
const METHODS_TO_BIND = ['handleDownloadTextChange', 'handleUploadTextChange'];
|
||||
|
||||
export default class BandwidthTab extends SettingsTab {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
downloadValue: null,
|
||||
uploadValue: null
|
||||
};
|
||||
|
||||
METHODS_TO_BIND.forEach((method) => {
|
||||
this[method] = this[method].bind(this);
|
||||
});
|
||||
}
|
||||
|
||||
arrayToString(array) {
|
||||
return array.join(', ');
|
||||
}
|
||||
|
||||
getTextboxValue(input = []) {
|
||||
if (Array.isArray(input)) {
|
||||
return this.arrayToString(input);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
handleDownloadTextChange(event) {
|
||||
this.setState({
|
||||
downloadValue: event.target.value
|
||||
});
|
||||
|
||||
this.props.onSettingsChange({
|
||||
speedLimits: {
|
||||
download: this.processSpeedsForSave(event.target.value),
|
||||
upload: this.processSpeedsForSave(this.getUploadValue())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleUploadTextChange(event) {
|
||||
this.setState({
|
||||
uploadValue: event.target.value
|
||||
});
|
||||
|
||||
this.props.onSettingsChange({
|
||||
speedLimits: {
|
||||
download: this.processSpeedsForSave(this.getDownloadValue()),
|
||||
upload: this.processSpeedsForSave(event.target.value)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDownloadValue() {
|
||||
let displayedValue = this.state.downloadValue;
|
||||
|
||||
if (displayedValue == null && this.props.settings.speedLimits != null) {
|
||||
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.download);
|
||||
}
|
||||
|
||||
return displayedValue;
|
||||
}
|
||||
|
||||
getUploadValue() {
|
||||
let displayedValue = this.state.uploadValue;
|
||||
|
||||
if (displayedValue == null && this.props.settings.speedLimits != null) {
|
||||
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.upload);
|
||||
}
|
||||
|
||||
return displayedValue;
|
||||
}
|
||||
|
||||
processSpeedsForDisplay(speeds = []) {
|
||||
if (!speeds || speeds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.arrayToString(speeds.map((speed) => {
|
||||
return Number(speed) / 1024;
|
||||
}));
|
||||
}
|
||||
|
||||
processSpeedsForSave(speeds = '') {
|
||||
if (speeds === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.stringToArray(speeds).map((speed) => {
|
||||
return Number(speed) * 1024;
|
||||
});
|
||||
}
|
||||
|
||||
stringToArray(string = '') {
|
||||
return string.replace(/\s/g, '').split(',');
|
||||
}
|
||||
|
||||
render() {
|
||||
let downloadValue = this.getDownloadValue() || 0;
|
||||
let uploadValue = this.getUploadValue() || 0;
|
||||
|
||||
return (
|
||||
<div className="form">
|
||||
<div className="form__section">
|
||||
<p className="form__section__heading">
|
||||
Speed Limit Dropdown Presets
|
||||
</p>
|
||||
<p className="form__section__sub-heading">
|
||||
Enter a comma-separated list of speeds in kB. 0 represents unlimited.
|
||||
</p>
|
||||
<div className="form__row">
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Download Presets
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleDownloadTextChange}
|
||||
value={downloadValue} />
|
||||
</div>
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Upload Presets
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleUploadTextChange}
|
||||
value={uploadValue} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__section">
|
||||
<div className="form__section__heading">
|
||||
Slot Availability
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Upload Slots Per Torrent
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxUploads')}
|
||||
value={this.getFieldValue('throttleMaxUploads')} />
|
||||
</div>
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Upload Slots Divider
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxUploadsDiv')}
|
||||
value={this.getFieldValue('throttleMaxUploadsDiv')} />
|
||||
</div>
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Upload Slots Global
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxUploadsGlobal')}
|
||||
value={this.getFieldValue('throttleMaxUploadsGlobal')} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Download Slots Per Torrent
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxDownloads')}
|
||||
value={this.getFieldValue('throttleMaxDownloads')} />
|
||||
</div>
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Download Slots Divider
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxDownloadsDiv')}
|
||||
value={this.getFieldValue('throttleMaxDownloadsDiv')} />
|
||||
</div>
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Download Slots Global
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxDownloadsGlobal')}
|
||||
value={this.getFieldValue('throttleMaxDownloadsGlobal')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
115
client/source/scripts/components/settings/ConnectivityTab.js
Normal file
115
client/source/scripts/components/settings/ConnectivityTab.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../forms/Checkbox';
|
||||
import SettingsTab from './SettingsTab';
|
||||
|
||||
export default class ConnectivityTab extends SettingsTab {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="form">
|
||||
<div className="form__section">
|
||||
<div className="form__section__heading">
|
||||
Listening Port
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column form__column--small">
|
||||
<label className="form__label">
|
||||
Port Range
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'networkPortRange')}
|
||||
value={this.getFieldValue('networkPortRange')} />
|
||||
</div>
|
||||
<div className="form__column form__column--auto form__column--unlabled">
|
||||
<Checkbox
|
||||
checked={this.getFieldValue('networkPortRandom') === '1'}
|
||||
onChange={this.handleClientSettingCheckboxChange.bind(this, 'networkPortRandom')}>
|
||||
Randomize Port
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="form__column form__column--auto form__column--unlabled">
|
||||
<Checkbox
|
||||
checked={this.getFieldValue('networkPortOpen') === '1'}
|
||||
onChange={this.handleClientSettingCheckboxChange.bind(this, 'networkPortOpen')}>
|
||||
Open Port
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__section">
|
||||
<div className="form__section__heading">
|
||||
DHT
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column form__column--small">
|
||||
<label className="form__label">
|
||||
Port
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'dhtPort')}
|
||||
value={this.getFieldValue('dhtPort')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__section">
|
||||
<div className="form__section__heading">
|
||||
Peers
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Minimum Peers
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMinPeersNormal')}
|
||||
value={this.getFieldValue('throttleMinPeersNormal')} />
|
||||
</div>
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Maximum Peers
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxPeersNormal')}
|
||||
value={this.getFieldValue('throttleMaxPeersNormal')} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Minimum Peers Seeding
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMinPeersSeed')}
|
||||
value={this.getFieldValue('throttleMinPeersSeed')} />
|
||||
</div>
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Maximum Peers Seeding
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'throttleMaxPeersSeed')}
|
||||
value={this.getFieldValue('throttleMaxPeersSeed')} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column form__column--half">
|
||||
<label className="form__label">
|
||||
Amount Desired
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingFieldChange.bind(this, 'trackersNumWant')}
|
||||
value={this.getFieldValue('trackersNumWant')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
36
client/source/scripts/components/settings/SettingsTab.js
Normal file
36
client/source/scripts/components/settings/SettingsTab.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
|
||||
const METHODS_TO_BIND = ['handleClientSettingFieldChange'];
|
||||
|
||||
export default class SettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
METHODS_TO_BIND.forEach((method) => {
|
||||
this[method] = this[method].bind(this);
|
||||
});
|
||||
}
|
||||
|
||||
getFieldValue(fieldName) {
|
||||
if (this.state[fieldName] == null) {
|
||||
return this.props.settings[fieldName] || '';
|
||||
}
|
||||
|
||||
return this.state[fieldName];
|
||||
}
|
||||
|
||||
handleClientSettingFieldChange(fieldName, event) {
|
||||
let newState = {[fieldName]: event.target.value};
|
||||
|
||||
this.setState(newState);
|
||||
this.props.onClientSettingsChange(newState);
|
||||
}
|
||||
|
||||
handleClientSettingCheckboxChange(fieldName, value) {
|
||||
let checkedValue = value ? '1' : '0';
|
||||
let newState = {[fieldName]: checkedValue};
|
||||
|
||||
this.setState(newState);
|
||||
this.props.onClientSettingsChange(newState);
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const METHODS_TO_BIND = ['handleDownloadTextChange', 'handleUploadTextChange'];
|
||||
|
||||
export default class SpeedLimitTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
downloadValue: null,
|
||||
uploadValue: null
|
||||
};
|
||||
|
||||
METHODS_TO_BIND.forEach((method) => {
|
||||
this[method] = this[method].bind(this);
|
||||
});
|
||||
}
|
||||
|
||||
arrayToString(array) {
|
||||
return array.join(', ');
|
||||
}
|
||||
|
||||
getTextboxValue(input = []) {
|
||||
if (Array.isArray(input)) {
|
||||
return this.arrayToString(input);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
handleDownloadTextChange(event) {
|
||||
this.setState({
|
||||
downloadValue: event.target.value
|
||||
});
|
||||
|
||||
this.props.onSettingsChange({
|
||||
speedLimits: {
|
||||
download: this.processSpeedsForSave(event.target.value)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleUploadTextChange(event) {
|
||||
this.setState({
|
||||
uploadValue: event.target.value
|
||||
});
|
||||
|
||||
this.props.onSettingsChange({
|
||||
speedLimits: {
|
||||
upload: this.processSpeedsForSave(event.target.value)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDownloadValue() {
|
||||
let displayedValue = this.state.downloadValue;
|
||||
|
||||
if (displayedValue == null && this.props.settings.speedLimits != null) {
|
||||
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.download);
|
||||
}
|
||||
|
||||
return displayedValue;
|
||||
}
|
||||
|
||||
getUploadValue() {
|
||||
let displayedValue = this.state.uploadValue;
|
||||
|
||||
if (displayedValue == null && this.props.settings.speedLimits != null) {
|
||||
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.upload);
|
||||
}
|
||||
|
||||
return displayedValue;
|
||||
}
|
||||
|
||||
processSpeedsForDisplay(speeds = []) {
|
||||
if (!speeds || speeds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.arrayToString(speeds.map((speed) => {
|
||||
return Number(speed) / 1024;
|
||||
}));
|
||||
}
|
||||
|
||||
processSpeedsForSave(speeds = '') {
|
||||
if (speeds === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.stringToArray(speeds).map((speed) => {
|
||||
return Number(speed) * 1024;
|
||||
});
|
||||
}
|
||||
|
||||
stringToArray(string = '') {
|
||||
return string.replace(/\s/g, '').split(',');
|
||||
}
|
||||
|
||||
render() {
|
||||
let downloadValue = this.getDownloadValue() || 0;
|
||||
let uploadValue = this.getUploadValue() || 0;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="modal__tab__introduction">
|
||||
Provide a comma-separated list of speed values (in kilobytes per second). 0 represents unlimited.
|
||||
</p>
|
||||
<div className="form">
|
||||
<div className="form__row">
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Download Presets
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleDownloadTextChange}
|
||||
value={downloadValue} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Upload Presets
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleUploadTextChange}
|
||||
value={uploadValue} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
35
client/source/scripts/components/settings/StorageTab.js
Normal file
35
client/source/scripts/components/settings/StorageTab.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import Checkbox from '../forms/Checkbox';
|
||||
import SettingsTab from './SettingsTab';
|
||||
|
||||
export default class StorageTab extends SettingsTab {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="form">
|
||||
<div className="form__section">
|
||||
<div className="form__section__heading">
|
||||
Directories
|
||||
</div>
|
||||
<div className="form__row">
|
||||
<div className="form__column">
|
||||
<label className="form__label">
|
||||
Default Download Directory
|
||||
</label>
|
||||
<input className="textbox" type="text"
|
||||
onChange={this.handleClientSettingCheckboxChange.bind(this, 'directoryDefault')}
|
||||
value={this.getFieldValue('directoryDefault')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class SpeedLimitDropdown extends React.Component {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
speedLimits: SettingsStore.getSettings('speedLimits'),
|
||||
speedLimits: SettingsStore.getFloodSettings('speedLimits'),
|
||||
throttle: null
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ class SpeedLimitDropdown extends React.Component {
|
||||
this.handleSettingsFetchRequestSuccess);
|
||||
TransferDataStore.listen(EventTypes.CLIENT_TRANSFER_DATA_REQUEST_SUCCESS,
|
||||
this.onTransferDataRequestSuccess);
|
||||
SettingsStore.fetchSettings('speedLimits');
|
||||
SettingsStore.fetchFloodSettings('speedLimits');
|
||||
TransferDataStore.fetchTransferData();
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ class SpeedLimitDropdown extends React.Component {
|
||||
}
|
||||
|
||||
handleSettingsFetchRequestSuccess() {
|
||||
let speedLimits = SettingsStore.getSettings('speedLimits');
|
||||
let speedLimits = SettingsStore.getFloodSettings('speedLimits');
|
||||
|
||||
if (!!speedLimits) {
|
||||
this.setState({speedLimits});
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class ActionBar extends React.Component {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
sortBy: SettingsStore.getSettings('sortTorrents')
|
||||
sortBy: SettingsStore.getFloodSettings('sortTorrents')
|
||||
};
|
||||
|
||||
METHODS_TO_BIND.forEach((method) => {
|
||||
@@ -40,7 +40,7 @@ export default class ActionBar extends React.Component {
|
||||
componentDidMount() {
|
||||
this.onSortChange();
|
||||
SettingsStore.listen(EventTypes.SETTINGS_CHANGE, this.onSortChange);
|
||||
SettingsStore.fetchSettings('sortTorrents');
|
||||
SettingsStore.fetchFloodSettings('sortTorrents');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -99,7 +99,7 @@ export default class ActionBar extends React.Component {
|
||||
}
|
||||
|
||||
handleSortChange(sortBy) {
|
||||
SettingsStore.saveSettings({id: 'sortTorrents', data: sortBy});
|
||||
SettingsStore.saveFloodSettings({id: 'sortTorrents', data: sortBy});
|
||||
UIActions.setTorrentsSort(sortBy);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export default class ActionBar extends React.Component {
|
||||
}
|
||||
|
||||
onSortChange() {
|
||||
let sortBy = SettingsStore.getSettings('sortTorrents');
|
||||
let sortBy = SettingsStore.getFloodSettings('sortTorrents');
|
||||
TorrentFilterStore.setTorrentsSort(sortBy);
|
||||
this.setState({sortBy});
|
||||
}
|
||||
|
||||
@@ -21,9 +21,14 @@ const ActionTypes = {
|
||||
CLIENT_SET_FILE_PRIORITY_SUCCESS: 'CLIENT_SET_FILE_PRIORITY_SUCCESS',
|
||||
CLIENT_SET_THROTTLE_ERROR: 'CLIENT_SET_THROTTLE_ERROR',
|
||||
CLIENT_SET_THROTTLE_SUCCESS: 'CLIENT_SET_THROTTLE_SUCCESS',
|
||||
CLIENT_SETTINGS_FETCH_REQUEST_ERROR: 'CLIENT_SETTINGS_FETCH_REQUEST_ERROR',
|
||||
CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS: 'CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS',
|
||||
CLIENT_SETTINGS_SAVE_ERROR: 'CLIENT_SETTINGS_SAVE_ERROR',
|
||||
CLIENT_SETTINGS_SAVE_SUCCESS: 'CLIENT_SETTINGS_SAVE_SUCCESS',
|
||||
CLIENT_START_TORRENT_ERROR: 'CLIENT_START_TORRENT_ERROR',
|
||||
CLIENT_START_TORRENT_SUCCESS: 'CLIENT_START_TORRENT_SUCCESS',
|
||||
CLIENT_STOP_TORRENT_ERROR: 'CLIENT_STOP_TORRENT_ERROR',
|
||||
CLIENT_STOP_TORRENT_SUCCESS: 'CLIENT_STOP_TORRENT_SUCCESS',
|
||||
SETTINGS_FETCH_REQUEST_SUCCESS: 'SETTINGS_FETCH_REQUEST_SUCCESS',
|
||||
SETTINGS_FETCH_REQUEST_ERROR: 'SETTINGS_FETCH_REQUEST_ERROR',
|
||||
SETTINGS_SAVE_REQUEST_SUCCESS: 'SETTINGS_SAVE_REQUEST_SUCCESS',
|
||||
|
||||
@@ -5,6 +5,10 @@ const EventTypes = {
|
||||
CLIENT_SET_THROTTLE_SUCCESS: 'CLIENT_SET_THROTTLE_SUCCESS',
|
||||
CLIENT_MOVE_TORRENTS_REQUEST_ERROR: 'CLIENT_MOVE_TORRENTS_REQUEST_ERROR',
|
||||
CLIENT_MOVE_TORRENTS_SUCCESS: 'CLIENT_MOVE_TORRENTS_SUCCESS',
|
||||
CLIENT_SETTINGS_FETCH_REQUEST_ERROR: 'CLIENT_SETTINGS_FETCH_REQUEST_ERROR',
|
||||
CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS: 'CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS',
|
||||
CLIENT_SETTINGS_SAVE_REQUEST_ERROR: 'CLIENT_SETTINGS_SAVE_REQUEST_ERROR',
|
||||
CLIENT_SETTINGS_SAVE_REQUEST_SUCCESS: 'CLIENT_SETTINGS_SAVE_REQUEST_SUCCESS',
|
||||
CLIENT_TORRENTS_REQUEST_ERROR: 'CLIENT_TORRENTS_REQUEST_ERROR',
|
||||
CLIENT_TORRENT_STATUS_COUNT_CHANGE: 'CLIENT_TORRENT_STATUS_COUNT_CHANGE',
|
||||
CLIENT_TORRENT_STATUS_COUNT_REQUEST_ERROR: 'CLIENT_TORRENT_STATUS_COUNT_REQUEST_ERROR',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
import AppDispatcher from '../dispatcher/AppDispatcher';
|
||||
import BaseStore from './BaseStore';
|
||||
import ClientActions from '../actions/ClientActions';
|
||||
import EventTypes from '../constants/EventTypes';
|
||||
import NotificationStore from './NotificationStore';
|
||||
import SettingsActions from '../actions/SettingsActions';
|
||||
@@ -10,8 +11,15 @@ class SettingsStoreClass extends BaseStore {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.fetchStatus = {
|
||||
clientSettingsFetched: false,
|
||||
floodSettingsFetched: false
|
||||
};
|
||||
|
||||
this.clientSettings = {};
|
||||
|
||||
// Default settings are overridden by settings stored in database.
|
||||
this.settings = {
|
||||
this.floodSettings = {
|
||||
sortTorrents: {
|
||||
direction: 'desc',
|
||||
displayName: 'Date Added',
|
||||
@@ -25,16 +33,61 @@ class SettingsStoreClass extends BaseStore {
|
||||
};
|
||||
}
|
||||
|
||||
fetchSettings(property) {
|
||||
fetchClientSettings(property) {
|
||||
ClientActions.fetchSettings(property);
|
||||
}
|
||||
|
||||
fetchFloodSettings(property) {
|
||||
SettingsActions.fetchSettings(property);
|
||||
}
|
||||
|
||||
getSettings(property) {
|
||||
getClientSettings(property) {
|
||||
if (property) {
|
||||
return this.settings[property];
|
||||
return this.clientSettings[property];
|
||||
}
|
||||
|
||||
return this.settings;
|
||||
return Object.assign({}, this.clientSettings);
|
||||
}
|
||||
|
||||
getFloodSettings(property) {
|
||||
if (property) {
|
||||
return this.floodSettings[property];
|
||||
}
|
||||
|
||||
return Object.assign({}, this.floodSettings);
|
||||
}
|
||||
|
||||
handleClientSettingsFetchSuccess(settings) {
|
||||
this.fetchStatus.clientSettingsFetched = true;
|
||||
this.clientSettings = settings;
|
||||
|
||||
this.emit(EventTypes.CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS);
|
||||
this.processSettingsState();
|
||||
}
|
||||
|
||||
handleClientSettingsFetchError(error) {
|
||||
this.emit(EventTypes.CLIENT_SETTINGS_FETCH_REQUEST_ERROR);
|
||||
}
|
||||
|
||||
handleClientSettingsSaveRequestError() {
|
||||
this.emit(EventTypes.CLIENT_SETTINGS_SAVE_REQUEST_ERROR);
|
||||
}
|
||||
|
||||
handleClientSettingsSaveRequestSuccess(data, options) {
|
||||
this.emit(EventTypes.CLIENT_SETTINGS_SAVE_REQUEST_SUCCESS);
|
||||
|
||||
if (options.notify) {
|
||||
NotificationStore.add({
|
||||
adverb: 'Successfully',
|
||||
action: 'saved',
|
||||
subject: 'settings',
|
||||
id: 'save-settings-success'
|
||||
});
|
||||
}
|
||||
|
||||
if (options.dismissModal) {
|
||||
UIStore.dismissModal();
|
||||
}
|
||||
}
|
||||
|
||||
handleSettingsFetchError(error) {
|
||||
@@ -42,12 +95,14 @@ class SettingsStoreClass extends BaseStore {
|
||||
}
|
||||
|
||||
handleSettingsFetchSuccess(settings) {
|
||||
this.fetchStatus.floodSettingsFetched = true;
|
||||
|
||||
Object.keys(settings).forEach((property) => {
|
||||
this.settings[property] = settings[property];
|
||||
this.floodSettings[property] = settings[property];
|
||||
});
|
||||
|
||||
this.emit(EventTypes.SETTINGS_CHANGE);
|
||||
this.emit(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS);
|
||||
this.processSettingsState();
|
||||
}
|
||||
|
||||
handleSettingsSaveRequestError() {
|
||||
@@ -62,7 +117,7 @@ class SettingsStoreClass extends BaseStore {
|
||||
adverb: 'Successfully',
|
||||
action: 'saved',
|
||||
subject: 'settings',
|
||||
id: 'save-torrents-success'
|
||||
id: 'save-settings-success'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -71,11 +126,38 @@ class SettingsStoreClass extends BaseStore {
|
||||
}
|
||||
}
|
||||
|
||||
saveSettings(settings, options) {
|
||||
this.settings[settings.id] = settings.data;
|
||||
SettingsActions.saveSettings(settings, options);
|
||||
processSettingsState() {
|
||||
if (this.fetchStatus.clientSettingsFetched
|
||||
&& this.fetchStatus.floodSettingsFetched) {
|
||||
this.emit(EventTypes.SETTINGS_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
saveFloodSettings(settings, options) {
|
||||
if (!Array.isArray(settings)) {
|
||||
settings = [settings];
|
||||
}
|
||||
|
||||
SettingsActions.saveSettings(settings, options);
|
||||
this.updateLocalSettings(settings, 'floodSettings');
|
||||
this.emit(EventTypes.SETTINGS_CHANGE);
|
||||
}
|
||||
|
||||
saveClientSettings(settings, options) {
|
||||
if (!Array.isArray(settings)) {
|
||||
settings = [settings];
|
||||
}
|
||||
|
||||
ClientActions.saveSettings(settings, options);
|
||||
this.updateLocalSettings(settings, 'clientSettings');
|
||||
this.emit(EventTypes.SETTINGS_CHANGE);
|
||||
}
|
||||
|
||||
updateLocalSettings(settings, settingsType) {
|
||||
settings.forEach((setting) => {
|
||||
this[settingsType][setting.id] = setting.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let SettingsStore = new SettingsStoreClass();
|
||||
@@ -84,18 +166,30 @@ SettingsStore.dispatcherID = AppDispatcher.register((payload) => {
|
||||
const {action, source} = payload;
|
||||
|
||||
switch (action.type) {
|
||||
case ActionTypes.SETTINGS_FETCH_REQUEST_SUCCESS:
|
||||
SettingsStore.handleSettingsFetchSuccess(action.data);
|
||||
case ActionTypes.CLIENT_SETTINGS_FETCH_REQUEST_ERROR:
|
||||
SettingsStore.handleClientSettingsFetchError(action.error);
|
||||
break;
|
||||
case ActionTypes.CLIENT_SETTINGS_FETCH_REQUEST_SUCCESS:
|
||||
SettingsStore.handleClientSettingsFetchSuccess(action.data);
|
||||
break;
|
||||
case ActionTypes.SETTINGS_FETCH_REQUEST_ERROR:
|
||||
SettingsStore.handleSettingsFetchError(action.error);
|
||||
break;
|
||||
case ActionTypes.SETTINGS_FETCH_REQUEST_SUCCESS:
|
||||
SettingsStore.handleSettingsFetchSuccess(action.data);
|
||||
break;
|
||||
case ActionTypes.SETTINGS_SAVE_REQUEST_ERROR:
|
||||
SettingsStore.handleSettingsSaveRequestError(action.error);
|
||||
break;
|
||||
case ActionTypes.SETTINGS_SAVE_REQUEST_SUCCESS:
|
||||
SettingsStore.handleSettingsSaveRequestSuccess(action.data, action.options);
|
||||
break;
|
||||
case ActionTypes.CLIENT_SETTINGS_SAVE_ERROR:
|
||||
SettingsStore.handleClientSettingsSaveRequestError(action.error);
|
||||
break;
|
||||
case ActionTypes.CLIENT_SETTINGS_SAVE_SUCCESS:
|
||||
SettingsStore.handleClientSettingsSaveRequestSuccess(action.data, action.options);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class TorrentFilterStoreClass extends BaseStore {
|
||||
this.searchFilter = null;
|
||||
this.statusFilter = 'all';
|
||||
this.trackerFilter = 'all';
|
||||
this.sortTorrentsBy = SettingsStore.getSettings('sortTorrents');
|
||||
this.sortTorrentsBy = SettingsStore.getFloodSettings('sortTorrents');
|
||||
}
|
||||
|
||||
fetchTorrentStatusCount() {
|
||||
|
||||
@@ -104,7 +104,7 @@ class TorrentStoreClass extends BaseStore {
|
||||
handleAddTorrentSuccess(response) {
|
||||
this.emit(EventTypes.CLIENT_ADD_TORRENT_SUCCESS);
|
||||
|
||||
SettingsStore.saveSettings({
|
||||
SettingsStore.saveFloodSettings({
|
||||
id: 'torrentDestination',
|
||||
data: response.destination
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
let util = require('util');
|
||||
|
||||
let clientUtil = require('../util/clientUtil');
|
||||
let fs = require('fs');
|
||||
let mv = require('mv');
|
||||
let path = require('path');
|
||||
let util = require('util');
|
||||
|
||||
let clientSettingsMap = require('../../shared/constants/clientSettingsMap');
|
||||
let clientUtil = require('../util/clientUtil');
|
||||
let rTorrentPropMap = require('../util/rTorrentPropMap');
|
||||
let scgi = require('../util/scgi');
|
||||
let stringUtil = require('../../shared/util/stringUtil');
|
||||
@@ -163,6 +164,27 @@ class ClientRequest {
|
||||
);
|
||||
}
|
||||
|
||||
fetchSettingsMethodCall(options) {
|
||||
let requestedSettings = [];
|
||||
|
||||
if (options.requestedSettings) {
|
||||
requestedSettings = options.requestedSettings;
|
||||
} else {
|
||||
requestedSettings = clientSettingsMap.defaults.map((settingsKey) => {
|
||||
return clientSettingsMap[settingsKey];
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure client's response gets mapped to the correct requested property.
|
||||
if (options.setPropertiesArr) {
|
||||
options.setPropertiesArr(requestedSettings);
|
||||
}
|
||||
|
||||
requestedSettings.forEach((settingsKey) => {
|
||||
this.requests.push(this.getMethodCall(settingsKey));
|
||||
});
|
||||
}
|
||||
|
||||
getTorrentDetailsMethodCall(options) {
|
||||
var peerParams = [options.hash, ''].concat(options.peerProps);
|
||||
var fileParams = [options.hash, ''].concat(options.fileProps);
|
||||
@@ -251,6 +273,16 @@ class ClientRequest {
|
||||
});
|
||||
}
|
||||
|
||||
setSettingsMethodCall(options) {
|
||||
console.log(options);
|
||||
let settings = this.getEnsuredArray(options.settings);
|
||||
|
||||
settings.forEach((setting) => {
|
||||
this.requests.push(this.getMethodCall(`${clientSettingsMap[setting.id]}.set`,
|
||||
['', setting.data]));
|
||||
});
|
||||
}
|
||||
|
||||
setThrottleMethodCall(options) {
|
||||
let methodName = 'throttle.global_down.max_rate.set';
|
||||
if (options.direction === 'upload') {
|
||||
|
||||
@@ -4,6 +4,7 @@ let fs = require('fs');
|
||||
let util = require('util');
|
||||
|
||||
let clientResponseUtil = require('../util/clientResponseUtil');
|
||||
let clientSettingsMap = require('../../shared/constants/clientSettingsMap');
|
||||
let ClientRequest = require('./ClientRequest');
|
||||
let clientUtil = require('../util/clientUtil');
|
||||
let propsMap = require('../../shared/constants/propsMap');
|
||||
@@ -61,6 +62,33 @@ var client = {
|
||||
request.send();
|
||||
},
|
||||
|
||||
getSettings: (options, callback) => {
|
||||
let properties = [];
|
||||
let request = new ClientRequest();
|
||||
let response = {};
|
||||
|
||||
request.add('fetchSettings', {
|
||||
options,
|
||||
setPropertiesArr: (propertiesArr) => {
|
||||
properties = propertiesArr;
|
||||
}
|
||||
});
|
||||
|
||||
request.postProcess((data) => {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
data.forEach((datum, index) => {
|
||||
response[clientSettingsMap[properties[index]]] = datum[0];
|
||||
});
|
||||
|
||||
return response;
|
||||
});
|
||||
request.onComplete(callback);
|
||||
request.send();
|
||||
},
|
||||
|
||||
getTorrentStatusCount: (callback) => {
|
||||
callback(_statusCount);
|
||||
},
|
||||
@@ -172,6 +200,19 @@ var client = {
|
||||
request.send();
|
||||
},
|
||||
|
||||
setSettings: (payloads, callback) => {
|
||||
let request = new ClientRequest();
|
||||
|
||||
if (payloads.length === 0) {
|
||||
callback({});
|
||||
return;
|
||||
}
|
||||
|
||||
request.add('setSettings', {settings: payloads});
|
||||
request.onComplete(callback);
|
||||
request.send();
|
||||
},
|
||||
|
||||
setSpeedLimits: (data, callback) => {
|
||||
let request = new ClientRequest();
|
||||
|
||||
|
||||
@@ -24,23 +24,28 @@ router.post('/add-files', upload.array('torrents'), function(req, res, next) {
|
||||
client.addFiles(req, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.get('/settings', function(req, res, next) {
|
||||
client.getSettings(req.query, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.patch('/settings', function(req, res, next) {
|
||||
client.setSettings(req.body, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.put('/settings/speed-limits', function(req, res, next) {
|
||||
client.setSpeedLimits(req.body, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.post('/start', function(req, res, next) {
|
||||
var hashes = req.body.hashes;
|
||||
client.startTorrent(hashes, ajaxUtil.getResponseFn(res));
|
||||
client.startTorrent(req.body.hashes, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.post('/stop', function(req, res, next) {
|
||||
var hashes = req.body.hashes;
|
||||
client.stopTorrent(hashes, ajaxUtil.getResponseFn(res));
|
||||
client.stopTorrent(req.body.hashes, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.post('/torrent-details', function(req, res, next) {
|
||||
var hash = req.body.hash;
|
||||
client.getTorrentDetails(hash, ajaxUtil.getResponseFn(res));
|
||||
client.getTorrentDetails(req.body.hash, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.get('/torrents', function(req, res, next) {
|
||||
@@ -59,6 +64,10 @@ router.post('/torrents/move', function(req, res, next) {
|
||||
client.moveTorrents(req.body, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.post('/torrents/delete', function(req, res, next) {
|
||||
client.deleteTorrents(req.body.hash, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.get('/torrents/status-count', function(req, res, next) {
|
||||
client.getTorrentStatusCount(ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
@@ -67,11 +76,6 @@ router.get('/torrents/tracker-count', function(req, res, next) {
|
||||
client.getTorrentTrackerCount(ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.post('/torrents/delete', function(req, res, next) {
|
||||
var hash = req.body.hash;
|
||||
client.deleteTorrents(hash, ajaxUtil.getResponseFn(res));
|
||||
});
|
||||
|
||||
router.get('/methods.json', function(req, res, next) {
|
||||
var type = req.query.type;
|
||||
var args = req.query.args;
|
||||
|
||||
105
shared/constants/clientSettingsMap.js
Normal file
105
shared/constants/clientSettingsMap.js
Normal file
@@ -0,0 +1,105 @@
|
||||
'use strict';
|
||||
|
||||
let objectUtil = require('../util/objectUtil');
|
||||
|
||||
const clientSettingsMap = objectUtil.reflect({
|
||||
dhtPort: 'dht.port',
|
||||
dhtStatus: 'dht.statistics',
|
||||
directoryDefault: 'directory.default',
|
||||
maxFileSize: 'system.file.max_size',
|
||||
networkBindAddress: 'network.bind_address',
|
||||
networkHttpCert: 'network.http.cacert',
|
||||
networkHttpMaxOpen: 'network.http.max_open',
|
||||
networkHttpPath: 'network.http.capath',
|
||||
networkHttpProxy: 'network.http.proxy_address',
|
||||
networkLocalAddress: 'network.local_address',
|
||||
networkMaxOpenFiles: 'network.max_open_files',
|
||||
networkMaxOpenSockets: 'network.max_open_sockets',
|
||||
networkPortOpen: 'network.port_open',
|
||||
networkPortRandom: 'network.port_random',
|
||||
networkPortRange: 'network.port_range',
|
||||
networkReceiveBufferSize: 'network.receive_buffer.size',
|
||||
networkScgiDontRoute: 'network.scgi.dont_route',
|
||||
networkSendBufferSize: 'network.send_buffer.size',
|
||||
piecesHashOnCompletion: 'pieces.hash.on_completion',
|
||||
piecesMemoryMax: 'pieces.memory.max',
|
||||
piecesPreloadMinRate: 'pieces.preload.min_rate',
|
||||
piecesPreloadMinSize: 'pieces.preload.min_size',
|
||||
piecesPreloadType: 'pieces.preload.type',
|
||||
piecesSyncAlwaysSafe: 'pieces.sync.always_safe',
|
||||
piecesSyncTimeout: 'pieces.sync.timeout',
|
||||
piecesSyncTimeoutSafe: 'pieces.sync.timeout_safe',
|
||||
protocolPex: 'protocol.pex',
|
||||
sessionOnCompletion: 'session.on_completion',
|
||||
sessionPath: 'session.path',
|
||||
sessionUseLock: 'session.use_lock',
|
||||
systemFileSplitSize: 'system.file.split_size',
|
||||
systemFileSplitSuffix: 'system.file.split_suffix',
|
||||
throttleDownMax: 'throttle.global_down.max_rate',
|
||||
throttleGlobalUpMax: 'throttle.global_up.max_rate',
|
||||
throttleMaxDownloadsDiv: 'throttle.max_downloads.div',
|
||||
throttleMaxDownloadsGlobal: 'throttle.max_downloads.global',
|
||||
throttleMaxPeersNormal: 'throttle.max_peers.normal',
|
||||
throttleMaxPeersSeed: 'throttle.max_peers.seed',
|
||||
throttleMaxDownloads: 'throttle.max_downloads',
|
||||
throttleMaxDownloadsDiv: 'throttle.max_downloads.div',
|
||||
throttleMaxDownloadsGlobal: 'throttle.max_downloads.global',
|
||||
throttleMaxUploads: 'throttle.max_uploads',
|
||||
throttleMaxUploadsDiv: 'throttle.max_uploads.div',
|
||||
throttleMaxUploadsGlobal: 'throttle.max_uploads.global',
|
||||
throttleMinPeersNormal: 'throttle.min_peers.normal',
|
||||
throttleMinPeersSeed: 'throttle.min_peers.seed',
|
||||
trackersNumWant: 'trackers.numwant',
|
||||
trackersUseUdp: 'trackers.use_udp'
|
||||
});
|
||||
|
||||
clientSettingsMap.defaults = [
|
||||
'dhtPort',
|
||||
'dhtStatus',
|
||||
'directoryDefault',
|
||||
'maxFileSize',
|
||||
'networkBindAddress',
|
||||
'networkHttpCert',
|
||||
'networkHttpMaxOpen',
|
||||
'networkHttpPath',
|
||||
'networkHttpProxy',
|
||||
'networkLocalAddress',
|
||||
'networkMaxOpenFiles',
|
||||
'networkMaxOpenSockets',
|
||||
'networkPortOpen',
|
||||
'networkPortRandom',
|
||||
'networkPortRange',
|
||||
'networkReceiveBufferSize',
|
||||
'networkScgiDontRoute',
|
||||
'networkSendBufferSize',
|
||||
'piecesHashOnCompletion',
|
||||
'piecesMemoryMax',
|
||||
'piecesPreloadMinRate',
|
||||
'piecesPreloadMinSize',
|
||||
'piecesPreloadType',
|
||||
'piecesSyncAlwaysSafe',
|
||||
'piecesSyncTimeout',
|
||||
'piecesSyncTimeoutSafe',
|
||||
'protocolPex',
|
||||
'sessionOnCompletion',
|
||||
'sessionPath',
|
||||
'sessionUseLock',
|
||||
'systemFileSplitSize',
|
||||
'systemFileSplitSuffix',
|
||||
'throttleDownMax',
|
||||
'throttleGlobalUpMax',
|
||||
'throttleMaxDownloadsDiv',
|
||||
'throttleMaxDownloadsGlobal',
|
||||
'throttleMaxPeersNormal',
|
||||
'throttleMaxPeersSeed',
|
||||
'throttleMaxDownloads',
|
||||
'throttleMaxUploads',
|
||||
'throttleMaxUploadsDiv',
|
||||
'throttleMaxUploadsGlobal',
|
||||
'throttleMinPeersNormal',
|
||||
'throttleMinPeersSeed',
|
||||
'trackersNumWant',
|
||||
'trackersUseUdp'
|
||||
];
|
||||
|
||||
module.exports = clientSettingsMap;
|
||||
13
shared/util/objectUtil.js
Normal file
13
shared/util/objectUtil.js
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
let objectUtil = {
|
||||
reflect: (hash) => {
|
||||
return Object.keys(hash).reduce((memo, key) => {
|
||||
memo[key] = hash[key];
|
||||
memo[hash[key]] = key;
|
||||
return memo;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = objectUtil;
|
||||
Reference in New Issue
Block a user