Introduce client settings

This commit is contained in:
John Furrow
2016-06-06 21:01:10 -07:00
parent 30d75c124b
commit 02c3b5e4b0
27 changed files with 907 additions and 206 deletions

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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'
}
};

View 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>
);
}
}

View 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>
);
}
}

View 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);
}
}

View File

@@ -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>
);
}
}

View 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>
);
}
}

View File

@@ -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});

View File

@@ -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});
}