Handle default settings better

This commit is contained in:
John Furrow
2016-06-01 22:12:43 -07:00
parent 15130809f8
commit ca01c3869e
18 changed files with 144 additions and 212 deletions
+1 -9
View File
@@ -89,17 +89,9 @@ $modal--body--foreground: desaturate(lighten($foreground, 20%), 10%);
.icon {
height: 16px;
margin: 0 $spacing-unit * 1/3;
margin: 0 $spacing-unit * 1/3 0 0;
vertical-align: middle;
width: 16px;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
}
}
@@ -65,6 +65,10 @@ $notification--foreground: #8fa2b2;
}
}
& + .notification {
margin-top: $spacing-unit * 2/5;
}
&__content {
flex: 1 1 auto;
}
@@ -24,7 +24,7 @@ const SettingsActions = {
});
},
saveSettings: (settings) => {
saveSettings: (settings, options = {}) => {
return axios.patch('/client/settings', settings)
.then((json = {}) => {
return json.data;
@@ -32,11 +32,12 @@ const SettingsActions = {
.then((data) => {
AppDispatcher.dispatchServerAction({
type: ActionTypes.SETTINGS_SAVE_REQUEST_SUCCESS,
data
data,
options
});
})
.catch((error) => {
console.error(error);
console.trace(error);
AppDispatcher.dispatchServerAction({
type: ActionTypes.SETTINGS_SAVE_REQUEST_ERROR,
error
+4 -34
View File
@@ -27,34 +27,10 @@ const UIActions = {
},
dismissModal: () => {
// TODO: Remove this try..catch.
try {
AppDispatcher.dispatchUIAction({
type: ActionTypes.UI_DISPLAY_MODAL,
data: null
});
} catch (err) {
console.error(err);
}
},
fetchSortProps: () => {
return axios.get('/ui/sort-props')
.then((json = {}) => {
return json.data;
})
.then((data) => {
AppDispatcher.dispatchServerAction({
type: ActionTypes.UI_SORT_PROPS_REQUEST_SUCCESS,
data
});
})
.catch((error) => {
AppDispatcher.dispatchServerAction({
type: ActionTypes.UI_SORT_PROPS_REQUEST_ERROR,
error
});
});
AppDispatcher.dispatchUIAction({
type: ActionTypes.UI_DISPLAY_MODAL,
data: null
});
},
handleDetailsClick: (data) => {
@@ -93,12 +69,6 @@ const UIActions = {
},
setTorrentsSort: (data) => {
axios
.post('/ui/sort-props', data)
.catch(() => {
console.log(error);
});
AppDispatcher.dispatchUIAction({
type: ActionTypes.UI_SET_TORRENT_SORT,
data
@@ -2,14 +2,16 @@ import classnames from 'classnames';
import React from 'react';
import EventTypes from '../../constants/EventTypes';
import LoadingIndicatorDots from '../icons/LoadingIndicatorDots';
import Modal from './Modal';
import SettingsSpeedLimit from './SettingsSpeedLimit';
import SettingsStore from '../../stores/SettingsStore';
const METHODS_TO_BIND = [
'handleSettingsChange',
'handleSaveSettingsClick',
'handleSettingsFetchRequestSuccess'
'handleSaveSettingsError',
'handleSettingsChange',
'handleSettingsStoreChange'
];
export default class SettingsModal extends React.Component {
@@ -17,12 +19,8 @@ export default class SettingsModal extends React.Component {
super();
this.state = {
settings: {
speedLimits: {
download: null,
upload: null
}
}
isSavingSettings: false,
settings: SettingsStore.getSettings()
};
METHODS_TO_BIND.forEach((method) => {
@@ -31,21 +29,25 @@ export default class SettingsModal extends React.Component {
}
componentDidMount() {
SettingsStore.listen(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS, this.handleSettingsFetchRequestSuccess);
SettingsStore.listen(EventTypes.SETTINGS_CHANGE,
this.handleSettingsStoreChange);
SettingsStore.listen(EventTypes.SETTINGS_SAVE_REQUEST_ERROR,
this.handleSaveSettingsError);
SettingsStore.fetchSettings('speedLimits');
}
componentWillUnmount() {
SettingsStore.unlisten(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS, this.handleSettingsFetchRequestSuccess);
SettingsStore.unlisten(EventTypes.SETTINGS_CHANGE,
this.handleSettingsStoreChange);
}
getActions() {
let icon = null;
let primaryButtonText = 'Add Torrent';
let primaryButtonText = 'Save Settings';
if (this.state.isAddingTorrents) {
if (this.state.isSavingSettings) {
icon = <LoadingIndicatorDots viewBox="0 0 32 32" />;
primaryButtonText = 'Adding...';
primaryButtonText = 'Saving...';
}
return [
@@ -57,7 +59,13 @@ export default class SettingsModal extends React.Component {
},
{
clickHandler: this.handleSaveSettingsClick,
content: 'Save Settings',
content: (
<span>
{icon}
{primaryButtonText}
</span>
),
supplementalClassName: icon != null ? 'has-icon' : '',
triggerDismiss: false,
type: 'primary'
}
@@ -65,6 +73,8 @@ export default class SettingsModal extends React.Component {
}
handleSaveSettingsClick() {
this.setState({isSavingSettings: true});
let settingsToSave = Object.keys(this.state.settings).map((settingsKey) => {
return {
id: settingsKey,
@@ -72,14 +82,18 @@ export default class SettingsModal extends React.Component {
};
});
SettingsStore.saveSettings(settingsToSave);
SettingsStore.saveSettings(settingsToSave, {dismissModal: true, notify: true});
}
handleSaveSettingsError() {
this.setState({isSavingSettings: false});
}
handleSettingsFetchRequestError(error) {
console.log(error);
}
handleSettingsFetchRequestSuccess() {
handleSettingsStoreChange() {
this.setState({
settings: SettingsStore.getSettings()
});
@@ -55,7 +55,7 @@ export default class SettingsSpeedLimit extends React.Component {
getDownloadValue() {
let displayedValue = this.state.downloadValue;
if (displayedValue == null) {
if (displayedValue == null && this.props.settings.speedLimits != null) {
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.download);
}
@@ -65,7 +65,7 @@ export default class SettingsSpeedLimit extends React.Component {
getUploadValue() {
let displayedValue = this.state.uploadValue;
if (displayedValue == null) {
if (displayedValue == null && this.props.settings.speedLimits != null) {
displayedValue = this.processSpeedsForDisplay(this.props.settings.speedLimits.upload);
}
@@ -20,7 +20,7 @@ export default class Notification extends React.Component {
icon = <CircleExclamationIcon />;
}
if (this.props.count !== 1) {
if (!!this.props.accumulation && this.props.count !== 1) {
countText = (
<span className="notification__count">
{this.props.count}
@@ -10,14 +10,13 @@ import SettingsStore from '../../stores/SettingsStore';
import TransferDataStore from '../../stores/TransferDataStore';
const METHODS_TO_BIND = ['handleSettingsFetchRequestSuccess', 'onTransferDataRequestSuccess'];
const DEFAULT_SPEEDS = [1024, 10240, 102400, 512000, 1048576, 2097152, 5242880, 10485760, 0];
class SpeedLimitDropdown extends React.Component {
constructor() {
super();
this.state = {
speedLimits: {},
speedLimits: SettingsStore.getSettings('speedLimits'),
throttle: null
};
@@ -27,27 +26,23 @@ class SpeedLimitDropdown extends React.Component {
}
componentDidMount() {
SettingsStore.listen(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS, this.handleSettingsFetchRequestSuccess);
SettingsStore.listen(EventTypes.SETTINGS_CHANGE,
this.handleSettingsFetchRequestSuccess);
TransferDataStore.listen(EventTypes.CLIENT_TRANSFER_DATA_REQUEST_SUCCESS,
this.onTransferDataRequestSuccess);
SettingsStore.fetchSettings('speedLimits');
TransferDataStore.listen(
EventTypes.CLIENT_TRANSFER_DATA_REQUEST_SUCCESS,
this.onTransferDataRequestSuccess
);
TransferDataStore.fetchTransferData();
}
componentWillUnmount() {
SettingsStore.unlisten(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS, this.handleSettingsFetchRequestSuccess);
TransferDataStore.unlisten(
EventTypes.CLIENT_TRANSFER_DATA_REQUEST_SUCCESS,
this.onTransferDataRequestSuccess
);
SettingsStore.unlisten(EventTypes.SETTINGS_CHANGE,
this.handleSettingsFetchRequestSuccess);
TransferDataStore.unlisten(EventTypes.CLIENT_TRANSFER_DATA_REQUEST_SUCCESS,
this.onTransferDataRequestSuccess);
}
onTransferDataRequestSuccess() {
this.setState({
throttle: TransferDataStore.getThrottles({latest: true})
});
this.setState({throttle: TransferDataStore.getThrottles({latest: true})});
}
getDropdownHeader() {
@@ -82,7 +77,7 @@ class SpeedLimitDropdown extends React.Component {
let insertCurrentThrottle = true;
let currentThrottle = this.state.throttle;
let speeds = this.state.speedLimits[property] || DEFAULT_SPEEDS;
let speeds = this.state.speedLimits[property];
let items = speeds.map((bytes) => {
let selected = false;
@@ -5,6 +5,7 @@ import Add from '../icons/Add';
import EventTypes from '../../constants/EventTypes';
import PauseIcon from '../icons/PauseIcon';
import Remove from '../icons/Remove';
import SettingsStore from '../../stores/SettingsStore';
import SortDropdown from './SortDropdown';
import StartIcon from '../icons/StartIcon';
import StopIcon from '../icons/StopIcon';
@@ -28,7 +29,7 @@ export default class ActionBar extends React.Component {
super();
this.state = {
sortBy: TorrentFilterStore.getTorrentsSort()
sortBy: SettingsStore.getSettings('sortTorrents')
};
METHODS_TO_BIND.forEach((method) => {
@@ -37,12 +38,13 @@ export default class ActionBar extends React.Component {
}
componentDidMount() {
TorrentFilterStore.fetchSortProps();
TorrentFilterStore.listen(EventTypes.UI_TORRENTS_SORT_CHANGE, this.onSortChange);
this.onSortChange();
SettingsStore.listen(EventTypes.SETTINGS_CHANGE, this.onSortChange);
SettingsStore.fetchSettings('sortTorrents');
}
componentWillUnmount() {
TorrentFilterStore.unlisten(EventTypes.UI_TORRENTS_SORT_CHANGE, this.onSortChange);
SettingsStore.unlisten(EventTypes.SETTINGS_CHANGE, this.onSortChange);
}
handleAddTorrents() {
@@ -97,6 +99,7 @@ export default class ActionBar extends React.Component {
}
handleSortChange(sortBy) {
SettingsStore.saveSettings({id: 'sortTorrents', data: sortBy});
UIActions.setTorrentsSort(sortBy);
}
@@ -109,9 +112,9 @@ export default class ActionBar extends React.Component {
}
onSortChange() {
this.setState({
sortBy: TorrentFilterStore.getTorrentsSort()
});
let sortBy = SettingsStore.getSettings('sortTorrents');
TorrentFilterStore.setTorrentsSort(sortBy);
this.setState({sortBy});
}
render() {
@@ -3,7 +3,6 @@ import CSSTransitionGroup from 'react-addons-css-transition-group';
import React from 'react';
import Dropdown from '../forms/Dropdown';
import UIActions from '../../actions/UIActions';
const METHODS_TO_BIND = [
'getDropdownHeader',
@@ -105,6 +104,10 @@ export default class SortDropdown extends React.Component {
}
render() {
if (this.props.selectedItem == null) {
return null;
}
return (
<Dropdown
handleItemSelect={this.handleItemSelect}
@@ -17,8 +17,11 @@ const EventTypes = {
CLIENT_TRANSFER_HISTORY_REQUEST_SUCCESS: 'CLIENT_TRANSFER_HISTORY_REQUEST_SUCCESS',
CLIENT_TRANSFER_HISTORY_REQUEST_ERROR: 'CLIENT_TRANSFER_HISTORY_REQUEST_ERROR',
NOTIFICATIONS_CHANGE: 'NOTIFICATIONS_CHANGE',
SETTINGS_FETCH_REQUEST_SUCCESS: 'SETTINGS_FETCH_REQUEST_SUCCESS',
SETTINGS_CHANGE: 'SETTINGS_CHANGE',
SETTINGS_SAVE_REQUEST_ERROR: 'SETTINGS_SAVE_REQUEST_ERROR',
SETTINGS_SAVE_REQUEST_SUCCESS: 'SETTINGS_SAVE_REQUEST_SUCCESS',
SETTINGS_FETCH_REQUEST_ERROR: 'SETTINGS_FETCH_REQUEST_ERROR',
SETTINGS_FETCH_REQUEST_SUCCESS: 'SETTINGS_FETCH_REQUEST_SUCCESS',
UI_CONTEXT_MENU_CHANGE: 'UI_CONTEXT_MENU_CHANGE',
UI_DEPENDENCIES_LOADED: 'UI_DEPENDENCIES_LOADED',
UI_MODAL_CHANGE: 'UI_MODAL_CHANGE',
+55 -7
View File
@@ -2,11 +2,27 @@ import ActionTypes from '../constants/ActionTypes';
import AppDispatcher from '../dispatcher/AppDispatcher';
import BaseStore from './BaseStore';
import EventTypes from '../constants/EventTypes';
import NotificationStore from './NotificationStore';
import SettingsActions from '../actions/SettingsActions';
import UIStore from './UIStore';
class SettingsStoreClass extends BaseStore {
constructor() {
super();
// Default settings are overridden by settings stored in database.
this.settings = {
sortTorrents: {
direction: 'desc',
displayName: 'Date Added',
property: 'sortBy',
value: 'added'
},
speedLimits: {
download: [1024, 10240, 102400, 512000, 1048576, 2097152, 5242880, 10485760, 0],
upload: [1024, 10240, 102400, 512000, 1048576, 2097152, 5242880, 10485760, 0]
}
};
}
fetchSettings(property) {
@@ -21,18 +37,44 @@ class SettingsStoreClass extends BaseStore {
return this.settings;
}
handleSettingsFetchSuccess(settings) {
this.settings = settings;
this.emit(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS);
}
handleSettingsFetchError(error) {
this.emit(EventTypes.SETTINGS_FETCH_REQUEST_ERROR);
}
saveSettings(settings) {
handleSettingsFetchSuccess(settings) {
Object.keys(settings).forEach((property) => {
this.settings[property] = settings[property];
});
this.emit(EventTypes.SETTINGS_CHANGE);
this.emit(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS);
}
handleSettingsSaveRequestError() {
this.emit(EventTypes.SETTINGS_SAVE_REQUEST_ERROR);
}
handleSettingsSaveRequestSuccess(data, options = {}) {
this.emit(EventTypes.SETTINGS_SAVE_REQUEST_SUCCESS);
if (options.notify) {
NotificationStore.add({
adverb: 'Successfully',
action: 'saved',
subject: 'settings',
id: 'save-torrents-success'
});
}
if (options.dismissModal) {
UIStore.dismissModal();
}
}
saveSettings(settings, options) {
this.settings[settings.id] = settings.data;
SettingsActions.saveSettings(settings);
SettingsActions.saveSettings(settings, options);
this.emit(EventTypes.SETTINGS_CHANGE);
}
}
@@ -48,6 +90,12 @@ SettingsStore.dispatcherID = AppDispatcher.register((payload) => {
case ActionTypes.SETTINGS_FETCH_REQUEST_ERROR:
SettingsStore.handleSettingsFetchError(action.error);
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;
}
});
@@ -2,6 +2,7 @@ import ActionTypes from '../constants/ActionTypes';
import AppDispatcher from '../dispatcher/AppDispatcher';
import BaseStore from './BaseStore';
import EventTypes from '../constants/EventTypes';
import SettingsStore from './SettingsStore';
import TorrentActions from '../actions/TorrentActions';
import UIActions from '../actions/UIActions';
@@ -12,16 +13,7 @@ class TorrentFilterStoreClass extends BaseStore {
this.searchFilter = null;
this.statusFilter = 'all';
this.trackerFilter = 'all';
this.sortTorrentsBy = {
direction: 'desc',
displayName: 'Date Added',
property: 'sortBy',
value: 'added'
};
}
fetchSortProps() {
UIActions.fetchSortProps();
this.sortTorrentsBy = SettingsStore.getSettings('sortTorrents');
}
fetchTorrentStatusCount() {
+4 -1
View File
@@ -104,7 +104,10 @@ class TorrentStoreClass extends BaseStore {
handleAddTorrentSuccess(response) {
this.emit(EventTypes.CLIENT_ADD_TORRENT_SUCCESS);
SettingsStore.saveSettings({id: 'torrentDestination', data: response.destination});
SettingsStore.saveSettings({
id: 'torrentDestination',
data: response.destination
});
NotificationStore.add({
adverb: 'Successfully',
+6 -2
View File
@@ -17,6 +17,10 @@ class UIStoreClass extends BaseStore {
this.torrentDetailsHash = null;
}
dismissModal() {
this.setActiveModal(null);
}
getActiveContextMenu() {
return this.activeContextMenu;
}
@@ -93,9 +97,9 @@ UIStore.dispatcherID = AppDispatcher.register((payload) => {
case ActionTypes.UI_DISPLAY_MODAL:
UIStore.setActiveModal(action.data);
break;
case ActionTypes.CLIENT_MOVE_TORRENTS_SUCCESS:
case ActionTypes.CLIENT_ADD_TORRENT_SUCCESS:
UIStore.setActiveModal(null);
case ActionTypes.CLIENT_MOVE_TORRENTS_SUCCESS:
UIStore.dismissModal();
break;
case ActionTypes.UI_DISPLAY_CONTEXT_MENU:
UIStore.setActiveContextMenu(action.data);
+2 -4
View File
@@ -9,8 +9,7 @@ var logger = require('morgan');
var path = require('path');
var clientRoutes = require('./routes/client');
var uiRoutes = require('./routes/ui');
var routes = require('./routes/index');
var mainRoutes = require('./routes/index');
var app = express();
@@ -36,9 +35,8 @@ app.use((req, res, next) => {
next();
});
app.use('/', routes);
app.use('/', mainRoutes);
app.use('/client', clientRoutes);
app.use('/ui', uiRoutes);
// catch 404 and forward to error handler
app.use((req, res, next) => {
-62
View File
@@ -1,62 +0,0 @@
'use strict';
let Datastore = require('nedb');
let config = require('../../config');
let uiDB = new Datastore({
autoload: true,
filename: `${config.dbPath}uiSettings.db`
});
uiDB.persistence.setAutocompactionInterval(config.dbCleanInterval);
let getDbResponseHandler = (callback) => {
return (error, docs) => {
if (error) {
callback(null, error);
return;
}
if (Array.isArray(docs)) {
callback(docs[0]);
return;
}
callback(docs);
return;
};
};
let uiSettings = {
get: (type, callback) => {
uiDB.find({type}, getDbResponseHandler(callback));
},
set: (payload, callback) => {
let newLocationData = Object.assign({}, {type: payload.type}, {data: payload.data});
uiDB.update({type: payload.type, data: payload.data}, getDbResponseHandler(callback));
},
getLatestTorrentLocation: (callback) => {
try {
uiDB.find({type: 'location'}, getDbResponseHandler(callback));
} catch (e) { console.log(e); }
},
getSortProps: (callback) => {
uiDB.find({type: 'sort'}, getDbResponseHandler(callback));
},
setLatestTorrentLocation: (data, callback) => {
let newLocationData = Object.assign({}, {type: 'location'}, {path: data.destination});
uiDB.update({type: 'location'}, newLocationData, {upsert: true}, getDbResponseHandler(callback));
},
setSortProps: (sortProps, callback) => {
let newSortPropData = Object.assign({}, {type: 'sort'}, sortProps);
uiDB.update({type: 'sort'}, newSortPropData, {upsert: true}, getDbResponseHandler(callback));
}
}
module.exports = uiSettings;
-36
View File
@@ -1,36 +0,0 @@
'use strict';
let express = require('express');
let router = express.Router();
let xmlrpc = require('xmlrpc');
let ajaxUtil = require('../util/ajaxUtil');
let client = require('../models/client');
let history = require('../models/history');
let uiSettings = require('../models/uiSettings');
router.get('/settings', (req, res, next) => {
uiSettings.get(req.body.type, ajaxUtil.getResponseFn(res));
});
router.post('/settings', (req, res, next) => {
uiSettings.set(req.body, ajaxUtil.getResponseFn(res));
});
router.post('/sort-props', (req, res, next) => {
uiSettings.setSortProps(req.body, ajaxUtil.getResponseFn(res));
});
router.get('/sort-props', (req, res, next) => {
uiSettings.getSortProps(ajaxUtil.getResponseFn(res));
});
router.get('/torrent-location', (req, res, next) => {
uiSettings.getLatestTorrentLocation(ajaxUtil.getResponseFn(res));
});
router.post('/torrent-location', (req, res, next) => {
uiSettings.setLatestTorrentLocation(req.body, ajaxUtil.getResponseFn(res));
});
module.exports = router;