diff options
author | 2018-11-22 14:14:25 +0100 | |
---|---|---|
committer | 2018-11-22 14:14:25 +0100 | |
commit | 46b8c8c4b3a5b80e0187b284abc84566a7e784db (patch) | |
tree | 7fd378bcdd18e78c42dfeb61a15f89fd10106046 /src/stores | |
parent | ADD features loading spinner (diff) | |
parent | feat(App): Add option to enable dark mode for supported services (diff) | |
download | ferdium-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.js | 72 | ||||
-rw-r--r-- | src/stores/FeaturesStore.js | 6 | ||||
-rw-r--r-- | src/stores/RecipesStore.js | 8 | ||||
-rw-r--r-- | src/stores/RequestStore.js | 4 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 58 | ||||
-rw-r--r-- | src/stores/SettingsStore.js | 121 | ||||
-rw-r--r-- | src/stores/UIStore.js | 4 | ||||
-rw-r--r-- | src/stores/UserStore.js | 24 |
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 | ||
16 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; | 16 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; |
17 | 17 | ||
18 | const { app } = remote; | 18 | const debug = require('debug')('Franz:AppStore'); |
19 | |||
20 | const { app, systemPreferences } = remote; | ||
21 | |||
22 | const mainWindow = remote.getCurrentWindow(); | ||
19 | 23 | ||
20 | const defaultLocale = DEFAULT_APP_SETTINGS.locale; | 24 | const defaultLocale = DEFAULT_APP_SETTINGS.locale; |
21 | const autoLauncher = new AutoLaunch({ | 25 | const 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'; | |||
4 | import CachedRequest from './lib/CachedRequest'; | 4 | import CachedRequest from './lib/CachedRequest'; |
5 | 5 | ||
6 | export default class FeaturesStore extends Store { | 6 | export 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'; | |||
5 | import Request from './lib/Request'; | 5 | import Request from './lib/Request'; |
6 | import { matchRoute } from '../helpers/routing-helpers'; | 6 | import { matchRoute } from '../helpers/routing-helpers'; |
7 | 7 | ||
8 | const debug = require('debug')('Franz:RecipeStore'); | ||
9 | |||
8 | export default class RecipesStore extends Store { | 10 | export 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 | ||
3 | import Store from './lib/Store'; | 3 | import Store from './lib/Store'; |
4 | 4 | ||
5 | const debug = require('debug')('Franz:RequestsStore'); | ||
6 | |||
5 | export default class RequestStore extends Store { | 7 | export 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'; | 1 | import { action, reaction, computed, observable } from 'mobx'; |
2 | import { action, computed, observable } from 'mobx'; | ||
3 | import { debounce, remove } from 'lodash'; | 2 | import { debounce, remove } from 'lodash'; |
4 | // import path from 'path'; | ||
5 | // import fs from 'fs-extra'; | ||
6 | 3 | ||
7 | import Store from './lib/Store'; | 4 | import Store from './lib/Store'; |
8 | import Request from './lib/Request'; | 5 | import Request from './lib/Request'; |
@@ -10,6 +7,8 @@ import CachedRequest from './lib/CachedRequest'; | |||
10 | import { matchRoute } from '../helpers/routing-helpers'; | 7 | import { matchRoute } from '../helpers/routing-helpers'; |
11 | import { gaEvent } from '../lib/analytics'; | 8 | import { gaEvent } from '../lib/analytics'; |
12 | 9 | ||
10 | const debug = require('debug')('Franz:ServiceStore'); | ||
11 | |||
13 | export default class ServicesStore extends Store { | 12 | export 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 @@ | |||
1 | import { ipcRenderer } from 'electron'; | 1 | import { remote } from 'electron'; |
2 | import { action, computed } from 'mobx'; | 2 | import { action, computed, observable } from 'mobx'; |
3 | import localStorage from 'mobx-localstorage'; | 3 | import localStorage from 'mobx-localstorage'; |
4 | 4 | ||
5 | import Store from './lib/Store'; | 5 | import Store from './lib/Store'; |
6 | import { gaEvent } from '../lib/analytics'; | ||
7 | import SettingsModel from '../models/Settings'; | 6 | import SettingsModel from '../models/Settings'; |
7 | import Request from './lib/Request'; | ||
8 | import CachedRequest from './lib/CachedRequest'; | ||
9 | |||
10 | const { systemPreferences } = remote; | ||
11 | const debug = require('debug')('Franz:SettingsStore'); | ||
8 | 12 | ||
9 | export default class SettingsStore extends Store { | 13 | export 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'; | |||
9 | import CachedRequest from './lib/CachedRequest'; | 9 | import CachedRequest from './lib/CachedRequest'; |
10 | import { gaEvent } from '../lib/analytics'; | 10 | import { gaEvent } from '../lib/analytics'; |
11 | 11 | ||
12 | const debug = require('debug')('Franz:UserStore'); | ||
13 | |||
12 | // TODO: split stores into UserStore and AuthStore | 14 | // TODO: split stores into UserStore and AuthStore |
13 | export default class UserStore extends Store { | 15 | export 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 | } |