diff --git a/client/sass/base/_form-elements.scss b/client/sass/base/_form-elements.scss
index c4234f8a..5526e768 100644
--- a/client/sass/base/_form-elements.scss
+++ b/client/sass/base/_form-elements.scss
@@ -1,5 +1,7 @@
$form--label--foreground: desaturate(lighten(#5c7087, 3%), 3%);
+$form--element--border-radius: 4px;
+
$textbox--background: #242b36;
$textbox--foreground: #5e728c;
$textbox--placeholder: #424d5e;
@@ -76,7 +78,7 @@ $interacative-list--item--icon--fill--hover: rgba(lighten($interacative-list--it
.textbox {
background: $textbox--background;
- border-radius: 4px;
+ border-radius: $form--element--border-radius;
border: 1px solid $textbox--border;
color: $textbox--foreground;
display: block;
@@ -119,7 +121,7 @@ $interacative-list--item--icon--fill--hover: rgba(lighten($interacative-list--it
.button {
background: transparent;
border: none;
- border-radius: 4px;
+ border-radius: $form--element--border-radius;
cursor: pointer;
font-weight: 500;
padding: 8px 22px;
@@ -253,7 +255,7 @@ $interacative-list--item--icon--fill--hover: rgba(lighten($interacative-list--it
.interactive-list {
@extend .textbox;
@extend .textbox.is-fulfilled;
- border-radius: 4px;
+ border-radius: $form--element--border-radius;
color: $interacative-list--item--foreground;
padding: $spacing-unit * 1/2;
@@ -384,6 +386,31 @@ $interacative-list--item--icon--fill--hover: rgba(lighten($interacative-list--it
justify-content: center;
padding-top: $spacing-unit * 3/5;
}
+
+ & > .dropdown {
+
+ & > .dropdown {
+
+ &__trigger {
+
+ .dropdown {
+
+ &__button {
+ @extend .button--deemphasize;
+ border-radius: $form--element--border-radius;
+ transition: background 0.25s, color 0.25s;
+ }
+ }
+ }
+ }
+ }
+
+ .dropdown {
+
+ &__button {
+ padding-top: 7px;
+ }
+ }
}
&__actions {
diff --git a/client/scripts/app.js b/client/scripts/app.js
index d1babe25..6602764d 100644
--- a/client/scripts/app.js
+++ b/client/scripts/app.js
@@ -5,32 +5,74 @@ import ReactDOM from 'react-dom';
import * as i18n from './i18n';
import Application from './components/Layout/Application';
+import EventTypes from './constants/EventTypes';
import Login from './views/Login';
import Register from './views/Register';
import SettingsStore from './stores/SettingsStore';
import TorrentList from './views/TorrentList';
+import UIStore from './stores/UIStore';
+
+let appRoutes = (
+
+
+
+
+
+
+
+
+
+);
+
+const METHODS_TO_BIND = ['handleSettingsChange'];
class FloodApp extends React.Component {
+ constructor() {
+ super();
+
+ this.state = {
+ locale: SettingsStore.getFloodSettings('language')
+ };
+
+ METHODS_TO_BIND.forEach((method) => {
+ this[method] = this[method].bind(this);
+ });
+ }
+
+ componentWillMount() {
+ UIStore.registerDependency('flood-settings');
+ }
+
+ componentDidMount() {
+ SettingsStore.listen(EventTypes.SETTINGS_CHANGE,
+ this.handleSettingsChange);
+ }
+
+ componentWillUnmount() {
+ SettingsStore.unlisten(EventTypes.SETTINGS_CHANGE,
+ this.handleSettingsChange);
+ }
+
+ handleSettingsChange() {
+ if (SettingsStore.getFloodSettings('language') !== this.state.language) {
+ this.setState({locale: SettingsStore.getFloodSettings('language')});
+ }
+
+ UIStore.satisfyDependency('flood-settings');
+ }
+
render() {
+ let {locale} = this.state;
+
return (
-
-
-
-
-
-
-
-
-
+
+ {appRoutes}
+
);
}
}
-let locale = SettingsStore.getFloodSettings('language');
-
ReactDOM.render(
-
-
- ,
+ ,
document.getElementById('app')
);
diff --git a/client/scripts/components/Modals/SettingsModal/UITab.js b/client/scripts/components/Modals/SettingsModal/UITab.js
new file mode 100644
index 00000000..3d81a15c
--- /dev/null
+++ b/client/scripts/components/Modals/SettingsModal/UITab.js
@@ -0,0 +1,93 @@
+import _ from 'lodash';
+import {formatMessage, FormattedMessage, injectIntl} from 'react-intl';
+import React from 'react';
+
+import AuthStore from '../../../stores/AuthStore';
+import Checkbox from '../../General/FormElements/Checkbox';
+import Close from '../../Icons/Close';
+import Dropdown from '../../General/FormElements/Dropdown';
+import EventTypes from '../../../constants/EventTypes';
+import Languages from '../../../constants/Languages';
+import SettingsStore from '../../../stores/SettingsStore';
+import SettingsTab from './SettingsTab';
+
+const METHODS_TO_BIND = [
+ 'handleItemSelect'
+];
+
+class UITab extends SettingsTab {
+ constructor() {
+ super(...arguments);
+
+ this.state = {
+ selectedLanguage: SettingsStore.getFloodSettings('language')
+ };
+
+ METHODS_TO_BIND.forEach((method) => {
+ this[method] = this[method].bind(this);
+ });
+ }
+
+ getDropdownHeader() {
+ return (
+
+
+
+
+
+ );
+ }
+
+ getDropdownMenu() {
+ let items = Object.keys(Languages).map((language) => {
+ return {
+ displayName: this.props.intl.formatMessage(
+ Languages[language]
+ ),
+ selected: this.state.selectedLanguage === language,
+ language
+ };
+ });
+
+ // Dropdown expects an array of arrays.
+ return [items];
+ }
+
+ handleItemSelect(item) {
+ let {language} = item;
+
+ this.setState({selectedLanguage: language});
+ this.props.onSettingsChange({language});
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default injectIntl(UITab);
diff --git a/client/scripts/components/Modals/SettingsModal/index.js b/client/scripts/components/Modals/SettingsModal/index.js
index 45f9325f..3c206006 100644
--- a/client/scripts/components/Modals/SettingsModal/index.js
+++ b/client/scripts/components/Modals/SettingsModal/index.js
@@ -11,6 +11,7 @@ import LoadingIndicatorDots from '../../Icons/LoadingIndicatorDots';
import Modal from '../Modal';
import ResourcesTab from './ResourcesTab';
import SettingsStore from '../../../stores/SettingsStore';
+import UITab from './UITab';
const METHODS_TO_BIND = [
'handleClientSettingsChange',
@@ -213,6 +214,16 @@ class SettingsModal extends React.Component {
id: 'settings.tabs.authentication',
defaultMessage: 'Authentication'
})
+ },
+ ui: {
+ content: UITab,
+ label: this.props.intl.formatMessage({
+ id: 'settings.tabs.userinterface',
+ defaultMessage: 'User Interface'
+ }),
+ props: {
+ onSettingsChange: this.handleFloodSettingsChange
+ }
}
};
diff --git a/client/scripts/constants/Languages.js b/client/scripts/constants/Languages.js
new file mode 100644
index 00000000..fe27fa5b
--- /dev/null
+++ b/client/scripts/constants/Languages.js
@@ -0,0 +1,12 @@
+const Languages = {
+ en: {
+ defaultMessage: 'English',
+ id: 'locale.language.en'
+ },
+ nl: {
+ defaultMessage: 'Nederlands',
+ id: 'locale.language.nl'
+ }
+};
+
+export default Languages;
diff --git a/client/scripts/stores/SettingsStore.js b/client/scripts/stores/SettingsStore.js
index 62dbb059..fd82792d 100644
--- a/client/scripts/stores/SettingsStore.js
+++ b/client/scripts/stores/SettingsStore.js
@@ -93,7 +93,9 @@ class SettingsStoreClass extends BaseStore {
this.fetchStatus.floodSettingsFetched = true;
Object.keys(settings).forEach((property) => {
- this.floodSettings[property] = settings[property];
+ if (settings[property] != null) {
+ this.floodSettings[property] = settings[property];
+ }
});
this.emit(EventTypes.SETTINGS_FETCH_REQUEST_SUCCESS);