diff options
author | 2021-02-08 10:34:45 +0100 | |
---|---|---|
committer | 2021-02-08 10:34:45 +0100 | |
commit | 035002ceedf78d5ec73eabc0df7f06139939b967 (patch) | |
tree | 1c0d1e9531bae05fb65d70b9ea25baf404b74fe1 /src/stores | |
parent | docs: add k0staa as a contributor (#1193) (diff) | |
download | ferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.tar.gz ferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.tar.zst ferdium-app-035002ceedf78d5ec73eabc0df7f06139939b967.zip |
Synchronize with Franz 5.6.0 (#1033)
Co-authored-by: FranzBot <i18n@meetfranz.com>
Co-authored-by: vantezzen <hello@vantezzen.io>
Co-authored-by: Makazzz <makazzzpro@live.ca>
Co-authored-by: Stefan Malzner <stefan@adlk.io>
Co-authored-by: Amine Mouafik <amine@mouafik.fr>
Diffstat (limited to 'src/stores')
-rw-r--r-- | src/stores/AppStore.js | 136 | ||||
-rw-r--r-- | src/stores/RecipesStore.js | 2 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 131 | ||||
-rw-r--r-- | src/stores/UserStore.js | 19 |
4 files changed, 234 insertions, 54 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index 153fdb2c8..869cfa9d6 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -3,7 +3,6 @@ import { | |||
3 | action, computed, observable, | 3 | action, computed, observable, |
4 | } from 'mobx'; | 4 | } from 'mobx'; |
5 | import moment from 'moment'; | 5 | import moment from 'moment'; |
6 | import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; | ||
7 | import AutoLaunch from 'auto-launch'; | 6 | import AutoLaunch from 'auto-launch'; |
8 | import prettyBytes from 'pretty-bytes'; | 7 | import prettyBytes from 'pretty-bytes'; |
9 | import ms from 'ms'; | 8 | import ms from 'ms'; |
@@ -27,7 +26,10 @@ import { sleep } from '../helpers/async-helpers'; | |||
27 | const debug = require('debug')('Ferdi:AppStore'); | 26 | const debug = require('debug')('Ferdi:AppStore'); |
28 | 27 | ||
29 | const { | 28 | const { |
30 | app, nativeTheme, screen, powerMonitor, | 29 | app, |
30 | screen, | ||
31 | powerMonitor, | ||
32 | nativeTheme, | ||
31 | } = remote; | 33 | } = remote; |
32 | 34 | ||
33 | const mainWindow = remote.getCurrentWindow(); | 35 | const mainWindow = remote.getCurrentWindow(); |
@@ -63,7 +65,7 @@ export default class AppStore extends Store { | |||
63 | 65 | ||
64 | @observable authRequestFailed = false; | 66 | @observable authRequestFailed = false; |
65 | 67 | ||
66 | @observable timeSuspensionStart; | 68 | @observable timeSuspensionStart = moment(); |
67 | 69 | ||
68 | @observable timeOfflineStart; | 70 | @observable timeOfflineStart; |
69 | 71 | ||
@@ -118,11 +120,19 @@ export default class AppStore extends Store { | |||
118 | window.addEventListener('focus', this.actions.service.focusActiveService); | 120 | window.addEventListener('focus', this.actions.service.focusActiveService); |
119 | 121 | ||
120 | // Online/Offline handling | 122 | // Online/Offline handling |
121 | window.addEventListener('online', () => { this.isOnline = true; }); | 123 | window.addEventListener('online', () => { |
122 | window.addEventListener('offline', () => { this.isOnline = false; }); | 124 | this.isOnline = true; |
125 | }); | ||
126 | window.addEventListener('offline', () => { | ||
127 | this.isOnline = false; | ||
128 | }); | ||
123 | 129 | ||
124 | mainWindow.on('enter-full-screen', () => { this.isFullScreen = true; }); | 130 | mainWindow.on('enter-full-screen', () => { |
125 | mainWindow.on('leave-full-screen', () => { this.isFullScreen = false; }); | 131 | this.isFullScreen = true; |
132 | }); | ||
133 | mainWindow.on('leave-full-screen', () => { | ||
134 | this.isFullScreen = false; | ||
135 | }); | ||
126 | 136 | ||
127 | 137 | ||
128 | this.isOnline = navigator.onLine; | 138 | this.isOnline = navigator.onLine; |
@@ -137,10 +147,16 @@ export default class AppStore extends Store { | |||
137 | setInterval(() => this._systemDND(), ms('5s')); | 147 | setInterval(() => this._systemDND(), ms('5s')); |
138 | 148 | ||
139 | this.fetchDataInterval = setInterval(() => { | 149 | this.fetchDataInterval = setInterval(() => { |
140 | this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); | 150 | this.stores.user.getUserInfoRequest.invalidate({ |
141 | this.stores.features.featuresRequest.invalidate({ immediately: true }); | 151 | immediately: true, |
142 | this.stores.news.latestNewsRequest.invalidate({ immediately: true }); | 152 | }); |
143 | }, ms('10m')); | 153 | this.stores.features.featuresRequest.invalidate({ |
154 | immediately: true, | ||
155 | }); | ||
156 | this.stores.news.latestNewsRequest.invalidate({ | ||
157 | immediately: true, | ||
158 | }); | ||
159 | }, ms('60m')); | ||
144 | 160 | ||
145 | // Check for updates once every 4 hours | 161 | // Check for updates once every 4 hours |
146 | setInterval(() => this._checkForUpdates(), CHECK_INTERVAL); | 162 | setInterval(() => this._checkForUpdates(), CHECK_INTERVAL); |
@@ -174,7 +190,9 @@ export default class AppStore extends Store { | |||
174 | // Handle deep linking (franz://) | 190 | // Handle deep linking (franz://) |
175 | ipcRenderer.on('navigateFromDeepLink', (event, data) => { | 191 | ipcRenderer.on('navigateFromDeepLink', (event, data) => { |
176 | debug('Navigate from deep link', data); | 192 | debug('Navigate from deep link', data); |
177 | let { url } = data; | 193 | let { |
194 | url, | ||
195 | } = data; | ||
178 | if (!url) return; | 196 | if (!url) return; |
179 | 197 | ||
180 | url = url.replace(/\/$/, ''); | 198 | url = url.replace(/\/$/, ''); |
@@ -207,13 +225,17 @@ export default class AppStore extends Store { | |||
207 | }); | 225 | }); |
208 | 226 | ||
209 | powerMonitor.on('resume', () => { | 227 | powerMonitor.on('resume', () => { |
210 | debug('System resumed, last suspended on', this.timeSuspensionStart.toString()); | 228 | debug('System resumed, last suspended on', this.timeSuspensionStart); |
229 | this.actions.service.resetLastPollTimer(); | ||
211 | 230 | ||
212 | if (this.timeSuspensionStart.add(10, 'm').isBefore(moment()) && this.stores.settings.app.get('reloadAfterResume')) { | 231 | if (this.timeSuspensionStart.add(10, 'm').isBefore(moment()) && this.stores.settings.app.get('reloadAfterResume')) { |
213 | debug('Reloading services, user info and features'); | 232 | debug('Reloading services, user info and features'); |
214 | 233 | ||
215 | setTimeout(() => { | 234 | setInterval(() => { |
216 | window.location.reload(); | 235 | debug('Reload app interval is starting'); |
236 | if (this.isOnline) { | ||
237 | window.location.reload(); | ||
238 | } | ||
217 | }, ms('2s')); | 239 | }, ms('2s')); |
218 | } | 240 | } |
219 | }); | 241 | }); |
@@ -251,8 +273,14 @@ export default class AppStore extends Store { | |||
251 | ferdi: { | 273 | ferdi: { |
252 | version: app.getVersion(), | 274 | version: app.getVersion(), |
253 | electron: process.versions.electron, | 275 | electron: process.versions.electron, |
254 | installedRecipes: this.stores.recipes.all.map(recipe => ({ id: recipe.id, version: recipe.version })), | 276 | installedRecipes: this.stores.recipes.all.map(recipe => ({ |
255 | devRecipes: this.stores.recipePreviews.dev.map(recipe => ({ id: recipe.id, version: recipe.version })), | 277 | id: recipe.id, |
278 | version: recipe.version, | ||
279 | })), | ||
280 | devRecipes: this.stores.recipePreviews.dev.map(recipe => ({ | ||
281 | id: recipe.id, | ||
282 | version: recipe.version, | ||
283 | })), | ||
256 | services: this.stores.services.all.map(service => ({ | 284 | services: this.stores.services.all.map(service => ({ |
257 | id: service.id, | 285 | id: service.id, |
258 | recipe: service.recipe.id, | 286 | recipe: service.recipe.id, |
@@ -264,7 +292,10 @@ export default class AppStore extends Store { | |||
264 | isDarkModeEnabled: service.isDarkModeEnabled, | 292 | isDarkModeEnabled: service.isDarkModeEnabled, |
265 | })), | 293 | })), |
266 | messages: this.stores.globalError.messages, | 294 | messages: this.stores.globalError.messages, |
267 | workspaces: this.stores.workspaces.workspaces.map(workspace => ({ id: workspace.id, services: workspace.services })), | 295 | workspaces: this.stores.workspaces.workspaces.map(workspace => ({ |
296 | id: workspace.id, | ||
297 | services: workspace.services, | ||
298 | })), | ||
268 | windowSettings: readJsonSync(path.join(app.getPath('userData'), 'window-state.json')), | 299 | windowSettings: readJsonSync(path.join(app.getPath('userData'), 'window-state.json')), |
269 | settings, | 300 | settings, |
270 | features: this.stores.features.features, | 301 | features: this.stores.features.features, |
@@ -275,7 +306,10 @@ export default class AppStore extends Store { | |||
275 | 306 | ||
276 | // Actions | 307 | // Actions |
277 | @action _notify({ | 308 | @action _notify({ |
278 | title, options, notificationId, serviceId = null, | 309 | title, |
310 | options, | ||
311 | notificationId, | ||
312 | serviceId = null, | ||
279 | }) { | 313 | }) { |
280 | if (this.stores.settings.all.app.isAppMuted) return; | 314 | if (this.stores.settings.all.app.isAppMuted) return; |
281 | 315 | ||
@@ -288,15 +322,17 @@ export default class AppStore extends Store { | |||
288 | 322 | ||
289 | debug('New notification', title, options); | 323 | debug('New notification', title, options); |
290 | 324 | ||
291 | notification.onclick = (e) => { | 325 | notification.onclick = () => { |
292 | if (serviceId) { | 326 | if (serviceId) { |
293 | this.actions.service.sendIPCMessage({ | 327 | this.actions.service.sendIPCMessage({ |
294 | channel: `notification-onclick:${notificationId}`, | 328 | channel: `notification-onclick:${notificationId}`, |
295 | args: e, | 329 | args: {}, |
296 | serviceId, | 330 | serviceId, |
297 | }); | 331 | }); |
298 | 332 | ||
299 | this.actions.service.setActive({ serviceId }); | 333 | this.actions.service.setActive({ |
334 | serviceId, | ||
335 | }); | ||
300 | mainWindow.show(); | 336 | mainWindow.show(); |
301 | if (app.mainWindow.isMinimized()) { | 337 | if (app.mainWindow.isMinimized()) { |
302 | mainWindow.restore(); | 338 | mainWindow.restore(); |
@@ -308,7 +344,10 @@ export default class AppStore extends Store { | |||
308 | }; | 344 | }; |
309 | } | 345 | } |
310 | 346 | ||
311 | @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) { | 347 | @action _setBadge({ |
348 | unreadDirectMessageCount, | ||
349 | unreadIndirectMessageCount, | ||
350 | }) { | ||
312 | let indicator = unreadDirectMessageCount; | 351 | let indicator = unreadDirectMessageCount; |
313 | 352 | ||
314 | if (indicator === 0 && unreadIndirectMessageCount !== 0) { | 353 | if (indicator === 0 && unreadIndirectMessageCount !== 0) { |
@@ -319,10 +358,14 @@ export default class AppStore extends Store { | |||
319 | indicator = parseInt(indicator, 10); | 358 | indicator = parseInt(indicator, 10); |
320 | } | 359 | } |
321 | 360 | ||
322 | ipcRenderer.send('updateAppIndicator', { indicator }); | 361 | ipcRenderer.send('updateAppIndicator', { |
362 | indicator, | ||
363 | }); | ||
323 | } | 364 | } |
324 | 365 | ||
325 | @action _launchOnStartup({ enable }) { | 366 | @action _launchOnStartup({ |
367 | enable, | ||
368 | }) { | ||
326 | this.autoLaunchOnStart = enable; | 369 | this.autoLaunchOnStart = enable; |
327 | 370 | ||
328 | try { | 371 | try { |
@@ -338,7 +381,9 @@ export default class AppStore extends Store { | |||
338 | } | 381 | } |
339 | } | 382 | } |
340 | 383 | ||
341 | @action _openExternalUrl({ url }) { | 384 | @action _openExternalUrl({ |
385 | url, | ||
386 | }) { | ||
342 | const parsedUrl = new URL(url); | 387 | const parsedUrl = new URL(url); |
343 | debug('open external url', parsedUrl); | 388 | debug('open external url', parsedUrl); |
344 | 389 | ||
@@ -348,14 +393,20 @@ export default class AppStore extends Store { | |||
348 | } | 393 | } |
349 | 394 | ||
350 | @action _checkForUpdates() { | 395 | @action _checkForUpdates() { |
351 | this.updateStatus = this.updateStatusTypes.CHECKING; | 396 | if (this.isOnline) { |
352 | ipcRenderer.send('autoUpdate', { action: 'check' }); | 397 | this.updateStatus = this.updateStatusTypes.CHECKING; |
398 | ipcRenderer.send('autoUpdate', { | ||
399 | action: 'check', | ||
400 | }); | ||
353 | 401 | ||
354 | this.actions.recipe.update(); | 402 | this.actions.recipe.update(); |
403 | } | ||
355 | } | 404 | } |
356 | 405 | ||
357 | @action _installUpdate() { | 406 | @action _installUpdate() { |
358 | ipcRenderer.send('autoUpdate', { action: 'install' }); | 407 | ipcRenderer.send('autoUpdate', { |
408 | action: 'install', | ||
409 | }); | ||
359 | } | 410 | } |
360 | 411 | ||
361 | @action _resetUpdateStatus() { | 412 | @action _resetUpdateStatus() { |
@@ -366,7 +417,10 @@ export default class AppStore extends Store { | |||
366 | this.healthCheckRequest.execute(); | 417 | this.healthCheckRequest.execute(); |
367 | } | 418 | } |
368 | 419 | ||
369 | @action _muteApp({ isMuted, overrideSystemMute = true }) { | 420 | @action _muteApp({ |
421 | isMuted, | ||
422 | overrideSystemMute = true, | ||
423 | }) { | ||
370 | this.isSystemMuteOverridden = overrideSystemMute; | 424 | this.isSystemMuteOverridden = overrideSystemMute; |
371 | this.actions.settings.update({ | 425 | this.actions.settings.update({ |
372 | type: 'app', | 426 | type: 'app', |
@@ -377,7 +431,9 @@ export default class AppStore extends Store { | |||
377 | } | 431 | } |
378 | 432 | ||
379 | @action _toggleMuteApp() { | 433 | @action _toggleMuteApp() { |
380 | this._muteApp({ isMuted: !this.stores.settings.all.app.isAppMuted }); | 434 | this._muteApp({ |
435 | isMuted: !this.stores.settings.all.app.isAppMuted, | ||
436 | }); | ||
381 | } | 437 | } |
382 | 438 | ||
383 | @action async _clearAllCache() { | 439 | @action async _clearAllCache() { |
@@ -391,7 +447,9 @@ export default class AppStore extends Store { | |||
391 | } catch (ex) { | 447 | } catch (ex) { |
392 | console.log('Error while deleting service partition directory - ', ex); | 448 | console.log('Error while deleting service partition directory - ', ex); |
393 | } | 449 | } |
394 | await Promise.all(this.stores.services.all.map(s => this.actions.service.clearCache({ serviceId: s.id }))); | 450 | await Promise.all(this.stores.services.all.map(s => this.actions.service.clearCache({ |
451 | serviceId: s.id, | ||
452 | }))); | ||
395 | 453 | ||
396 | await clearAppCache._promise; | 454 | await clearAppCache._promise; |
397 | 455 | ||
@@ -446,7 +504,10 @@ export default class AppStore extends Store { | |||
446 | const { showMessageBadgesEvenWhenMuted } = this.stores.ui; | 504 | const { showMessageBadgesEvenWhenMuted } = this.stores.ui; |
447 | 505 | ||
448 | if (!showMessageBadgesEvenWhenMuted) { | 506 | if (!showMessageBadgesEvenWhenMuted) { |
449 | this.actions.app.setBadge({ unreadDirectMessageCount: 0, unreadIndirectMessageCount: 0 }); | 507 | this.actions.app.setBadge({ |
508 | unreadDirectMessageCount: 0, | ||
509 | unreadIndirectMessageCount: 0, | ||
510 | }); | ||
450 | } | 511 | } |
451 | } | 512 | } |
452 | 513 | ||
@@ -491,8 +552,11 @@ export default class AppStore extends Store { | |||
491 | return autoLauncher.isEnabled() || false; | 552 | return autoLauncher.isEnabled() || false; |
492 | } | 553 | } |
493 | 554 | ||
494 | _systemDND() { | 555 | async _systemDND() { |
495 | const dnd = getDoNotDisturb(); | 556 | debug('Checking if Do Not Disturb Mode is on'); |
557 | const dnd = await ipcRenderer.invoke('get-dnd'); | ||
558 | debug('Do not disturb mode is', dnd); | ||
559 | // ipcRenderer.on('autoUpdate', (event, data) => { | ||
496 | if (dnd !== this.stores.settings.all.app.isAppMuted && !this.isSystemMuteOverridden) { | 560 | if (dnd !== this.stores.settings.all.app.isAppMuted && !this.isSystemMuteOverridden) { |
497 | this.actions.app.muteApp({ | 561 | this.actions.app.muteApp({ |
498 | isMuted: dnd, | 562 | isMuted: dnd, |
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js index cf5d0a074..965aa3a0a 100644 --- a/src/stores/RecipesStore.js +++ b/src/stores/RecipesStore.js | |||
@@ -66,7 +66,7 @@ export default class RecipesStore extends Store { | |||
66 | } | 66 | } |
67 | 67 | ||
68 | // Actions | 68 | // Actions |
69 | @action async _install({ recipeId }) { | 69 | async _install({ recipeId }) { |
70 | const recipe = await this.installRecipeRequest.execute(recipeId)._promise; | 70 | const recipe = await this.installRecipeRequest.execute(recipeId)._promise; |
71 | await this.allRecipesRequest.invalidate({ immediately: true })._promise; | 71 | await this.allRecipesRequest.invalidate({ immediately: true })._promise; |
72 | 72 | ||
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 448260638..21ed0a234 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -20,6 +20,8 @@ import { workspaceStore } from '../features/workspaces'; | |||
20 | import { serviceLimitStore } from '../features/serviceLimit'; | 20 | import { serviceLimitStore } from '../features/serviceLimit'; |
21 | import { RESTRICTION_TYPES } from '../models/Service'; | 21 | import { RESTRICTION_TYPES } from '../models/Service'; |
22 | import { KEEP_WS_LOADED_USID } from '../config'; | 22 | import { KEEP_WS_LOADED_USID } from '../config'; |
23 | import { TODOS_RECIPE_ID } from '../features/todos'; | ||
24 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | ||
23 | 25 | ||
24 | const debug = require('debug')('Ferdi:ServiceStore'); | 26 | const debug = require('debug')('Ferdi:ServiceStore'); |
25 | 27 | ||
@@ -82,7 +84,9 @@ export default class ServicesStore extends Store { | |||
82 | this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this)); | 84 | this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this)); |
83 | this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); | 85 | this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); |
84 | this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); | 86 | this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); |
85 | this.actions.service.setHibernation.listen(this._setHibernation.bind(this)); | 87 | this.actions.service.hibernate.listen(this._hibernate.bind(this)); |
88 | this.actions.service.awake.listen(this._awake.bind(this)); | ||
89 | this.actions.service.resetLastPollTimer.listen(this._resetLastPollTimer.bind(this)); | ||
86 | this.actions.service.shareSettingsWithServiceProcess.listen(this._shareSettingsWithServiceProcess.bind(this)); | 90 | this.actions.service.shareSettingsWithServiceProcess.listen(this._shareSettingsWithServiceProcess.bind(this)); |
87 | 91 | ||
88 | this.registerReactions([ | 92 | this.registerReactions([ |
@@ -93,6 +97,7 @@ export default class ServicesStore extends Store { | |||
93 | this._logoutReaction.bind(this), | 97 | this._logoutReaction.bind(this), |
94 | this._handleMuteSettings.bind(this), | 98 | this._handleMuteSettings.bind(this), |
95 | this._restrictServiceAccess.bind(this), | 99 | this._restrictServiceAccess.bind(this), |
100 | this._checkForActiveService.bind(this), | ||
96 | ]); | 101 | ]); |
97 | 102 | ||
98 | // Just bind this | 103 | // Just bind this |
@@ -155,16 +160,24 @@ export default class ServicesStore extends Store { | |||
155 | */ | 160 | */ |
156 | _serviceMaintenance() { | 161 | _serviceMaintenance() { |
157 | this.all.forEach((service) => { | 162 | this.all.forEach((service) => { |
158 | if (service.lastPoll && (service.lastPoll) - service.lastPollAnswer > ms('30s')) { | 163 | // Defines which services should be hibernated. |
159 | // If service did not reply for more than 30s try to reload. | 164 | if (!service.isActive && (Date.now() - service.lastUsed > ms('5m'))) { |
165 | // If service is stale for 5 min, hibernate it. | ||
166 | this._hibernate({ serviceId: service.id }); | ||
167 | } | ||
168 | |||
169 | if (service.lastPoll && (service.lastPoll - service.lastPollAnswer > ms('1m'))) { | ||
170 | // If service did not reply for more than 1m try to reload. | ||
160 | if (!service.isActive) { | 171 | if (!service.isActive) { |
161 | if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) { | 172 | if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) { |
162 | service.webview.reload(); | 173 | debug(`Reloading service: ${service.name} (${service.id}). Attempt: ${service.lostRecipeReloadAttempt}`); |
174 | // service.webview.reload(); | ||
163 | service.lostRecipeReloadAttempt += 1; | 175 | service.lostRecipeReloadAttempt += 1; |
164 | 176 | ||
165 | service.lostRecipeConnection = false; | 177 | service.lostRecipeConnection = false; |
166 | } | 178 | } |
167 | } else { | 179 | } else { |
180 | debug(`Service lost connection: ${service.name} (${service.id}).`); | ||
168 | service.lostRecipeConnection = true; | 181 | service.lostRecipeConnection = true; |
169 | } | 182 | } |
170 | } else { | 183 | } else { |
@@ -256,6 +269,14 @@ export default class ServicesStore extends Store { | |||
256 | return null; | 269 | return null; |
257 | } | 270 | } |
258 | 271 | ||
272 | @computed get isTodosServiceAdded() { | ||
273 | return this.allDisplayed.find(service => service.recipe.id === TODOS_RECIPE_ID && service.isEnabled) || null; | ||
274 | } | ||
275 | |||
276 | @computed get isTodosServiceActive() { | ||
277 | return this.active && this.active.recipe.id === TODOS_RECIPE_ID; | ||
278 | } | ||
279 | |||
259 | one(id) { | 280 | one(id) { |
260 | return this.all.find(service => service.id === id); | 281 | return this.all.find(service => service.id === id); |
261 | } | 282 | } |
@@ -265,10 +286,34 @@ export default class ServicesStore extends Store { | |||
265 | } | 286 | } |
266 | 287 | ||
267 | // Actions | 288 | // Actions |
268 | @action async _createService({ recipeId, serviceData, redirect = true }) { | 289 | async _createService({ |
290 | recipeId, serviceData, redirect = true, skipCleanup = false, | ||
291 | }) { | ||
269 | if (serviceLimitStore.userHasReachedServiceLimit) return; | 292 | if (serviceLimitStore.userHasReachedServiceLimit) return; |
270 | 293 | ||
271 | const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); | 294 | if (!this.stores.recipes.isInstalled(recipeId)) { |
295 | debug(`Recipe "${recipeId}" is not installed, installing recipe`); | ||
296 | await this.stores.recipes._install({ recipeId }); | ||
297 | debug(`Recipe "${recipeId}" installed`); | ||
298 | } | ||
299 | |||
300 | // set default values for serviceData | ||
301 | Object.assign({ | ||
302 | isEnabled: true, | ||
303 | isHibernationEnabled: false, | ||
304 | isNotificationEnabled: true, | ||
305 | isBadgeEnabled: true, | ||
306 | isMuted: false, | ||
307 | customIcon: false, | ||
308 | isDarkModeEnabled: false, | ||
309 | spellcheckerLanguage: SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage], | ||
310 | }, serviceData); | ||
311 | |||
312 | let data = serviceData; | ||
313 | |||
314 | if (!skipCleanup) { | ||
315 | data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); | ||
316 | } | ||
272 | 317 | ||
273 | const response = await this.createServiceRequest.execute(recipeId, data)._promise; | 318 | const response = await this.createServiceRequest.execute(recipeId, data)._promise; |
274 | 319 | ||
@@ -427,8 +472,13 @@ export default class ServicesStore extends Store { | |||
427 | this.all[index].isActive = false; | 472 | this.all[index].isActive = false; |
428 | }); | 473 | }); |
429 | service.isActive = true; | 474 | service.isActive = true; |
475 | this._awake({ serviceId: service.id }); | ||
430 | service.lastUsed = Date.now(); | 476 | service.lastUsed = Date.now(); |
431 | 477 | ||
478 | if (this.active.recipe.id === TODOS_RECIPE_ID && !this.stores.todos.settings.isFeatureEnabledByUser) { | ||
479 | this.actions.todos.toggleTodosFeatureVisibility(); | ||
480 | } | ||
481 | |||
432 | // Update list of last used services | 482 | // Update list of last used services |
433 | this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId); | 483 | this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId); |
434 | this.lastUsedServices.unshift(serviceId); | 484 | this.lastUsedServices.unshift(serviceId); |
@@ -617,10 +667,10 @@ export default class ServicesStore extends Store { | |||
617 | @action _sendIPCMessage({ serviceId, channel, args }) { | 667 | @action _sendIPCMessage({ serviceId, channel, args }) { |
618 | const service = this.one(serviceId); | 668 | const service = this.one(serviceId); |
619 | 669 | ||
620 | if (service.webview) { | 670 | // Make sure the args are clean, otherwise ElectronJS can't transmit them |
621 | // Make sure the args are clean, otherwise ElectronJS can't transmit them | 671 | const cleanArgs = JSON.parse(JSON.stringify(args)); |
622 | const cleanArgs = JSON.parse(JSON.stringify(args)); | ||
623 | 672 | ||
673 | if (service.webview) { | ||
624 | service.webview.send(channel, cleanArgs); | 674 | service.webview.send(channel, cleanArgs); |
625 | } | 675 | } |
626 | } | 676 | } |
@@ -659,8 +709,11 @@ export default class ServicesStore extends Store { | |||
659 | service.resetMessageCount(); | 709 | service.resetMessageCount(); |
660 | service.lostRecipeConnection = false; | 710 | service.lostRecipeConnection = false; |
661 | 711 | ||
662 | // service.webview.loadURL(service.url); | 712 | if (service.recipe.id === TODOS_RECIPE_ID) { |
663 | service.webview.reload(); | 713 | return this.actions.todos.reload(); |
714 | } | ||
715 | |||
716 | return service.webview.loadURL(service.url); | ||
664 | } | 717 | } |
665 | 718 | ||
666 | @action _reloadActive() { | 719 | @action _reloadActive() { |
@@ -743,23 +796,57 @@ export default class ServicesStore extends Store { | |||
743 | 796 | ||
744 | @action _openDevTools({ serviceId }) { | 797 | @action _openDevTools({ serviceId }) { |
745 | const service = this.one(serviceId); | 798 | const service = this.one(serviceId); |
746 | 799 | if (service.recipe.id === TODOS_RECIPE_ID) { | |
747 | service.webview.openDevTools(); | 800 | this.actions.todos.openDevTools(); |
801 | } else { | ||
802 | service.webview.openDevTools(); | ||
803 | } | ||
748 | } | 804 | } |
749 | 805 | ||
750 | @action _openDevToolsForActiveService() { | 806 | @action _openDevToolsForActiveService() { |
751 | const service = this.active; | 807 | const service = this.active; |
752 | 808 | ||
753 | if (service) { | 809 | if (service) { |
754 | service.webview.openDevTools(); | 810 | this._openDevTools({ serviceId: service.id }); |
755 | } else { | 811 | } else { |
756 | debug('No service is active'); | 812 | debug('No service is active'); |
757 | } | 813 | } |
758 | } | 814 | } |
759 | 815 | ||
760 | @action _setHibernation({ serviceId, hibernating }) { | 816 | @action _hibernate({ serviceId }) { |
817 | const service = this.one(serviceId); | ||
818 | if (service.isActive || !service.isHibernationEnabled) { | ||
819 | debug('Skipping service hibernation'); | ||
820 | return; | ||
821 | } | ||
822 | |||
823 | debug(`Hibernate ${service.name}`); | ||
824 | |||
825 | service.isHibernating = true; | ||
826 | } | ||
827 | |||
828 | @action _awake({ serviceId }) { | ||
761 | const service = this.one(serviceId); | 829 | const service = this.one(serviceId); |
762 | service.isHibernating = hibernating; | 830 | service.isHibernating = false; |
831 | service.liveFrom = Date.now(); | ||
832 | } | ||
833 | |||
834 | @action _resetLastPollTimer({ serviceId = null }) { | ||
835 | debug(`Reset last poll timer for ${serviceId ? `service: "${serviceId}"` : 'all services'}`); | ||
836 | |||
837 | const resetTimer = (service) => { | ||
838 | service.lastPollAnswer = Date.now(); | ||
839 | service.lastPoll = Date.now(); | ||
840 | }; | ||
841 | |||
842 | if (!serviceId) { | ||
843 | this.allDisplayed.forEach(service => resetTimer(service)); | ||
844 | } else { | ||
845 | const service = this.one(serviceId); | ||
846 | if (service) { | ||
847 | resetTimer(service); | ||
848 | } | ||
849 | } | ||
763 | } | 850 | } |
764 | 851 | ||
765 | // Reactions | 852 | // Reactions |
@@ -893,6 +980,18 @@ export default class ServicesStore extends Store { | |||
893 | }); | 980 | }); |
894 | } | 981 | } |
895 | 982 | ||
983 | _checkForActiveService() { | ||
984 | if (!this.stores.router.location || this.stores.router.location.pathname.includes('auth/signup')) { | ||
985 | return; | ||
986 | } | ||
987 | |||
988 | if (this.allDisplayed.findIndex(service => service.isActive) === -1 && this.allDisplayed.length !== 0) { | ||
989 | debug('No active service found, setting active service to index 0'); | ||
990 | |||
991 | this._setActive({ serviceId: this.allDisplayed[0].id }); | ||
992 | } | ||
993 | } | ||
994 | |||
896 | // Helper | 995 | // Helper |
897 | _initializeServiceRecipeInWebview(serviceId) { | 996 | _initializeServiceRecipeInWebview(serviceId) { |
898 | const service = this.one(serviceId); | 997 | const service = this.one(serviceId); |
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index c1ed2944a..7b4d39524 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -3,6 +3,7 @@ import moment from 'moment'; | |||
3 | import jwt from 'jsonwebtoken'; | 3 | import jwt from 'jsonwebtoken'; |
4 | import localStorage from 'mobx-localstorage'; | 4 | import localStorage from 'mobx-localstorage'; |
5 | import ms from 'ms'; | 5 | import ms from 'ms'; |
6 | import { remote } from 'electron'; | ||
6 | 7 | ||
7 | import { isDevMode } from '../environment'; | 8 | import { isDevMode } from '../environment'; |
8 | import Store from './lib/Store'; | 9 | import Store from './lib/Store'; |
@@ -11,6 +12,9 @@ import CachedRequest from './lib/CachedRequest'; | |||
11 | import { sleep } from '../helpers/async-helpers'; | 12 | import { sleep } from '../helpers/async-helpers'; |
12 | import { getPlan } from '../helpers/plan-helpers'; | 13 | import { getPlan } from '../helpers/plan-helpers'; |
13 | import { PLANS } from '../config'; | 14 | import { PLANS } from '../config'; |
15 | import { TODOS_PARTITION_ID } from '../features/todos'; | ||
16 | |||
17 | const { session } = remote; | ||
14 | 18 | ||
15 | const debug = require('debug')('Ferdi:UserStore'); | 19 | const debug = require('debug')('Ferdi:UserStore'); |
16 | 20 | ||
@@ -28,6 +32,8 @@ export default class UserStore extends Store { | |||
28 | 32 | ||
29 | PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`; | 33 | PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`; |
30 | 34 | ||
35 | SETUP_ROUTE = `${this.BASE_ROUTE}/signup/setup`; | ||
36 | |||
31 | IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`; | 37 | IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`; |
32 | 38 | ||
33 | INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`; | 39 | INVITE_ROUTE = `${this.BASE_ROUTE}/signup/invite`; |
@@ -127,6 +133,10 @@ export default class UserStore extends Store { | |||
127 | return this.PRICING_ROUTE; | 133 | return this.PRICING_ROUTE; |
128 | } | 134 | } |
129 | 135 | ||
136 | get setupRoute() { | ||
137 | return this.SETUP_ROUTE; | ||
138 | } | ||
139 | |||
130 | get inviteRoute() { | 140 | get inviteRoute() { |
131 | return this.INVITE_ROUTE; | 141 | return this.INVITE_ROUTE; |
132 | } | 142 | } |
@@ -227,7 +237,7 @@ export default class UserStore extends Store { | |||
227 | 237 | ||
228 | this._setUserData(authToken); | 238 | this._setUserData(authToken); |
229 | 239 | ||
230 | this.stores.router.push('/'); | 240 | this.stores.router.push(this.SETUP_ROUTE); |
231 | } | 241 | } |
232 | 242 | ||
233 | @action async _retrievePassword({ email }) { | 243 | @action async _retrievePassword({ email }) { |
@@ -285,6 +295,13 @@ export default class UserStore extends Store { | |||
285 | 295 | ||
286 | this.getUserInfoRequest.invalidate().reset(); | 296 | this.getUserInfoRequest.invalidate().reset(); |
287 | this.authToken = null; | 297 | this.authToken = null; |
298 | |||
299 | this.stores.services.allServicesRequest.invalidate().reset(); | ||
300 | |||
301 | if (this.stores.todos.isTodosEnabled) { | ||
302 | const sess = session.fromPartition(TODOS_PARTITION_ID); | ||
303 | sess.clearStorageData(); | ||
304 | } | ||
288 | } | 305 | } |
289 | 306 | ||
290 | @action async _importLegacyServices({ services }) { | 307 | @action async _importLegacyServices({ services }) { |