aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2018-11-22 14:14:25 +0100
committerLibravatar Stefan Malzner <stefan@adlk.io>2018-11-22 14:14:25 +0100
commit46b8c8c4b3a5b80e0187b284abc84566a7e784db (patch)
tree7fd378bcdd18e78c42dfeb61a15f89fd10106046 /src/stores
parentADD features loading spinner (diff)
parentfeat(App): Add option to enable dark mode for supported services (diff)
downloadferdium-app-46b8c8c4b3a5b80e0187b284abc84566a7e784db.tar.gz
ferdium-app-46b8c8c4b3a5b80e0187b284abc84566a7e784db.tar.zst
ferdium-app-46b8c8c4b3a5b80e0187b284abc84566a7e784db.zip
Merge branch 'develop' into feature/features-api
Diffstat (limited to 'src/stores')
-rw-r--r--src/stores/AppStore.js72
-rw-r--r--src/stores/FeaturesStore.js6
-rw-r--r--src/stores/RecipesStore.js8
-rw-r--r--src/stores/RequestStore.js4
-rw-r--r--src/stores/ServicesStore.js58
-rw-r--r--src/stores/SettingsStore.js121
-rw-r--r--src/stores/UIStore.js4
-rw-r--r--src/stores/UserStore.js24
8 files changed, 214 insertions, 83 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 162422017..9ad4cd531 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -15,7 +15,11 @@ 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 { app } = remote; 18const debug = require('debug')('Franz:AppStore');
19
20const { app, systemPreferences } = remote;
21
22const mainWindow = remote.getCurrentWindow();
19 23
20const defaultLocale = DEFAULT_APP_SETTINGS.locale; 24const defaultLocale = DEFAULT_APP_SETTINGS.locale;
21const autoLauncher = new AutoLaunch({ 25const autoLauncher = new AutoLaunch({
@@ -46,8 +50,12 @@ export default class AppStore extends Store {
46 50
47 @observable isSystemMuteOverridden = false; 51 @observable isSystemMuteOverridden = false;
48 52
53 @observable isSystemDarkModeEnabled = false;
54
49 @observable isClearingAllCache = false; 55 @observable isClearingAllCache = false;
50 56
57 @observable isFullScreen = mainWindow.isFullScreen();
58
51 constructor(...args) { 59 constructor(...args) {
52 super(...args); 60 super(...args);
53 61
@@ -80,6 +88,10 @@ export default class AppStore extends Store {
80 window.addEventListener('online', () => { this.isOnline = true; }); 88 window.addEventListener('online', () => { this.isOnline = true; });
81 window.addEventListener('offline', () => { this.isOnline = false; }); 89 window.addEventListener('offline', () => { this.isOnline = false; });
82 90
91 mainWindow.on('enter-full-screen', () => { this.isFullScreen = true; });
92 mainWindow.on('leave-full-screen', () => { this.isFullScreen = false; });
93
94
83 this.isOnline = navigator.onLine; 95 this.isOnline = navigator.onLine;
84 96
85 // Check if Franz should launch on start 97 // Check if Franz should launch on start
@@ -98,6 +110,10 @@ export default class AppStore extends Store {
98 ipcRenderer.on('autoUpdate', (event, data) => { 110 ipcRenderer.on('autoUpdate', (event, data) => {
99 if (data.available) { 111 if (data.available) {
100 this.updateStatus = this.updateStatusTypes.AVAILABLE; 112 this.updateStatus = this.updateStatusTypes.AVAILABLE;
113
114 if (isMac) {
115 app.dock.bounce();
116 }
101 } 117 }
102 118
103 if (data.available !== undefined && !data.available) { 119 if (data.available !== undefined && !data.available) {
@@ -124,19 +140,6 @@ export default class AppStore extends Store {
124 this.stores.router.push(data.url); 140 this.stores.router.push(data.url);
125 }); 141 });
126 142
127 // Reload all services after a healthy nap
128 // Alternative solution for powerMonitor as the resume event is not fired
129 // More information: https://github.com/electron/electron/issues/1615
130 const TIMEOUT = 5000;
131 let lastTime = (new Date()).getTime();
132 setInterval(() => {
133 const currentTime = (new Date()).getTime();
134 if (currentTime > (lastTime + TIMEOUT + 2000)) {
135 this._reactivateServices();
136 }
137 lastTime = currentTime;
138 }, TIMEOUT);
139
140 // Set active the next service 143 // Set active the next service
141 key( 144 key(
142 '⌘+pagedown, ctrl+pagedown, ⌘+alt+right, ctrl+tab', () => { 145 '⌘+pagedown, ctrl+pagedown, ⌘+alt+right, ctrl+tab', () => {
@@ -158,6 +161,8 @@ export default class AppStore extends Store {
158 this.locale = this._getDefaultLocale(); 161 this.locale = this._getDefaultLocale();
159 162
160 this._healthCheck(); 163 this._healthCheck();
164
165 this.isSystemDarkModeEnabled = systemPreferences.isDarkMode();
161 } 166 }
162 167
163 @computed get cacheSize() { 168 @computed get cacheSize() {
@@ -166,7 +171,7 @@ export default class AppStore extends Store {
166 171
167 // Actions 172 // Actions
168 @action _notify({ title, options, notificationId, serviceId = null }) { 173 @action _notify({ title, options, notificationId, serviceId = null }) {
169 if (this.stores.settings.all.isAppMuted) return; 174 if (this.stores.settings.all.app.isAppMuted) return;
170 175
171 const notification = new window.Notification(title, options); 176 const notification = new window.Notification(title, options);
172 notification.onclick = (e) => { 177 notification.onclick = (e) => {
@@ -179,8 +184,6 @@ export default class AppStore extends Store {
179 184
180 this.actions.service.setActive({ serviceId }); 185 this.actions.service.setActive({ serviceId });
181 186
182 const mainWindow = remote.getCurrentWindow();
183
184 if (isWindows) { 187 if (isWindows) {
185 mainWindow.restore(); 188 mainWindow.restore();
186 } else if (isLinux) { 189 } else if (isLinux) {
@@ -244,17 +247,18 @@ export default class AppStore extends Store {
244 } 247 }
245 248
246 @action _muteApp({ isMuted, overrideSystemMute = true }) { 249 @action _muteApp({ isMuted, overrideSystemMute = true }) {
247 this.isSystemMuteOverriden = overrideSystemMute; 250 this.isSystemMuteOverridden = overrideSystemMute;
248 251
249 this.actions.settings.update({ 252 this.actions.settings.update({
250 settings: { 253 type: 'app',
254 data: {
251 isAppMuted: isMuted, 255 isAppMuted: isMuted,
252 }, 256 },
253 }); 257 });
254 } 258 }
255 259
256 @action _toggleMuteApp() { 260 @action _toggleMuteApp() {
257 this._muteApp({ isMuted: !this.stores.settings.all.isAppMuted }); 261 this._muteApp({ isMuted: !this.stores.settings.all.app.isAppMuted });
258 } 262 }
259 263
260 @action async _clearAllCache() { 264 @action async _clearAllCache() {
@@ -288,13 +292,19 @@ export default class AppStore extends Store {
288 } 292 }
289 293
290 _setLocale() { 294 _setLocale() {
291 const locale = this.stores.settings.all.locale; 295 let locale;
296 if (this.stores.user.isLoggedIn) {
297 locale = this.stores.user.data.locale;
298 }
299
292 300
293 if (locale && Object.prototype.hasOwnProperty.call(locales, locale) && locale !== this.locale) { 301 if (locale && Object.prototype.hasOwnProperty.call(locales, locale) && locale !== this.locale) {
294 this.locale = locale; 302 this.locale = locale;
295 } else if (!locale) { 303 } else if (!locale) {
296 this.locale = this._getDefaultLocale(); 304 this.locale = this._getDefaultLocale();
297 } 305 }
306
307 debug(`Set locale to "${this.locale}"`);
298 } 308 }
299 309
300 _getDefaultLocale() { 310 _getDefaultLocale() {
@@ -336,8 +346,9 @@ export default class AppStore extends Store {
336 // Helpers 346 // Helpers
337 _appStartsCounter() { 347 _appStartsCounter() {
338 this.actions.settings.update({ 348 this.actions.settings.update({
339 settings: { 349 type: 'stats',
340 appStarts: (this.stores.settings.all.appStarts || 0) + 1, 350 data: {
351 appStarts: (this.stores.settings.all.stats.appStarts || 0) + 1,
341 }, 352 },
342 }); 353 });
343 } 354 }
@@ -345,7 +356,8 @@ export default class AppStore extends Store {
345 async _autoStart() { 356 async _autoStart() {
346 this.autoLaunchOnStart = await this._checkAutoStart(); 357 this.autoLaunchOnStart = await this._checkAutoStart();
347 358
348 if (this.stores.settings.all.appStarts === 1) { 359 if (this.stores.settings.all.stats.appStarts === 1) {
360 debug('Set app to launch on start');
349 this.actions.app.launchOnStartup({ 361 this.actions.app.launchOnStartup({
350 enable: true, 362 enable: true,
351 }); 363 });
@@ -356,19 +368,9 @@ export default class AppStore extends Store {
356 return autoLauncher.isEnabled() || false; 368 return autoLauncher.isEnabled() || false;
357 } 369 }
358 370
359 _reactivateServices(retryCount = 0) {
360 if (!this.isOnline) {
361 console.debug('reactivateServices: computer is offline, trying again in 5s, retries:', retryCount);
362 setTimeout(() => this._reactivateServices(retryCount + 1), 5000);
363 } else {
364 console.debug('reactivateServices: reload Franz');
365 window.location.reload();
366 }
367 }
368
369 _systemDND() { 371 _systemDND() {
370 const dnd = getDoNotDisturb(); 372 const dnd = getDoNotDisturb();
371 if (dnd === this.stores.settings.all.isAppMuted || !this.isSystemMuteOverriden) { 373 if (dnd !== this.stores.settings.all.app.isAppMuted && !this.isSystemMuteOverridden) {
372 this.actions.app.muteApp({ 374 this.actions.app.muteApp({
373 isMuted: dnd, 375 isMuted: dnd,
374 overrideSystemMute: false, 376 overrideSystemMute: false,
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index a315d3b46..f788c347d 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -4,7 +4,7 @@ import Store from './lib/Store';
4import CachedRequest from './lib/CachedRequest'; 4import CachedRequest from './lib/CachedRequest';
5 5
6export default class FeaturesStore extends Store { 6export default class FeaturesStore extends Store {
7 @observable baseFeaturesRequest = new CachedRequest(this.api.features, 'base'); 7 @observable defaultFeaturesRequest = new CachedRequest(this.api.features, 'default');
8 @observable featuresRequest = new CachedRequest(this.api.features, 'features'); 8 @observable featuresRequest = new CachedRequest(this.api.features, 'features');
9 9
10 setup() { 10 setup() {
@@ -19,7 +19,7 @@ export default class FeaturesStore extends Store {
19 return this.featuresRequest.execute().result || {}; 19 return this.featuresRequest.execute().result || {};
20 } 20 }
21 21
22 return this.baseFeaturesRequest.execute().result || {}; 22 return this.defaultFeaturesRequest.execute().result || {};
23 } 23 }
24 24
25 _debugFeatures() { 25 _debugFeatures() {
@@ -30,7 +30,7 @@ export default class FeaturesStore extends Store {
30 if (this.stores.user.isLoggedIn) { 30 if (this.stores.user.isLoggedIn) {
31 this.featuresRequest.invalidate({ immediately: true }); 31 this.featuresRequest.invalidate({ immediately: true });
32 } else { 32 } else {
33 this.baseFeaturesRequest.invalidate({ immediately: true }); 33 this.defaultFeaturesRequest.invalidate({ immediately: true });
34 } 34 }
35 } 35 }
36} 36}
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
index 67fee1d50..f2480bc8e 100644
--- a/src/stores/RecipesStore.js
+++ b/src/stores/RecipesStore.js
@@ -5,6 +5,8 @@ import CachedRequest from './lib/CachedRequest';
5import Request from './lib/Request'; 5import Request from './lib/Request';
6import { matchRoute } from '../helpers/routing-helpers'; 6import { matchRoute } from '../helpers/routing-helpers';
7 7
8const debug = require('debug')('Franz:RecipeStore');
9
8export default class RecipesStore extends Store { 10export default class RecipesStore extends Store {
9 @observable allRecipesRequest = new CachedRequest(this.api.recipes, 'all'); 11 @observable allRecipesRequest = new CachedRequest(this.api.recipes, 'all');
10 @observable installRecipeRequest = new Request(this.api.recipes, 'install'); 12 @observable installRecipeRequest = new Request(this.api.recipes, 'install');
@@ -34,7 +36,7 @@ export default class RecipesStore extends Store {
34 return activeRecipe; 36 return activeRecipe;
35 } 37 }
36 38
37 console.warn('Recipe not installed'); 39 debug(`Recipe ${match.id} not installed`);
38 } 40 }
39 41
40 return null; 42 return null;
@@ -54,10 +56,8 @@ export default class RecipesStore extends Store {
54 56
55 // Actions 57 // Actions
56 @action async _install({ recipeId }) { 58 @action async _install({ recipeId }) {
57 // console.log(this.installRecipeRequest._promise);
58 const recipe = await this.installRecipeRequest.execute(recipeId)._promise; 59 const recipe = await this.installRecipeRequest.execute(recipeId)._promise;
59 await this.allRecipesRequest.invalidate({ immediately: true })._promise; 60 await this.allRecipesRequest.invalidate({ immediately: true })._promise;
60 // console.log(this.installRecipeRequest._promise);
61 61
62 return recipe; 62 return recipe;
63 } 63 }
@@ -67,7 +67,7 @@ export default class RecipesStore extends Store {
67 const recipes = {}; 67 const recipes = {};
68 68
69 // Hackfix, reference this.all to fetch services 69 // Hackfix, reference this.all to fetch services
70 console.debug(`Check Recipe updates for ${this.all.map(recipe => recipe.id)}`); 70 debug(`Check Recipe updates for ${this.all.map(recipe => recipe.id)}`);
71 71
72 recipeIds.forEach((r) => { 72 recipeIds.forEach((r) => {
73 const recipe = this.one(r); 73 const recipe = this.one(r);
diff --git a/src/stores/RequestStore.js b/src/stores/RequestStore.js
index 4140ca362..bbfe6f6df 100644
--- a/src/stores/RequestStore.js
+++ b/src/stores/RequestStore.js
@@ -2,6 +2,8 @@ import { action, computed, observable } from 'mobx';
2 2
3import Store from './lib/Store'; 3import Store from './lib/Store';
4 4
5const debug = require('debug')('Franz:RequestsStore');
6
5export default class RequestStore extends Store { 7export default class RequestStore extends Store {
6 @observable userInfoRequest; 8 @observable userInfoRequest;
7 @observable servicesRequest; 9 @observable servicesRequest;
@@ -52,7 +54,7 @@ export default class RequestStore extends Store {
52 } 54 }
53 55
54 this._autoRetry(); 56 this._autoRetry();
55 console.debug(`Retry required requests delayed in ${(delay) / 1000}s`); 57 debug(`Retry required requests delayed in ${(delay) / 1000}s`);
56 }, delay); 58 }, delay);
57 } 59 }
58 } 60 }
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index c38d0d9ee..cdb2db142 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';
@@ -10,6 +7,8 @@ import CachedRequest from './lib/CachedRequest';
10import { matchRoute } from '../helpers/routing-helpers'; 7import { matchRoute } from '../helpers/routing-helpers';
11import { gaEvent } from '../lib/analytics'; 8import { gaEvent } from '../lib/analytics';
12 9
10const debug = require('debug')('Franz:ServiceStore');
11
13export default class ServicesStore extends Store { 12export default class ServicesStore extends Store {
14 @observable allServicesRequest = new CachedRequest(this.api.services, 'all'); 13 @observable allServicesRequest = new CachedRequest(this.api.services, 'all');
15 @observable createServiceRequest = new Request(this.api.services, 'create'); 14 @observable createServiceRequest = new Request(this.api.services, 'create');
@@ -61,13 +60,20 @@ export default class ServicesStore extends Store {
61 this._mapActiveServiceToServiceModelReaction.bind(this), 60 this._mapActiveServiceToServiceModelReaction.bind(this),
62 this._saveActiveService.bind(this), 61 this._saveActiveService.bind(this),
63 this._logoutReaction.bind(this), 62 this._logoutReaction.bind(this),
64 this._shareSettingsWithServiceProcess.bind(this),
65 ]); 63 ]);
66 64
67 // Just bind this 65 // Just bind this
68 this._initializeServiceRecipeInWebview.bind(this); 66 this._initializeServiceRecipeInWebview.bind(this);
69 } 67 }
70 68
69 setup() {
70 // Single key reactions
71 reaction(
72 () => this.stores.settings.all.app.enableSpellchecking,
73 () => this._shareSettingsWithServiceProcess(),
74 );
75 }
76
71 @computed get all() { 77 @computed get all() {
72 if (this.stores.user.isLoggedIn) { 78 if (this.stores.user.isLoggedIn) {
73 const services = this.allServicesRequest.execute().result; 79 const services = this.allServicesRequest.execute().result;
@@ -84,7 +90,13 @@ export default class ServicesStore extends Store {
84 } 90 }
85 91
86 @computed get allDisplayed() { 92 @computed get allDisplayed() {
87 return this.stores.settings.all.showDisabledServices ? this.all : this.enabled; 93 return this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled;
94 }
95
96 // This is just used to avoid unnecessary rerendering of resource-heavy webviews
97 @computed get allDisplayedUnordered() {
98 const services = this.allServicesRequest.execute().result || [];
99 return this.stores.settings.all.app.showDisabledServices ? services : services.filter(service => service.isEnabled);
88 } 100 }
89 101
90 @computed get filtered() { 102 @computed get filtered() {
@@ -103,7 +115,7 @@ export default class ServicesStore extends Store {
103 return activeService; 115 return activeService;
104 } 116 }
105 117
106 console.warn('Service not available'); 118 debug('Service not available');
107 } 119 }
108 120
109 return null; 121 return null;
@@ -117,10 +129,10 @@ export default class ServicesStore extends Store {
117 const recipesStore = this.stores.recipes; 129 const recipesStore = this.stores.recipes;
118 130
119 if (recipesStore.isInstalled(recipeId)) { 131 if (recipesStore.isInstalled(recipeId)) {
120 console.debug('Recipe is installed'); 132 debug(`Recipe ${recipeId} is installed`);
121 this._redirectToAddServiceRoute(recipeId); 133 this._redirectToAddServiceRoute(recipeId);
122 } else { 134 } else {
123 console.warn('Recipe is not installed'); 135 debug(`Recipe ${recipeId} is not installed`);
124 // We access the RecipeStore action directly 136 // We access the RecipeStore action directly
125 // returns Promise instead of action 137 // returns Promise instead of action
126 await this.stores.recipes._install({ recipeId }); 138 await this.stores.recipes._install({ recipeId });
@@ -202,6 +214,14 @@ export default class ServicesStore extends Store {
202 await request._promise; 214 await request._promise;
203 this.actionStatus = request.result.status; 215 this.actionStatus = request.result.status;
204 216
217 if (service.isEnabled) {
218 this._sendIPCMessage({
219 serviceId,
220 channel: 'service-settings-update',
221 args: newData,
222 });
223 }
224
205 if (redirect) { 225 if (redirect) {
206 this.stores.router.push('/settings/services'); 226 this.stores.router.push('/settings/services');
207 gaEvent('Service', 'update', service.recipe.id); 227 gaEvent('Service', 'update', service.recipe.id);
@@ -326,7 +346,7 @@ export default class ServicesStore extends Store {
326 }); 346 });
327 } else if (channel === 'notification') { 347 } else if (channel === 'notification') {
328 const options = args[0].options; 348 const options = args[0].options;
329 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.isAppMuted) { 349 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.app.isAppMuted) {
330 Object.assign(options, { 350 Object.assign(options, {
331 silent: true, 351 silent: true,
332 }); 352 });
@@ -426,7 +446,7 @@ export default class ServicesStore extends Store {
426 } 446 }
427 447
428 @action _reorder({ oldIndex, newIndex }) { 448 @action _reorder({ oldIndex, newIndex }) {
429 const showDisabledServices = this.stores.settings.all.showDisabledServices; 449 const showDisabledServices = this.stores.settings.all.app.showDisabledServices;
430 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]); 450 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]);
431 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]); 451 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]);
432 452
@@ -487,7 +507,7 @@ export default class ServicesStore extends Store {
487 if (service) { 507 if (service) {
488 service.webview.openDevTools(); 508 service.webview.openDevTools();
489 } else { 509 } else {
490 console.warn('No service is active'); 510 debug('No service is active');
491 } 511 }
492 } 512 }
493 513
@@ -504,7 +524,8 @@ export default class ServicesStore extends Store {
504 524
505 if (service) { 525 if (service) {
506 this.actions.settings.update({ 526 this.actions.settings.update({
507 settings: { 527 type: 'service',
528 data: {
508 activeService: service.id, 529 activeService: service.id,
509 }, 530 },
510 }); 531 });
@@ -512,7 +533,7 @@ export default class ServicesStore extends Store {
512 } 533 }
513 534
514 _mapActiveServiceToServiceModelReaction() { 535 _mapActiveServiceToServiceModelReaction() {
515 const { activeService } = this.stores.settings.all; 536 const { activeService } = this.stores.settings.all.service;
516 if (this.allDisplayed.length) { 537 if (this.allDisplayed.length) {
517 this.allDisplayed.map(service => Object.assign(service, { 538 this.allDisplayed.map(service => Object.assign(service, {
518 isActive: activeService ? activeService === service.id : this.allDisplayed[0].id === service.id, 539 isActive: activeService ? activeService === service.id : this.allDisplayed[0].id === service.id,
@@ -521,7 +542,7 @@ export default class ServicesStore extends Store {
521 } 542 }
522 543
523 _getUnreadMessageCountReaction() { 544 _getUnreadMessageCountReaction() {
524 const showMessageBadgeWhenMuted = this.stores.settings.all.showMessageBadgeWhenMuted; 545 const showMessageBadgeWhenMuted = this.stores.settings.all.app.showMessageBadgeWhenMuted;
525 const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; 546 const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted;
526 547
527 const unreadDirectMessageCount = this.allDisplayed 548 const unreadDirectMessageCount = this.allDisplayed
@@ -545,7 +566,10 @@ export default class ServicesStore extends Store {
545 566
546 _logoutReaction() { 567 _logoutReaction() {
547 if (!this.stores.user.isLoggedIn) { 568 if (!this.stores.user.isLoggedIn) {
548 this.actions.settings.remove({ key: 'activeService' }); 569 this.actions.settings.remove({
570 type: 'service',
571 key: 'activeService',
572 });
549 this.allServicesRequest.invalidate().reset(); 573 this.allServicesRequest.invalidate().reset();
550 } 574 }
551 } 575 }
@@ -553,7 +577,7 @@ export default class ServicesStore extends Store {
553 _shareSettingsWithServiceProcess() { 577 _shareSettingsWithServiceProcess() {
554 this.actions.service.sendIPCMessageToAllServices({ 578 this.actions.service.sendIPCMessageToAllServices({
555 channel: 'settings-update', 579 channel: 'settings-update',
556 args: this.stores.settings.all, 580 args: this.stores.settings.all.app,
557 }); 581 });
558 } 582 }
559 583
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index b7d803398..f1b067115 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -1,12 +1,19 @@
1import { ipcRenderer } from 'electron'; 1import { remote } from 'electron';
2import { action, computed } from 'mobx'; 2import { action, computed, observable } from 'mobx';
3import localStorage from 'mobx-localstorage'; 3import localStorage from 'mobx-localstorage';
4 4
5import Store from './lib/Store'; 5import Store from './lib/Store';
6import { gaEvent } from '../lib/analytics';
7import SettingsModel from '../models/Settings'; 6import SettingsModel from '../models/Settings';
7import Request from './lib/Request';
8import CachedRequest from './lib/CachedRequest';
9
10const { systemPreferences } = remote;
11const debug = require('debug')('Franz:SettingsStore');
8 12
9export default class SettingsStore extends Store { 13export default class SettingsStore extends Store {
14 @observable appSettingsRequest = new CachedRequest(this.api.local, 'getAppSettings');
15 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings');
16
10 constructor(...args) { 17 constructor(...args) {
11 super(...args); 18 super(...args);
12 19
@@ -15,36 +22,110 @@ export default class SettingsStore extends Store {
15 this.actions.settings.remove.listen(this._remove.bind(this)); 22 this.actions.settings.remove.listen(this._remove.bind(this));
16 } 23 }
17 24
18 setup() { 25 async setup() {
19 this._shareSettingsWithMainProcess(); 26 // We need to wait until `appSettingsRequest` has been executed once, otherwise we can't patch the result. If we don't wait we'd run into an issue with mobx not reacting to changes of previously not existing keys
27 await this.appSettingsRequest._promise;
28 await this._migrate();
20 } 29 }
21 30
22 @computed get all() { 31 @computed get all() {
23 return new SettingsModel(localStorage.getItem('app') || {}); 32 return new SettingsModel({
33 app: this.appSettingsRequest.execute().result || {},
34 service: localStorage.getItem('service') || {},
35 group: localStorage.getItem('group') || {},
36 stats: localStorage.getItem('stats') || {},
37 migration: localStorage.getItem('migration') || {},
38 });
24 } 39 }
25 40
26 @action async _update({ settings }) { 41 @action async _update({ type, data }) {
27 const appSettings = this.all; 42 const appSettings = this.all;
28 localStorage.setItem('app', Object.assign(appSettings, settings)); 43 if (type !== 'app') {
29 44 debug('Update settings', type, data, this.all);
30 // We need a little hack to wait until everything is patched 45 localStorage.setItem(type, Object.assign(appSettings[type], data));
31 setTimeout(() => this._shareSettingsWithMainProcess(), 0); 46 } else {
47 debug('Update settings on file system', type, data);
48 this.updateAppSettingsRequest.execute(data);
32 49
33 gaEvent('Settings', 'update'); 50 this.appSettingsRequest.patch((result) => {
51 if (!result) return;
52 Object.assign(result, data);
53 });
54 }
34 } 55 }
35 56
36 @action async _remove({ key }) { 57 @action async _remove({ type, key }) {
37 const appSettings = this.all; 58 if (type === 'app') return; // app keys can't be deleted
59
60 const appSettings = this.all[type];
38 if (Object.hasOwnProperty.call(appSettings, key)) { 61 if (Object.hasOwnProperty.call(appSettings, key)) {
39 delete appSettings[key]; 62 delete appSettings[key];
40 localStorage.setItem('app', appSettings);
41 }
42 63
43 this._shareSettingsWithMainProcess(); 64 this.actions.settings.update({
65 type,
66 data: appSettings,
67 });
68 }
44 } 69 }
45 70
46 // Reactions 71 // Helper
47 _shareSettingsWithMainProcess() { 72 async _migrate() {
48 ipcRenderer.send('settings', this.all); 73 const legacySettings = localStorage.getItem('app') || {};
74
75 if (!this.all.migration['5.0.0-beta.17-settings']) {
76 this.actions.settings.update({
77 type: 'app',
78 data: {
79 autoLaunchInBackground: legacySettings.autoLaunchInBackground,
80 runInBackground: legacySettings.runInBackground,
81 enableSystemTray: legacySettings.enableSystemTray,
82 minimizeToSystemTray: legacySettings.minimizeToSystemTray,
83 isAppMuted: legacySettings.isAppMuted,
84 enableGPUAcceleration: legacySettings.enableGPUAcceleration,
85 showMessageBadgeWhenMuted: legacySettings.showMessageBadgeWhenMuted,
86 showDisabledServices: legacySettings.showDisabledServices,
87 enableSpellchecking: legacySettings.enableSpellchecking,
88 },
89 });
90
91 this.actions.settings.update({
92 type: 'service',
93 data: {
94 activeService: legacySettings.activeService,
95 },
96 });
97
98 this.actions.settings.update({
99 type: 'migration',
100 data: {
101 '5.0.0-beta.17-settings': true,
102 },
103 });
104
105 localStorage.removeItem('app');
106
107 debug('Migrated settings to split stores');
108 }
109
110 // Enable dark mode once
111 if (!this.all.migration['5.0.0-beta.19-settings']) {
112 this.actions.settings.update({
113 type: 'app',
114 data: {
115 darkMode: systemPreferences.isDarkMode(),
116 },
117 });
118
119 this.actions.settings.update({
120 type: 'migration',
121 data: {
122 '5.0.0-beta.19-settings': true,
123 },
124 });
125
126 localStorage.removeItem('app');
127
128 debug('Set up dark mode');
129 }
49 } 130 }
50} 131}
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
index 5e9cc9ba7..bee6c8bcf 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
@@ -26,7 +26,7 @@ export default class UIStore extends Store {
26 this.stores.router.push(settingsPath); 26 this.stores.router.push(settingsPath);
27 } 27 }
28 28
29 @action _closeSettings(): void { 29 @action _closeSettings() {
30 this.stores.router.push('/'); 30 this.stores.router.push('/');
31 } 31 }
32 32
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 7dbbd955b..9d8ac5657 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')('Franz: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}