aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/actions/settings.js4
-rw-r--r--src/api/LocalApi.js8
-rw-r--r--src/api/server/LocalApi.js19
-rw-r--r--src/components/settings/settings/EditSettingsForm.js6
-rw-r--r--src/config.js10
-rw-r--r--src/containers/layout/AppLayoutContainer.js6
-rw-r--r--src/containers/settings/EditSettingsScreen.js52
-rw-r--r--src/electron/Settings.js39
-rw-r--r--src/electron/ipc-api/settings.js6
-rw-r--r--src/i18n/locales/en-US.json1
-rw-r--r--src/index.js8
-rw-r--r--src/models/Settings.js34
-rw-r--r--src/models/User.js2
-rw-r--r--src/stores/AppStore.js27
-rw-r--r--src/stores/ServicesStore.js36
-rw-r--r--src/stores/SettingsStore.js96
-rw-r--r--src/stores/UIStore.js2
-rw-r--r--src/stores/UserStore.js24
-rw-r--r--src/webview/plugin.js6
19 files changed, 279 insertions, 107 deletions
diff --git a/src/actions/settings.js b/src/actions/settings.js
index 3d53cd674..fd29b798b 100644
--- a/src/actions/settings.js
+++ b/src/actions/settings.js
@@ -2,9 +2,11 @@ import PropTypes from 'prop-types';
2 2
3export default { 3export default {
4 update: { 4 update: {
5 settings: PropTypes.object.isRequired, 5 type: PropTypes.string.isRequired,
6 data: PropTypes.object.isRequired,
6 }, 7 },
7 remove: { 8 remove: {
9 type: PropTypes.string.isRequired,
8 key: PropTypes.string.isRequired, 10 key: PropTypes.string.isRequired,
9 }, 11 },
10}; 12};
diff --git a/src/api/LocalApi.js b/src/api/LocalApi.js
index 59d7d8fa2..741917104 100644
--- a/src/api/LocalApi.js
+++ b/src/api/LocalApi.js
@@ -4,6 +4,14 @@ export default class LocalApi {
4 this.local = local; 4 this.local = local;
5 } 5 }
6 6
7 getAppSettings() {
8 return this.local.getAppSettings();
9 }
10
11 updateAppSettings(data) {
12 return this.local.updateAppSettings(data);
13 }
14
7 getAppCacheSize() { 15 getAppCacheSize() {
8 return this.local.getAppCacheSize(); 16 return this.local.getAppCacheSize();
9 } 17 }
diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js
index 4d2497c61..78deb7aa5 100644
--- a/src/api/server/LocalApi.js
+++ b/src/api/server/LocalApi.js
@@ -1,4 +1,4 @@
1import { remote } from 'electron'; 1import { ipcRenderer, remote } from 'electron';
2import du from 'du'; 2import du from 'du';
3 3
4import { getServicePartitionsDirectory } from '../../helpers/service-helpers.js'; 4import { getServicePartitionsDirectory } from '../../helpers/service-helpers.js';
@@ -8,6 +8,23 @@ const debug = require('debug')('LocalApi');
8const { session } = remote; 8const { session } = remote;
9 9
10export default class LocalApi { 10export default class LocalApi {
11 // Settings
12 getAppSettings() {
13 return new Promise((resolve) => {
14 ipcRenderer.once('appSettings', (event, data) => {
15 debug('LocalApi::getAppSettings resolves', data);
16 resolve(data);
17 });
18
19 ipcRenderer.send('getAppSettings');
20 });
21 }
22
23 async updateAppSettings(data) {
24 debug('LocalApi::updateAppSettings resolves', data);
25 ipcRenderer.send('updateAppSettings', data);
26 }
27
11 // Services 28 // Services
12 async getAppCacheSize() { 29 async getAppCacheSize() {
13 const partitionsDir = getServicePartitionsDirectory(); 30 const partitionsDir = getServicePartitionsDirectory();
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 72aa5a8af..97f535594 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -76,6 +76,10 @@ const messages = defineMessages({
76 id: 'settings.app.currentVersion', 76 id: 'settings.app.currentVersion',
77 defaultMessage: '!!!Current version:', 77 defaultMessage: '!!!Current version:',
78 }, 78 },
79 enableGPUAccelerationInfo: {
80 id: 'settings.app.restartRequired',
81 defaultMessage: '!!!Changes require restart',
82 },
79}); 83});
80 84
81@observer 85@observer
@@ -172,6 +176,8 @@ export default class EditSettingsForm extends Component {
172 {/* Advanced */} 176 {/* Advanced */}
173 <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2> 177 <h2 id="advanced">{intl.formatMessage(messages.headlineAdvanced)}</h2>
174 <Toggle field={form.$('enableSpellchecking')} /> 178 <Toggle field={form.$('enableSpellchecking')} />
179 <Toggle field={form.$('enableGPUAcceleration')} />
180 <p className="settings__help">{intl.formatMessage(messages.enableGPUAccelerationInfo)}</p>
175 {/* <Select field={form.$('spellcheckingLanguage')} /> */} 181 {/* <Select field={form.$('spellcheckingLanguage')} /> */}
176 <div className="settings__settings-group"> 182 <div className="settings__settings-group">
177 <h3> 183 <h3>
diff --git a/src/config.js b/src/config.js
index e66594c59..77fa92eca 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1,3 +1,8 @@
1import electron from 'electron';
2import path from 'path';
3
4const app = process.type === 'renderer' ? electron.remote.app : electron.app;
5
1export const CHECK_INTERVAL = 1000 * 3600; // How often should we perform checks 6export const CHECK_INTERVAL = 1000 * 3600; // How often should we perform checks
2export const LOCAL_API = 'http://localhost:3000'; 7export const LOCAL_API = 'http://localhost:3000';
3export const DEV_API = 'https://dev.franzinfra.com'; 8export const DEV_API = 'https://dev.franzinfra.com';
@@ -5,7 +10,6 @@ export const LIVE_API = 'https://api.franzinfra.com';
5export const GA_ID = 'UA-74126766-6'; 10export const GA_ID = 'UA-74126766-6';
6 11
7export const DEFAULT_APP_SETTINGS = { 12export const DEFAULT_APP_SETTINGS = {
8 autoLaunchOnStart: true,
9 autoLaunchInBackground: false, 13 autoLaunchInBackground: false,
10 runInBackground: true, 14 runInBackground: true,
11 enableSystemTray: true, 15 enableSystemTray: true,
@@ -13,12 +17,14 @@ export const DEFAULT_APP_SETTINGS = {
13 showDisabledServices: true, 17 showDisabledServices: true,
14 showMessageBadgeWhenMuted: true, 18 showMessageBadgeWhenMuted: true,
15 enableSpellchecking: true, 19 enableSpellchecking: true,
16 // spellcheckingLanguage: 'auto',
17 locale: '', 20 locale: '',
18 fallbackLocale: 'en-US', 21 fallbackLocale: 'en-US',
19 beta: false, 22 beta: false,
20 isAppMuted: false, 23 isAppMuted: false,
24 enableGPUAcceleration: true,
21}; 25};
22 26
23export const FRANZ_SERVICE_REQUEST = 'http://bit.ly/franz-service-request'; 27export const FRANZ_SERVICE_REQUEST = 'http://bit.ly/franz-service-request';
24export const FRANZ_TRANSLATION = 'http://bit.ly/franz-translate'; 28export const FRANZ_TRANSLATION = 'http://bit.ly/franz-translate';
29
30export const SETTINGS_PATH = path.join(app.getPath('userData'), 'config', 'settings.json');
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index 222ffdc1a..9212f809f 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -77,7 +77,7 @@ export default class AppLayoutContainer extends Component {
77 <Sidebar 77 <Sidebar
78 services={services.allDisplayed} 78 services={services.allDisplayed}
79 setActive={setActive} 79 setActive={setActive}
80 isAppMuted={settings.all.isAppMuted} 80 isAppMuted={settings.all.app.isAppMuted}
81 openSettings={openSettings} 81 openSettings={openSettings}
82 closeSettings={closeSettings} 82 closeSettings={closeSettings}
83 reorder={reorder} 83 reorder={reorder}
@@ -87,7 +87,7 @@ export default class AppLayoutContainer extends Component {
87 deleteService={deleteService} 87 deleteService={deleteService}
88 updateService={updateService} 88 updateService={updateService}
89 toggleMuteApp={toggleMuteApp} 89 toggleMuteApp={toggleMuteApp}
90 showMessageBadgeWhenMutedSetting={settings.all.showMessageBadgeWhenMuted} 90 showMessageBadgeWhenMutedSetting={settings.all.app.showMessageBadgeWhenMuted}
91 showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted} 91 showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted}
92 /> 92 />
93 ); 93 );
@@ -99,7 +99,7 @@ export default class AppLayoutContainer extends Component {
99 setWebviewReference={setWebviewReference} 99 setWebviewReference={setWebviewReference}
100 openWindow={openWindow} 100 openWindow={openWindow}
101 reload={reload} 101 reload={reload}
102 isAppMuted={settings.all.isAppMuted} 102 isAppMuted={settings.all.app.isAppMuted}
103 update={updateService} 103 update={updateService}
104 /> 104 />
105 ); 105 );
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
index 1fa7ce8bc..018ce663f 100644
--- a/src/containers/settings/EditSettingsScreen.js
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -51,14 +51,14 @@ const messages = defineMessages({
51 id: 'settings.app.form.enableSpellchecking', 51 id: 'settings.app.form.enableSpellchecking',
52 defaultMessage: '!!!Enable spell checking', 52 defaultMessage: '!!!Enable spell checking',
53 }, 53 },
54 enableGPUAcceleration: {
55 id: 'settings.app.form.enableGPUAcceleration',
56 defaultMessage: '!!!Enable GPU Acceleration',
57 },
54 spellcheckingLanguage: { 58 spellcheckingLanguage: {
55 id: 'settings.app.form.spellcheckingLanguage', 59 id: 'settings.app.form.spellcheckingLanguage',
56 defaultMessage: '!!!Language for spell checking', 60 defaultMessage: '!!!Language for spell checking',
57 }, 61 },
58 // spellcheckingAutomaticDetection: {
59 // id: 'settings.app.form.spellcheckingAutomaticDetection',
60 // defaultMessage: '!!!Detect language automatically',
61 // },
62 beta: { 62 beta: {
63 id: 'settings.app.form.beta', 63 id: 'settings.app.form.beta',
64 defaultMessage: '!!!Include beta versions', 64 defaultMessage: '!!!Include beta versions',
@@ -84,22 +84,24 @@ export default class EditSettingsScreen extends Component {
84 }); 84 });
85 85
86 settings.update({ 86 settings.update({
87 settings: { 87 type: 'app',
88 data: {
88 runInBackground: settingsData.runInBackground, 89 runInBackground: settingsData.runInBackground,
89 enableSystemTray: settingsData.enableSystemTray, 90 enableSystemTray: settingsData.enableSystemTray,
90 minimizeToSystemTray: settingsData.minimizeToSystemTray, 91 minimizeToSystemTray: settingsData.minimizeToSystemTray,
92 enableGPUAcceleration: settingsData.enableGPUAcceleration,
91 showDisabledServices: settingsData.showDisabledServices, 93 showDisabledServices: settingsData.showDisabledServices,
92 showMessageBadgeWhenMuted: settingsData.showMessageBadgeWhenMuted, 94 showMessageBadgeWhenMuted: settingsData.showMessageBadgeWhenMuted,
93 enableSpellchecking: settingsData.enableSpellchecking, 95 enableSpellchecking: settingsData.enableSpellchecking,
94 // spellcheckingLanguage: settingsData.spellcheckingLanguage, 96 beta: settingsData.beta, // we need this info in the main process as well
95 locale: settingsData.locale, 97 locale: settingsData.locale, // we need this info in the main process as well
96 beta: settingsData.beta,
97 }, 98 },
98 }); 99 });
99 100
100 user.update({ 101 user.update({
101 userData: { 102 userData: {
102 beta: settingsData.beta, 103 beta: settingsData.beta,
104 locale: settingsData.locale,
103 }, 105 },
104 }); 106 });
105 } 107 }
@@ -116,17 +118,6 @@ export default class EditSettingsScreen extends Component {
116 }); 118 });
117 }); 119 });
118 120
119 // const spellcheckerLocales = [{
120 // value: 'auto',
121 // label: intl.formatMessage(messages.spellcheckingAutomaticDetection),
122 // }];
123 // Object.keys(SPELLCHECKER_LOCALES).forEach((key) => {
124 // spellcheckerLocales.push({
125 // value: key,
126 // label: SPELLCHECKER_LOCALES[key],
127 // });
128 // });
129
130 const config = { 121 const config = {
131 fields: { 122 fields: {
132 autoLaunchOnStart: { 123 autoLaunchOnStart: {
@@ -141,40 +132,39 @@ export default class EditSettingsScreen extends Component {
141 }, 132 },
142 runInBackground: { 133 runInBackground: {
143 label: intl.formatMessage(messages.runInBackground), 134 label: intl.formatMessage(messages.runInBackground),
144 value: settings.all.runInBackground, 135 value: settings.all.app.runInBackground,
145 default: DEFAULT_APP_SETTINGS.runInBackground, 136 default: DEFAULT_APP_SETTINGS.runInBackground,
146 }, 137 },
147 enableSystemTray: { 138 enableSystemTray: {
148 label: intl.formatMessage(messages.enableSystemTray), 139 label: intl.formatMessage(messages.enableSystemTray),
149 value: settings.all.enableSystemTray, 140 value: settings.all.app.enableSystemTray,
150 default: DEFAULT_APP_SETTINGS.enableSystemTray, 141 default: DEFAULT_APP_SETTINGS.enableSystemTray,
151 }, 142 },
152 minimizeToSystemTray: { 143 minimizeToSystemTray: {
153 label: intl.formatMessage(messages.minimizeToSystemTray), 144 label: intl.formatMessage(messages.minimizeToSystemTray),
154 value: settings.all.minimizeToSystemTray, 145 value: settings.all.app.minimizeToSystemTray,
155 default: DEFAULT_APP_SETTINGS.minimizeToSystemTray, 146 default: DEFAULT_APP_SETTINGS.minimizeToSystemTray,
156 }, 147 },
157 showDisabledServices: { 148 showDisabledServices: {
158 label: intl.formatMessage(messages.showDisabledServices), 149 label: intl.formatMessage(messages.showDisabledServices),
159 value: settings.all.showDisabledServices, 150 value: settings.all.app.showDisabledServices,
160 default: DEFAULT_APP_SETTINGS.showDisabledServices, 151 default: DEFAULT_APP_SETTINGS.showDisabledServices,
161 }, 152 },
162 showMessageBadgeWhenMuted: { 153 showMessageBadgeWhenMuted: {
163 label: intl.formatMessage(messages.showMessageBadgeWhenMuted), 154 label: intl.formatMessage(messages.showMessageBadgeWhenMuted),
164 value: settings.all.showMessageBadgeWhenMuted, 155 value: settings.all.app.showMessageBadgeWhenMuted,
165 default: DEFAULT_APP_SETTINGS.showMessageBadgeWhenMuted, 156 default: DEFAULT_APP_SETTINGS.showMessageBadgeWhenMuted,
166 }, 157 },
167 enableSpellchecking: { 158 enableSpellchecking: {
168 label: intl.formatMessage(messages.enableSpellchecking), 159 label: intl.formatMessage(messages.enableSpellchecking),
169 value: settings.all.enableSpellchecking, 160 value: settings.all.app.enableSpellchecking,
170 default: DEFAULT_APP_SETTINGS.enableSpellchecking, 161 default: DEFAULT_APP_SETTINGS.enableSpellchecking,
171 }, 162 },
172 // spellcheckingLanguage: { 163 enableGPUAcceleration: {
173 // label: intl.formatMessage(messages.spellcheckingLanguage), 164 label: intl.formatMessage(messages.enableGPUAcceleration),
174 // value: settings.all.spellcheckingLanguage, 165 value: settings.all.app.enableGPUAcceleration,
175 // options: spellcheckerLocales, 166 default: DEFAULT_APP_SETTINGS.enableGPUAcceleration,
176 // default: DEFAULT_APP_SETTINGS.spellcheckingLanguage, 167 },
177 // },
178 locale: { 168 locale: {
179 label: intl.formatMessage(messages.language), 169 label: intl.formatMessage(messages.language),
180 value: app.locale, 170 value: app.locale,
diff --git a/src/electron/Settings.js b/src/electron/Settings.js
index 824b4c20c..b3138e948 100644
--- a/src/electron/Settings.js
+++ b/src/electron/Settings.js
@@ -1,27 +1,42 @@
1import { observable } from 'mobx'; 1import { observable, toJS } from 'mobx';
2import { pathExistsSync, outputJsonSync, readJsonSync } from 'fs-extra';
2 3
3import { DEFAULT_APP_SETTINGS } from '../config'; 4import { SETTINGS_PATH, DEFAULT_APP_SETTINGS } from '../config';
5
6const debug = require('debug')('Settings');
4 7
5export default class Settings { 8export default class Settings {
6 @observable store = { 9 @observable store = DEFAULT_APP_SETTINGS;
7 autoLaunchOnStart: DEFAULT_APP_SETTINGS.autoLaunchOnStart, 10
8 autoLaunchInBackground: DEFAULT_APP_SETTINGS.autoLaunchInBackground, 11 constructor() {
9 runInBackground: DEFAULT_APP_SETTINGS.runInBackground, 12 if (!pathExistsSync(SETTINGS_PATH)) {
10 enableSystemTray: DEFAULT_APP_SETTINGS.enableSystemTray, 13 this._writeFile();
11 minimizeToSystemTray: DEFAULT_APP_SETTINGS.minimizeToSystemTray, 14 } else {
12 locale: DEFAULT_APP_SETTINGS.locale, 15 this._hydrate();
13 beta: DEFAULT_APP_SETTINGS.beta, 16 }
14 }; 17 }
15 18
16 set(settings) { 19 set(settings) {
17 this.store = Object.assign(this.store, settings); 20 this.store = Object.assign(this.store, settings);
21
22 this._writeFile();
18 } 23 }
19 24
20 all() { 25 get all() {
21 return this.store; 26 return this.store;
22 } 27 }
23 28
24 get(key) { 29 get(key) {
25 return this.store[key]; 30 return this.store[key];
26 } 31 }
32
33 _hydrate() {
34 this.store = readJsonSync(SETTINGS_PATH);
35 debug('Hydrate store', toJS(this.store));
36 }
37
38 _writeFile() {
39 outputJsonSync(SETTINGS_PATH, this.store);
40 debug('Write settings file', toJS(this.store));
41 }
27} 42}
diff --git a/src/electron/ipc-api/settings.js b/src/electron/ipc-api/settings.js
index 995b28fbd..3eab68a91 100644
--- a/src/electron/ipc-api/settings.js
+++ b/src/electron/ipc-api/settings.js
@@ -1,7 +1,11 @@
1import { ipcMain } from 'electron'; 1import { ipcMain } from 'electron';
2 2
3export default (params) => { 3export default (params) => {
4 ipcMain.on('settings', (event, args) => { 4 ipcMain.on('getAppSettings', () => {
5 params.mainWindow.webContents.send('appSettings', params.settings.all);
6 });
7
8 ipcMain.on('updateAppSettings', (event, args) => {
5 params.settings.set(args); 9 params.settings.set(args);
6 }); 10 });
7}; 11};
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 400a9a5d8..0c62da44a 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -164,6 +164,7 @@
164 "settings.app.form.runInBackground": "Keep Franz in background when closing the window", 164 "settings.app.form.runInBackground": "Keep Franz in background when closing the window",
165 "settings.app.form.language": "Language", 165 "settings.app.form.language": "Language",
166 "settings.app.form.enableSpellchecking": "Enable spell checking", 166 "settings.app.form.enableSpellchecking": "Enable spell checking",
167 "settings.app.form.enableGPUAcceleration": "Enable GPU Acceleration",
167 "settings.app.form.showDisabledServices": "Display disabled services tabs", 168 "settings.app.form.showDisabledServices": "Display disabled services tabs",
168 "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled", 169 "settings.app.form.showMessagesBadgesWhenMuted": "Show unread message badge when notifications are disabled",
169 "settings.app.form.beta": "Include beta versions", 170 "settings.app.form.beta": "Include beta versions",
diff --git a/src/index.js b/src/index.js
index 897943a85..5ba901b89 100644
--- a/src/index.js
+++ b/src/index.js
@@ -12,6 +12,8 @@ import handleDeepLink from './electron/deepLinking';
12import { appId } from './package.json'; // eslint-disable-line import/no-unresolved 12import { appId } from './package.json'; // eslint-disable-line import/no-unresolved
13import './electron/exception'; 13import './electron/exception';
14 14
15const debug = require('debug')('App');
16
15// Keep a global reference of the window object, if you don't, the window will 17// Keep a global reference of the window object, if you don't, the window will
16// be closed automatically when the JavaScript object is garbage collected. 18// be closed automatically when the JavaScript object is garbage collected.
17let mainWindow; 19let mainWindow;
@@ -57,6 +59,12 @@ if (isLinux && ['Pantheon', 'Unity:Unity7'].indexOf(process.env.XDG_CURRENT_DESK
57// Initialize Settings 59// Initialize Settings
58const settings = new Settings(); 60const settings = new Settings();
59 61
62// Disable GPU acceleration
63if (!settings.get('enableGPUAcceleration')) {
64 debug('Disable GPU Acceleration');
65 app.disableHardwareAcceleration();
66}
67
60const createWindow = () => { 68const createWindow = () => {
61 // Remember window size 69 // Remember window size
62 const mainWindowState = windowStateKeeper({ 70 const mainWindowState = windowStateKeeper({
diff --git a/src/models/Settings.js b/src/models/Settings.js
index e39b63087..0e4c59057 100644
--- a/src/models/Settings.js
+++ b/src/models/Settings.js
@@ -2,19 +2,29 @@ import { observable, extendObservable } from 'mobx';
2import { DEFAULT_APP_SETTINGS } from '../config'; 2import { DEFAULT_APP_SETTINGS } from '../config';
3 3
4export default class Settings { 4export default class Settings {
5 @observable autoLaunchInBackground = DEFAULT_APP_SETTINGS.autoLaunchInBackground; 5 @observable app = DEFAULT_APP_SETTINGS
6 @observable runInBackground = DEFAULT_APP_SETTINGS.runInBackground;
7 @observable enableSystemTray = DEFAULT_APP_SETTINGS.enableSystemTray;
8 @observable minimizeToSystemTray = DEFAULT_APP_SETTINGS.minimizeToSystemTray;
9 @observable showDisabledServices = DEFAULT_APP_SETTINGS.showDisabledServices;
10 @observable showMessageBadgeWhenMuted = DEFAULT_APP_SETTINGS.showMessageBadgeWhenMuted;
11 @observable enableSpellchecking = DEFAULT_APP_SETTINGS.enableSpellchecking;
12 @observable locale = DEFAULT_APP_SETTINGS.locale;
13 @observable beta = DEFAULT_APP_SETTINGS.beta;
14 @observable isAppMuted = DEFAULT_APP_SETTINGS.isAppMuted;
15 6
16 constructor(data) { 7 @observable service = {
17 Object.assign(this, data); 8 activeService: '',
9 }
10
11 @observable group = {
12 collapsed: [],
13 disabled: [],
14 }
15
16 @observable stats = {
17 appStarts: 0,
18 }
19
20 @observable migration = {}
21
22 constructor({ app, service, group, stats, migration }) {
23 Object.assign(this.app, app);
24 Object.assign(this.service, service);
25 Object.assign(this.group, group);
26 Object.assign(this.stats, stats);
27 Object.assign(this.migration, migration);
18 } 28 }
19 29
20 update(data) { 30 update(data) {
diff --git a/src/models/User.js b/src/models/User.js
index 2e5df4795..3e4aa187d 100644
--- a/src/models/User.js
+++ b/src/models/User.js
@@ -15,6 +15,7 @@ export default class User {
15 @observable donor = {}; 15 @observable donor = {};
16 @observable isDonor = false; 16 @observable isDonor = false;
17 @observable isMiner = false; 17 @observable isMiner = false;
18 @observable locale = false;
18 19
19 constructor(data) { 20 constructor(data) {
20 if (!data.id) { 21 if (!data.id) {
@@ -33,5 +34,6 @@ export default class User {
33 this.isDonor = data.isDonor || this.isDonor; 34 this.isDonor = data.isDonor || this.isDonor;
34 this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner; 35 this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner;
35 this.isMiner = data.isMiner || this.isMiner; 36 this.isMiner = data.isMiner || this.isMiner;
37 this.locale = data.locale || this.locale;
36 } 38 }
37} 39}
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index b3554f9d8..38edff1b4 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -15,6 +15,8 @@ import { gaEvent } from '../lib/analytics';
15 15
16import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; 16import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js';
17 17
18const debug = require('debug')('AppStore');
19
18const { app } = remote; 20const { app } = remote;
19 21
20const mainWindow = remote.getCurrentWindow(); 22const mainWindow = remote.getCurrentWindow();
@@ -165,7 +167,7 @@ export default class AppStore extends Store {
165 167
166 // Actions 168 // Actions
167 @action _notify({ title, options, notificationId, serviceId = null }) { 169 @action _notify({ title, options, notificationId, serviceId = null }) {
168 if (this.stores.settings.all.isAppMuted) return; 170 if (this.stores.settings.all.app.isAppMuted) return;
169 171
170 const notification = new window.Notification(title, options); 172 const notification = new window.Notification(title, options);
171 notification.onclick = (e) => { 173 notification.onclick = (e) => {
@@ -244,14 +246,15 @@ export default class AppStore extends Store {
244 this.isSystemMuteOverridden = overrideSystemMute; 246 this.isSystemMuteOverridden = overrideSystemMute;
245 247
246 this.actions.settings.update({ 248 this.actions.settings.update({
247 settings: { 249 type: 'app',
250 data: {
248 isAppMuted: isMuted, 251 isAppMuted: isMuted,
249 }, 252 },
250 }); 253 });
251 } 254 }
252 255
253 @action _toggleMuteApp() { 256 @action _toggleMuteApp() {
254 this._muteApp({ isMuted: !this.stores.settings.all.isAppMuted }); 257 this._muteApp({ isMuted: !this.stores.settings.all.app.isAppMuted });
255 } 258 }
256 259
257 @action async _clearAllCache() { 260 @action async _clearAllCache() {
@@ -285,13 +288,19 @@ export default class AppStore extends Store {
285 } 288 }
286 289
287 _setLocale() { 290 _setLocale() {
288 const locale = this.stores.settings.all.locale; 291 let locale;
292 if (this.stores.user.isLoggedIn) {
293 locale = this.stores.user.data.locale;
294 }
295
289 296
290 if (locale && Object.prototype.hasOwnProperty.call(locales, locale) && locale !== this.locale) { 297 if (locale && Object.prototype.hasOwnProperty.call(locales, locale) && locale !== this.locale) {
291 this.locale = locale; 298 this.locale = locale;
292 } else if (!locale) { 299 } else if (!locale) {
293 this.locale = this._getDefaultLocale(); 300 this.locale = this._getDefaultLocale();
294 } 301 }
302
303 debug(`Set locale to "${this.locale}"`);
295 } 304 }
296 305
297 _getDefaultLocale() { 306 _getDefaultLocale() {
@@ -333,8 +342,9 @@ export default class AppStore extends Store {
333 // Helpers 342 // Helpers
334 _appStartsCounter() { 343 _appStartsCounter() {
335 this.actions.settings.update({ 344 this.actions.settings.update({
336 settings: { 345 type: 'stats',
337 appStarts: (this.stores.settings.all.appStarts || 0) + 1, 346 data: {
347 appStarts: (this.stores.settings.all.stats.appStarts || 0) + 1,
338 }, 348 },
339 }); 349 });
340 } 350 }
@@ -342,7 +352,8 @@ export default class AppStore extends Store {
342 async _autoStart() { 352 async _autoStart() {
343 this.autoLaunchOnStart = await this._checkAutoStart(); 353 this.autoLaunchOnStart = await this._checkAutoStart();
344 354
345 if (this.stores.settings.all.appStarts === 1) { 355 if (this.stores.settings.all.stats.appStarts === 1) {
356 debug('Set app to launch on start');
346 this.actions.app.launchOnStartup({ 357 this.actions.app.launchOnStartup({
347 enable: true, 358 enable: true,
348 }); 359 });
@@ -355,7 +366,7 @@ export default class AppStore extends Store {
355 366
356 _systemDND() { 367 _systemDND() {
357 const dnd = getDoNotDisturb(); 368 const dnd = getDoNotDisturb();
358 if (dnd !== this.stores.settings.all.isAppMuted && !this.isSystemMuteOverridden) { 369 if (dnd !== this.stores.settings.all.app.isAppMuted && !this.isSystemMuteOverridden) {
359 this.actions.app.muteApp({ 370 this.actions.app.muteApp({
360 isMuted: dnd, 371 isMuted: dnd,
361 overrideSystemMute: false, 372 overrideSystemMute: false,
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index f7d92b1ff..ccb85421a 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -1,8 +1,5 @@
1// import { remote } from 'electron'; 1import { action, reaction, computed, observable } from 'mobx';
2import { action, computed, observable } from 'mobx';
3import { debounce, remove } from 'lodash'; 2import { debounce, remove } from 'lodash';
4// import path from 'path';
5// import fs from 'fs-extra';
6 3
7import Store from './lib/Store'; 4import Store from './lib/Store';
8import Request from './lib/Request'; 5import Request from './lib/Request';
@@ -63,13 +60,20 @@ export default class ServicesStore extends Store {
63 this._mapActiveServiceToServiceModelReaction.bind(this), 60 this._mapActiveServiceToServiceModelReaction.bind(this),
64 this._saveActiveService.bind(this), 61 this._saveActiveService.bind(this),
65 this._logoutReaction.bind(this), 62 this._logoutReaction.bind(this),
66 this._shareSettingsWithServiceProcess.bind(this),
67 ]); 63 ]);
68 64
69 // Just bind this 65 // Just bind this
70 this._initializeServiceRecipeInWebview.bind(this); 66 this._initializeServiceRecipeInWebview.bind(this);
71 } 67 }
72 68
69 setup() {
70 // Single key reactions
71 reaction(
72 () => this.stores.settings.all.app.enableSpellchecking,
73 () => this._shareSettingsWithServiceProcess(),
74 );
75 }
76
73 @computed get all() { 77 @computed get all() {
74 if (this.stores.user.isLoggedIn) { 78 if (this.stores.user.isLoggedIn) {
75 const services = this.allServicesRequest.execute().result; 79 const services = this.allServicesRequest.execute().result;
@@ -86,13 +90,13 @@ export default class ServicesStore extends Store {
86 } 90 }
87 91
88 @computed get allDisplayed() { 92 @computed get allDisplayed() {
89 return this.stores.settings.all.showDisabledServices ? this.all : this.enabled; 93 return this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled;
90 } 94 }
91 95
92 // This is just used to avoid unnecessary rerendering of resource-heavy webviews 96 // This is just used to avoid unnecessary rerendering of resource-heavy webviews
93 @computed get allDisplayedUnordered() { 97 @computed get allDisplayedUnordered() {
94 const services = this.allServicesRequest.execute().result || []; 98 const services = this.allServicesRequest.execute().result || [];
95 return this.stores.settings.all.showDisabledServices ? services : services.filter(service => service.isEnabled); 99 return this.stores.settings.all.app.showDisabledServices ? services : services.filter(service => service.isEnabled);
96 } 100 }
97 101
98 @computed get filtered() { 102 @computed get filtered() {
@@ -334,7 +338,7 @@ export default class ServicesStore extends Store {
334 }); 338 });
335 } else if (channel === 'notification') { 339 } else if (channel === 'notification') {
336 const options = args[0].options; 340 const options = args[0].options;
337 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.isAppMuted) { 341 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.app.isAppMuted) {
338 Object.assign(options, { 342 Object.assign(options, {
339 silent: true, 343 silent: true,
340 }); 344 });
@@ -434,7 +438,7 @@ export default class ServicesStore extends Store {
434 } 438 }
435 439
436 @action _reorder({ oldIndex, newIndex }) { 440 @action _reorder({ oldIndex, newIndex }) {
437 const showDisabledServices = this.stores.settings.all.showDisabledServices; 441 const showDisabledServices = this.stores.settings.all.app.showDisabledServices;
438 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]); 442 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]);
439 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]); 443 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]);
440 444
@@ -512,7 +516,8 @@ export default class ServicesStore extends Store {
512 516
513 if (service) { 517 if (service) {
514 this.actions.settings.update({ 518 this.actions.settings.update({
515 settings: { 519 type: 'service',
520 data: {
516 activeService: service.id, 521 activeService: service.id,
517 }, 522 },
518 }); 523 });
@@ -520,7 +525,7 @@ export default class ServicesStore extends Store {
520 } 525 }
521 526
522 _mapActiveServiceToServiceModelReaction() { 527 _mapActiveServiceToServiceModelReaction() {
523 const { activeService } = this.stores.settings.all; 528 const { activeService } = this.stores.settings.all.service;
524 if (this.allDisplayed.length) { 529 if (this.allDisplayed.length) {
525 this.allDisplayed.map(service => Object.assign(service, { 530 this.allDisplayed.map(service => Object.assign(service, {
526 isActive: activeService ? activeService === service.id : this.allDisplayed[0].id === service.id, 531 isActive: activeService ? activeService === service.id : this.allDisplayed[0].id === service.id,
@@ -529,7 +534,7 @@ export default class ServicesStore extends Store {
529 } 534 }
530 535
531 _getUnreadMessageCountReaction() { 536 _getUnreadMessageCountReaction() {
532 const showMessageBadgeWhenMuted = this.stores.settings.all.showMessageBadgeWhenMuted; 537 const showMessageBadgeWhenMuted = this.stores.settings.all.app.showMessageBadgeWhenMuted;
533 const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; 538 const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted;
534 539
535 const unreadDirectMessageCount = this.allDisplayed 540 const unreadDirectMessageCount = this.allDisplayed
@@ -553,7 +558,10 @@ export default class ServicesStore extends Store {
553 558
554 _logoutReaction() { 559 _logoutReaction() {
555 if (!this.stores.user.isLoggedIn) { 560 if (!this.stores.user.isLoggedIn) {
556 this.actions.settings.remove({ key: 'activeService' }); 561 this.actions.settings.remove({
562 type: 'service',
563 key: 'activeService',
564 });
557 this.allServicesRequest.invalidate().reset(); 565 this.allServicesRequest.invalidate().reset();
558 } 566 }
559 } 567 }
@@ -561,7 +569,7 @@ export default class ServicesStore extends Store {
561 _shareSettingsWithServiceProcess() { 569 _shareSettingsWithServiceProcess() {
562 this.actions.service.sendIPCMessageToAllServices({ 570 this.actions.service.sendIPCMessageToAllServices({
563 channel: 'settings-update', 571 channel: 'settings-update',
564 args: this.stores.settings.all, 572 args: this.stores.settings.all.app,
565 }); 573 });
566 } 574 }
567 575
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index b7d803398..82fc2a279 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -1,12 +1,17 @@
1import { ipcRenderer } from 'electron'; 1import { action, computed, observable } from 'mobx';
2import { action, computed } from 'mobx';
3import localStorage from 'mobx-localstorage'; 2import localStorage from 'mobx-localstorage';
4 3
5import Store from './lib/Store'; 4import Store from './lib/Store';
6import { gaEvent } from '../lib/analytics';
7import SettingsModel from '../models/Settings'; 5import SettingsModel from '../models/Settings';
6import Request from './lib/Request';
7import CachedRequest from './lib/CachedRequest';
8
9const debug = require('debug')('SettingsStore');
8 10
9export default class SettingsStore extends Store { 11export default class SettingsStore extends Store {
12 @observable appSettingsRequest = new CachedRequest(this.api.local, 'getAppSettings');
13 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings');
14
10 constructor(...args) { 15 constructor(...args) {
11 super(...args); 16 super(...args);
12 17
@@ -16,35 +21,88 @@ export default class SettingsStore extends Store {
16 } 21 }
17 22
18 setup() { 23 setup() {
19 this._shareSettingsWithMainProcess(); 24 this._migrate();
20 } 25 }
21 26
22 @computed get all() { 27 @computed get all() {
23 return new SettingsModel(localStorage.getItem('app') || {}); 28 return new SettingsModel({
29 app: this.appSettingsRequest.execute().result || {},
30 service: localStorage.getItem('service') || {},
31 group: localStorage.getItem('group') || {},
32 stats: localStorage.getItem('stats') || {},
33 migration: localStorage.getItem('migration') || {},
34 });
24 } 35 }
25 36
26 @action async _update({ settings }) { 37 @action async _update({ type, data }) {
27 const appSettings = this.all; 38 const appSettings = this.all;
28 localStorage.setItem('app', Object.assign(appSettings, settings)); 39 if (type !== 'app') {
40 debug('Update settings', type, data, this.all);
41 localStorage.setItem(type, Object.assign(appSettings[type], data));
42 } else {
43 debug('Update settings on file system', type, data);
44 this.updateAppSettingsRequest.execute(data);
29 45
30 // We need a little hack to wait until everything is patched 46 this.appSettingsRequest.patch((result) => {
31 setTimeout(() => this._shareSettingsWithMainProcess(), 0); 47 if (!result) return;
32 48 Object.assign(result, data);
33 gaEvent('Settings', 'update'); 49 });
50 }
34 } 51 }
35 52
36 @action async _remove({ key }) { 53 @action async _remove({ type, key }) {
37 const appSettings = this.all; 54 if (type === 'app') return; // app keys can't be deleted
55
56 const appSettings = this.all[type];
38 if (Object.hasOwnProperty.call(appSettings, key)) { 57 if (Object.hasOwnProperty.call(appSettings, key)) {
39 delete appSettings[key]; 58 delete appSettings[key];
40 localStorage.setItem('app', appSettings);
41 }
42 59
43 this._shareSettingsWithMainProcess(); 60 this.actions.settings.update({
61 type,
62 data: appSettings,
63 });
64 }
44 } 65 }
45 66
46 // Reactions 67 // Helper
47 _shareSettingsWithMainProcess() { 68 _migrate() {
48 ipcRenderer.send('settings', this.all); 69 const legacySettings = localStorage.getItem('app');
70
71 if (!this.all.migration['5.0.0-beta.17-settings']) {
72 this.actions.settings.update({
73 type: 'app',
74 data: {
75 autoLaunchInBackground: legacySettings.autoLaunchInBackground,
76 runInBackground: legacySettings.runInBackground,
77 enableSystemTray: legacySettings.enableSystemTray,
78 minimizeToSystemTray: legacySettings.minimizeToSystemTray,
79 isAppMuted: legacySettings.isAppMuted,
80 enableGPUAcceleration: legacySettings.enableGPUAcceleration,
81 showMessageBadgeWhenMuted: legacySettings.showMessageBadgeWhenMuted,
82 showDisabledServices: legacySettings.showDisabledServices,
83 enableSpellchecking: legacySettings.enableSpellchecking,
84 locale: legacySettings.locale,
85 beta: legacySettings.beta,
86 },
87 });
88
89 this.actions.settings.update({
90 type: 'service',
91 data: {
92 activeService: legacySettings.activeService,
93 },
94 });
95
96 this.actions.settings.update({
97 type: 'migration',
98 data: {
99 '5.0.0-beta.17-settings': true,
100 },
101 });
102
103 localStorage.removeItem('app');
104
105 debug('Migrated settings to split stores');
106 }
49 } 107 }
50} 108}
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
index 5e9cc9ba7..b391bdcae 100644
--- a/src/stores/UIStore.js
+++ b/src/stores/UIStore.js
@@ -17,7 +17,7 @@ export default class UIStore extends Store {
17 @computed get showMessageBadgesEvenWhenMuted() { 17 @computed get showMessageBadgesEvenWhenMuted() {
18 const settings = this.stores.settings.all; 18 const settings = this.stores.settings.all;
19 19
20 return (settings.isAppMuted && settings.showMessageBadgeWhenMuted) || !settings.isAppMuted; 20 return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted;
21 } 21 }
22 22
23 // Actions 23 // Actions
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 7dbbd955b..574616925 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -9,6 +9,8 @@ import Request from './lib/Request';
9import CachedRequest from './lib/CachedRequest'; 9import CachedRequest from './lib/CachedRequest';
10import { gaEvent } from '../lib/analytics'; 10import { gaEvent } from '../lib/analytics';
11 11
12const debug = require('debug')('UserStore');
13
12// TODO: split stores into UserStore and AuthStore 14// TODO: split stores into UserStore and AuthStore
13export default class UserStore extends Store { 15export default class UserStore extends Store {
14 BASE_ROUTE = '/auth'; 16 BASE_ROUTE = '/auth';
@@ -69,6 +71,11 @@ export default class UserStore extends Store {
69 ]); 71 ]);
70 } 72 }
71 73
74 setup() {
75 // Data migration
76 this._migrateUserLocale();
77 }
78
72 // Routes 79 // Routes
73 get loginRoute() { 80 get loginRoute() {
74 return this.LOGIN_ROUTE; 81 return this.LOGIN_ROUTE;
@@ -256,8 +263,10 @@ export default class UserStore extends Store {
256 263
257 // We need to set the beta flag for the SettingsStore 264 // We need to set the beta flag for the SettingsStore
258 this.actions.settings.update({ 265 this.actions.settings.update({
259 settings: { 266 type: 'app',
267 data: {
260 beta: data.beta, 268 beta: data.beta,
269 locale: data.locale,
261 }, 270 },
262 }); 271 });
263 } 272 }
@@ -292,4 +301,17 @@ export default class UserStore extends Store {
292 this.id = null; 301 this.id = null;
293 } 302 }
294 } 303 }
304
305 async _migrateUserLocale() {
306 await this.getUserInfoRequest._promise;
307
308 if (!this.data.locale) {
309 debug('Migrate "locale" to user data');
310 this.actions.user.update({
311 userData: {
312 locale: this.stores.app.locale,
313 },
314 });
315 }
316 }
295} 317}
diff --git a/src/webview/plugin.js b/src/webview/plugin.js
index 52b19b3fd..c6530fef6 100644
--- a/src/webview/plugin.js
+++ b/src/webview/plugin.js
@@ -8,6 +8,8 @@ import RecipeWebview from './lib/RecipeWebview';
8import Spellchecker from './spellchecker'; 8import Spellchecker from './spellchecker';
9import './notifications'; 9import './notifications';
10 10
11const debug = require('debug')('Plugin');
12
11ipcRenderer.on('initializeRecipe', (e, data) => { 13ipcRenderer.on('initializeRecipe', (e, data) => {
12 const modulePath = path.join(data.recipe.path, 'webview.js'); 14 const modulePath = path.join(data.recipe.path, 'webview.js');
13 // Delete module from cache 15 // Delete module from cache
@@ -15,8 +17,9 @@ ipcRenderer.on('initializeRecipe', (e, data) => {
15 try { 17 try {
16 // eslint-disable-next-line 18 // eslint-disable-next-line
17 require(modulePath)(new RecipeWebview(), data); 19 require(modulePath)(new RecipeWebview(), data);
20 debug('Initialize Recipe');
18 } catch (err) { 21 } catch (err) {
19 console.error(err); 22 debug('Recipe initialization failed', err);
20 } 23 }
21}); 24});
22 25
@@ -31,6 +34,7 @@ new ContextMenuListener((info) => { // eslint-disable-line
31 34
32ipcRenderer.on('settings-update', (e, data) => { 35ipcRenderer.on('settings-update', (e, data) => {
33 spellchecker.toggleSpellchecker(data.enableSpellchecking); 36 spellchecker.toggleSpellchecker(data.enableSpellchecking);
37 debug('Settings update received', data);
34}); 38});
35 39
36// initSpellche 40// initSpellche