aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores')
-rw-r--r--src/stores/AppStore.js105
-rw-r--r--src/stores/FeaturesStore.js18
-rw-r--r--src/stores/NewsStore.js2
-rw-r--r--src/stores/PaymentStore.js68
-rw-r--r--src/stores/RecipePreviewsStore.js8
-rw-r--r--src/stores/RecipesStore.js17
-rw-r--r--src/stores/ServicesStore.js452
-rw-r--r--src/stores/SettingsStore.js109
-rw-r--r--src/stores/UIStore.js2
-rw-r--r--src/stores/UserStore.js69
-rw-r--r--src/stores/index.js6
-rw-r--r--src/stores/lib/CachedRequest.js4
-rw-r--r--src/stores/lib/Reaction.js4
-rw-r--r--src/stores/lib/Request.js2
-rw-r--r--src/stores/lib/Store.js6
15 files changed, 409 insertions, 463 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index bbb5e6305..1d706f1ef 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -1,26 +1,38 @@
1import { ipcRenderer, shell } from 'electron'; 1import { ipcRenderer, shell } from 'electron';
2import { 2import {
3 app, screen, powerMonitor, nativeTheme, getCurrentWindow, process as remoteProcess, 3 app,
4 screen,
5 powerMonitor,
6 nativeTheme,
7 getCurrentWindow,
8 process as remoteProcess,
4} from '@electron/remote'; 9} from '@electron/remote';
5import { action, computed, observable } from 'mobx'; 10import { action, computed, observable } from 'mobx';
6import moment from 'moment'; 11import moment from 'moment';
7import AutoLaunch from 'auto-launch'; 12import AutoLaunch from 'auto-launch';
8import ms from 'ms'; 13import ms from 'ms';
9import { URL } from 'url'; 14import { URL } from 'url';
10import path from 'path';
11import { readJsonSync } from 'fs-extra'; 15import { readJsonSync } from 'fs-extra';
12 16
13import Store from './lib/Store'; 17import Store from './lib/Store';
14import Request from './lib/Request'; 18import Request from './lib/Request';
15import { CHECK_INTERVAL } from '../config'; 19import { CHECK_INTERVAL } from '../config';
16import { 20import {
17 DEFAULT_APP_SETTINGS, isMac, ferdiVersion, electronVersion, osRelease, 21 DEFAULT_APP_SETTINGS,
22 isMac,
23 ferdiVersion,
24 electronVersion,
25 osRelease,
26 userDataPath,
18} from '../environment'; 27} from '../environment';
19import locales from '../i18n/translations'; 28import locales from '../i18n/translations';
20import { onVisibilityChange } from '../helpers/visibility-helper'; 29import { onVisibilityChange } from '../helpers/visibility-helper';
21import { getLocale } from '../helpers/i18n-helpers'; 30import { getLocale } from '../helpers/i18n-helpers';
22 31
23import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; 32import {
33 getServiceIdsFromPartitions,
34 removeServicePartitionDirectory,
35} from '../helpers/service-helpers';
24import { isValidExternalURL } from '../helpers/url-helpers'; 36import { isValidExternalURL } from '../helpers/url-helpers';
25import { sleep } from '../helpers/async-helpers'; 37import { sleep } from '../helpers/async-helpers';
26 38
@@ -49,7 +61,10 @@ export default class AppStore extends Store {
49 61
50 @observable healthCheckRequest = new Request(this.api.app, 'health'); 62 @observable healthCheckRequest = new Request(this.api.app, 'health');
51 63
52 @observable getAppCacheSizeRequest = new Request(this.api.local, 'getAppCacheSize'); 64 @observable getAppCacheSizeRequest = new Request(
65 this.api.local,
66 'getAppCacheSize',
67 );
53 68
54 @observable clearAppCacheRequest = new Request(this.api.local, 'clearCache'); 69 @observable clearAppCacheRequest = new Request(this.api.local, 'clearCache');
55 70
@@ -93,7 +108,9 @@ export default class AppStore extends Store {
93 this.actions.app.openExternalUrl.listen(this._openExternalUrl.bind(this)); 108 this.actions.app.openExternalUrl.listen(this._openExternalUrl.bind(this));
94 this.actions.app.checkForUpdates.listen(this._checkForUpdates.bind(this)); 109 this.actions.app.checkForUpdates.listen(this._checkForUpdates.bind(this));
95 this.actions.app.installUpdate.listen(this._installUpdate.bind(this)); 110 this.actions.app.installUpdate.listen(this._installUpdate.bind(this));
96 this.actions.app.resetUpdateStatus.listen(this._resetUpdateStatus.bind(this)); 111 this.actions.app.resetUpdateStatus.listen(
112 this._resetUpdateStatus.bind(this),
113 );
97 this.actions.app.healthCheck.listen(this._healthCheck.bind(this)); 114 this.actions.app.healthCheck.listen(this._healthCheck.bind(this));
98 this.actions.app.muteApp.listen(this._muteApp.bind(this)); 115 this.actions.app.muteApp.listen(this._muteApp.bind(this));
99 this.actions.app.toggleMuteApp.listen(this._toggleMuteApp.bind(this)); 116 this.actions.app.toggleMuteApp.listen(this._toggleMuteApp.bind(this));
@@ -183,9 +200,7 @@ export default class AppStore extends Store {
183 // Handle deep linking (ferdi://) 200 // Handle deep linking (ferdi://)
184 ipcRenderer.on('navigateFromDeepLink', (event, data) => { 201 ipcRenderer.on('navigateFromDeepLink', (event, data) => {
185 debug('Navigate from deep link', data); 202 debug('Navigate from deep link', data);
186 let { 203 let { url } = data;
187 url,
188 } = data;
189 if (!url) return; 204 if (!url) return;
190 205
191 url = url.replace(/\/$/, ''); 206 url = url.replace(/\/$/, '');
@@ -221,7 +236,10 @@ export default class AppStore extends Store {
221 debug('System resumed, last suspended on', this.timeSuspensionStart); 236 debug('System resumed, last suspended on', this.timeSuspensionStart);
222 this.actions.service.resetLastPollTimer(); 237 this.actions.service.resetLastPollTimer();
223 238
224 if (this.timeSuspensionStart.add(10, 'm').isBefore(moment()) && this.stores.settings.app.get('reloadAfterResume')) { 239 if (
240 this.timeSuspensionStart.add(10, 'm').isBefore(moment())
241 && this.stores.settings.app.get('reloadAfterResume')
242 ) {
225 debug('Reloading services, user info and features'); 243 debug('Reloading services, user info and features');
226 244
227 setInterval(() => { 245 setInterval(() => {
@@ -266,15 +284,15 @@ export default class AppStore extends Store {
266 ferdi: { 284 ferdi: {
267 version: ferdiVersion, 285 version: ferdiVersion,
268 electron: electronVersion, 286 electron: electronVersion,
269 installedRecipes: this.stores.recipes.all.map(recipe => ({ 287 installedRecipes: this.stores.recipes.all.map((recipe) => ({
270 id: recipe.id, 288 id: recipe.id,
271 version: recipe.version, 289 version: recipe.version,
272 })), 290 })),
273 devRecipes: this.stores.recipePreviews.dev.map(recipe => ({ 291 devRecipes: this.stores.recipePreviews.dev.map((recipe) => ({
274 id: recipe.id, 292 id: recipe.id,
275 version: recipe.version, 293 version: recipe.version,
276 })), 294 })),
277 services: this.stores.services.all.map(service => ({ 295 services: this.stores.services.all.map((service) => ({
278 id: service.id, 296 id: service.id,
279 recipe: service.recipe.id, 297 recipe: service.recipe.id,
280 isAttached: service.isAttached, 298 isAttached: service.isAttached,
@@ -285,11 +303,11 @@ export default class AppStore extends Store {
285 isDarkModeEnabled: service.isDarkModeEnabled, 303 isDarkModeEnabled: service.isDarkModeEnabled,
286 })), 304 })),
287 messages: this.stores.globalError.messages, 305 messages: this.stores.globalError.messages,
288 workspaces: this.stores.workspaces.workspaces.map(workspace => ({ 306 workspaces: this.stores.workspaces.workspaces.map((workspace) => ({
289 id: workspace.id, 307 id: workspace.id,
290 services: workspace.services, 308 services: workspace.services,
291 })), 309 })),
292 windowSettings: readJsonSync(path.join(app.getPath('userData'), 'window-state.json')), 310 windowSettings: readJsonSync(userDataPath('window-state.json')),
293 settings, 311 settings,
294 features: this.stores.features.features, 312 features: this.stores.features.features,
295 user: this.stores.user.data.id, 313 user: this.stores.user.data.id,
@@ -299,10 +317,7 @@ export default class AppStore extends Store {
299 317
300 // Actions 318 // Actions
301 @action _notify({ 319 @action _notify({
302 title, 320 title, options, notificationId, serviceId = null,
303 options,
304 notificationId,
305 serviceId = null,
306 }) { 321 }) {
307 if (this.stores.settings.all.app.isAppMuted) return; 322 if (this.stores.settings.all.app.isAppMuted) return;
308 323
@@ -339,15 +354,15 @@ export default class AppStore extends Store {
339 }; 354 };
340 } 355 }
341 356
342 @action _setBadge({ 357 @action _setBadge({ unreadDirectMessageCount, unreadIndirectMessageCount }) {
343 unreadDirectMessageCount,
344 unreadIndirectMessageCount,
345 }) {
346 let indicator = unreadDirectMessageCount; 358 let indicator = unreadDirectMessageCount;
347 359
348 if (indicator === 0 && unreadIndirectMessageCount !== 0) { 360 if (indicator === 0 && unreadIndirectMessageCount !== 0) {
349 indicator = '•'; 361 indicator = '•';
350 } else if (unreadDirectMessageCount === 0 && unreadIndirectMessageCount === 0) { 362 } else if (
363 unreadDirectMessageCount === 0
364 && unreadIndirectMessageCount === 0
365 ) {
351 indicator = 0; 366 indicator = 0;
352 } else { 367 } else {
353 indicator = parseInt(indicator, 10); 368 indicator = parseInt(indicator, 10);
@@ -358,9 +373,7 @@ export default class AppStore extends Store {
358 }); 373 });
359 } 374 }
360 375
361 @action _launchOnStartup({ 376 @action _launchOnStartup({ enable }) {
362 enable,
363 }) {
364 this.autoLaunchOnStart = enable; 377 this.autoLaunchOnStart = enable;
365 378
366 try { 379 try {
@@ -376,9 +389,7 @@ export default class AppStore extends Store {
376 } 389 }
377 } 390 }
378 391
379 @action _openExternalUrl({ 392 @action _openExternalUrl({ url }) {
380 url,
381 }) {
382 const parsedUrl = new URL(url); 393 const parsedUrl = new URL(url);
383 debug('open external url', parsedUrl); 394 debug('open external url', parsedUrl);
384 395
@@ -414,10 +425,7 @@ export default class AppStore extends Store {
414 this.healthCheckRequest.execute(); 425 this.healthCheckRequest.execute();
415 } 426 }
416 427
417 @action _muteApp({ 428 @action _muteApp({ isMuted, overrideSystemMute = true }) {
418 isMuted,
419 overrideSystemMute = true,
420 }) {
421 this.isSystemMuteOverridden = overrideSystemMute; 429 this.isSystemMuteOverridden = overrideSystemMute;
422 this.actions.settings.update({ 430 this.actions.settings.update({
423 type: 'app', 431 type: 'app',
@@ -437,16 +445,24 @@ export default class AppStore extends Store {
437 this.isClearingAllCache = true; 445 this.isClearingAllCache = true;
438 const clearAppCache = this.clearAppCacheRequest.execute(); 446 const clearAppCache = this.clearAppCacheRequest.execute();
439 const allServiceIds = await getServiceIdsFromPartitions(); 447 const allServiceIds = await getServiceIdsFromPartitions();
440 const allOrphanedServiceIds = allServiceIds.filter(id => !this.stores.services.all.find(s => id.replace('service-', '') === s.id)); 448 const allOrphanedServiceIds = allServiceIds.filter(
449 (id) => !this.stores.services.all.find(
450 (s) => id.replace('service-', '') === s.id,
451 ),
452 );
441 453
442 try { 454 try {
443 await Promise.all(allOrphanedServiceIds.map(id => removeServicePartitionDirectory(id))); 455 await Promise.all(
456 allOrphanedServiceIds.map((id) => removeServicePartitionDirectory(id)),
457 );
444 } catch (ex) { 458 } catch (ex) {
445 console.log('Error while deleting service partition directory - ', ex); 459 console.log('Error while deleting service partition directory - ', ex);
446 } 460 }
447 await Promise.all(this.stores.services.all.map(s => this.actions.service.clearCache({ 461 await Promise.all(
448 serviceId: s.id, 462 this.stores.services.all.map((s) => this.actions.service.clearCache({
449 }))); 463 serviceId: s.id,
464 })),
465 );
450 466
451 await clearAppCache._promise; 467 await clearAppCache._promise;
452 468
@@ -476,7 +492,11 @@ export default class AppStore extends Store {
476 locale = this.stores.user.data.locale; 492 locale = this.stores.user.data.locale;
477 } 493 }
478 494
479 if (locale && Object.prototype.hasOwnProperty.call(locales, locale) && locale !== this.locale) { 495 if (
496 locale
497 && Object.prototype.hasOwnProperty.call(locales, locale)
498 && locale !== this.locale
499 ) {
480 this.locale = locale; 500 this.locale = locale;
481 } else if (!locale) { 501 } else if (!locale) {
482 this.locale = this._getDefaultLocale(); 502 this.locale = this._getDefaultLocale();
@@ -553,7 +573,10 @@ export default class AppStore extends Store {
553 const dnd = await ipcRenderer.invoke('get-dnd'); 573 const dnd = await ipcRenderer.invoke('get-dnd');
554 debug('Do not disturb mode is', dnd); 574 debug('Do not disturb mode is', dnd);
555 // ipcRenderer.on('autoUpdate', (event, data) => { 575 // ipcRenderer.on('autoUpdate', (event, data) => {
556 if (dnd !== this.stores.settings.all.app.isAppMuted && !this.isSystemMuteOverridden) { 576 if (
577 dnd !== this.stores.settings.all.app.isAppMuted
578 && !this.isSystemMuteOverridden
579 ) {
557 this.actions.app.muteApp({ 580 this.actions.app.muteApp({
558 isMuted: dnd, 581 isMuted: dnd,
559 overrideSystemMute: false, 582 overrideSystemMute: false,
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index 2fee9bdda..ac623c258 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -1,15 +1,12 @@
1import { 1import {
2 computed, 2 computed,
3 observable, 3 observable,
4 reaction,
5 runInAction, 4 runInAction,
6} from 'mobx'; 5} from 'mobx';
7 6
8import Store from './lib/Store'; 7import Store from './lib/Store';
9import CachedRequest from './lib/CachedRequest'; 8import CachedRequest from './lib/CachedRequest';
10 9
11import delayApp from '../features/delayApp';
12import spellchecker from '../features/spellchecker';
13import serviceProxy from '../features/serviceProxy'; 10import serviceProxy from '../features/serviceProxy';
14import basicAuth from '../features/basicAuth'; 11import basicAuth from '../features/basicAuth';
15import workspaces from '../features/workspaces'; 12import workspaces from '../features/workspaces';
@@ -19,12 +16,9 @@ import publishDebugInfo from '../features/publishDebugInfo';
19import shareFranz from '../features/shareFranz'; 16import shareFranz from '../features/shareFranz';
20import announcements from '../features/announcements'; 17import announcements from '../features/announcements';
21import settingsWS from '../features/settingsWS'; 18import settingsWS from '../features/settingsWS';
22import serviceLimit from '../features/serviceLimit';
23import communityRecipes from '../features/communityRecipes'; 19import communityRecipes from '../features/communityRecipes';
24import todos from '../features/todos'; 20import todos from '../features/todos';
25import appearance from '../features/appearance'; 21import appearance from '../features/appearance';
26import planSelection from '../features/planSelection';
27import trialStatusBar from '../features/trialStatusBar';
28 22
29import { DEFAULT_FEATURES_CONFIG } from '../config'; 23import { DEFAULT_FEATURES_CONFIG } from '../config';
30 24
@@ -43,13 +37,6 @@ export default class FeaturesStore extends Store {
43 37
44 await this.featuresRequest._promise; 38 await this.featuresRequest._promise;
45 setTimeout(this._setupFeatures.bind(this), 1); 39 setTimeout(this._setupFeatures.bind(this), 1);
46
47 // single key reaction
48 reaction(() => this.stores.user.data.isPremium, () => {
49 if (this.stores.user.isLoggedIn) {
50 this.featuresRequest.invalidate({ immediately: true });
51 }
52 });
53 } 40 }
54 41
55 @computed get anonymousFeatures() { 42 @computed get anonymousFeatures() {
@@ -80,8 +67,6 @@ export default class FeaturesStore extends Store {
80 } 67 }
81 68
82 _setupFeatures() { 69 _setupFeatures() {
83 delayApp(this.stores, this.actions);
84 spellchecker(this.stores, this.actions);
85 serviceProxy(this.stores, this.actions); 70 serviceProxy(this.stores, this.actions);
86 basicAuth(this.stores, this.actions); 71 basicAuth(this.stores, this.actions);
87 workspaces(this.stores, this.actions); 72 workspaces(this.stores, this.actions);
@@ -91,11 +76,8 @@ export default class FeaturesStore extends Store {
91 shareFranz(this.stores, this.actions); 76 shareFranz(this.stores, this.actions);
92 announcements(this.stores, this.actions); 77 announcements(this.stores, this.actions);
93 settingsWS(this.stores, this.actions); 78 settingsWS(this.stores, this.actions);
94 serviceLimit(this.stores, this.actions);
95 communityRecipes(this.stores, this.actions); 79 communityRecipes(this.stores, this.actions);
96 todos(this.stores, this.actions); 80 todos(this.stores, this.actions);
97 appearance(this.stores, this.actions); 81 appearance(this.stores, this.actions);
98 planSelection(this.stores, this.actions);
99 trialStatusBar(this.stores, this.actions);
100 } 82 }
101} 83}
diff --git a/src/stores/NewsStore.js b/src/stores/NewsStore.js
index 86e092592..66a17cb29 100644
--- a/src/stores/NewsStore.js
+++ b/src/stores/NewsStore.js
@@ -38,7 +38,7 @@ export default class NewsStore extends Store {
38 38
39 this.latestNewsRequest.invalidate().patch((result) => { 39 this.latestNewsRequest.invalidate().patch((result) => {
40 // TODO: check if we can use mobx.array remove 40 // TODO: check if we can use mobx.array remove
41 remove(result, n => n.id === newsId); 41 remove(result, (n) => n.id === newsId);
42 }); 42 });
43 } 43 }
44 44
diff --git a/src/stores/PaymentStore.js b/src/stores/PaymentStore.js
deleted file mode 100644
index 05bb5b3d0..000000000
--- a/src/stores/PaymentStore.js
+++ /dev/null
@@ -1,68 +0,0 @@
1import { action, observable, computed } from 'mobx';
2import { BrowserWindow, getCurrentWindow } from '@electron/remote';
3
4import Store from './lib/Store';
5import CachedRequest from './lib/CachedRequest';
6import Request from './lib/Request';
7
8export default class PaymentStore extends Store {
9 @observable plansRequest = new CachedRequest(this.api.payment, 'plans');
10
11 @observable createHostedPageRequest = new Request(this.api.payment, 'getHostedPage');
12
13 constructor(...args) {
14 super(...args);
15
16 this.actions.payment.createHostedPage.listen(this._createHostedPage.bind(this));
17 this.actions.payment.upgradeAccount.listen(this._upgradeAccount.bind(this));
18 }
19
20 @computed get plan() {
21 if (this.plansRequest.isError) {
22 return {};
23 }
24 return this.plansRequest.execute().result || {};
25 }
26
27 @action _createHostedPage({ planId }) {
28 const request = this.createHostedPageRequest.execute(planId);
29
30 return request;
31 }
32
33 @action _upgradeAccount({ planId, onCloseWindow = () => null }) {
34 let hostedPageURL = this.stores.features.features.subscribeURL;
35
36 const parsedUrl = new URL(hostedPageURL);
37 const params = new URLSearchParams(parsedUrl.search.slice(1));
38
39 params.set('plan', planId);
40
41 hostedPageURL = this.stores.user.getAuthURL(`${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`);
42
43 const win = new BrowserWindow({
44 parent: getCurrentWindow(),
45 modal: true,
46 title: '🔒 Upgrade Your Franz Account',
47 width: 800,
48 height: window.innerHeight - 100,
49 maxWidth: 800,
50 minWidth: 600,
51 autoHideMenuBar: true,
52 webPreferences: {
53 nodeIntegration: true,
54 webviewTag: true,
55 enableRemoteModule: true,
56 contextIsolation: false,
57 },
58 });
59 win.loadURL(`file://${__dirname}/../index.html#/payment/${encodeURIComponent(hostedPageURL)}`);
60
61 win.on('closed', () => {
62 this.stores.user.getUserInfoRequest.invalidate({ immediately: true });
63 this.stores.features.featuresRequest.invalidate({ immediately: true });
64
65 onCloseWindow();
66 });
67 }
68}
diff --git a/src/stores/RecipePreviewsStore.js b/src/stores/RecipePreviewsStore.js
index 989e1124a..f4e39306c 100644
--- a/src/stores/RecipePreviewsStore.js
+++ b/src/stores/RecipePreviewsStore.js
@@ -7,8 +7,6 @@ import Request from './lib/Request';
7export default class RecipePreviewsStore extends Store { 7export default class RecipePreviewsStore extends Store {
8 @observable allRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'all'); 8 @observable allRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'all');
9 9
10 @observable featuredRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'featured');
11
12 @observable searchRecipePreviewsRequest = new Request(this.api.recipePreviews, 'search'); 10 @observable searchRecipePreviewsRequest = new Request(this.api.recipePreviews, 'search');
13 11
14 constructor(...args) { 12 constructor(...args) {
@@ -22,16 +20,12 @@ export default class RecipePreviewsStore extends Store {
22 return this.allRecipePreviewsRequest.execute().result || []; 20 return this.allRecipePreviewsRequest.execute().result || [];
23 } 21 }
24 22
25 @computed get featured() {
26 return this.featuredRecipePreviewsRequest.execute().result || [];
27 }
28
29 @computed get searchResults() { 23 @computed get searchResults() {
30 return this.searchRecipePreviewsRequest.result || []; 24 return this.searchRecipePreviewsRequest.result || [];
31 } 25 }
32 26
33 @computed get dev() { 27 @computed get dev() {
34 return this.stores.recipes.all.filter(r => r.local); 28 return this.stores.recipes.all.filter((r) => r.local);
35 } 29 }
36 30
37 // Actions 31 // Actions
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
index b49fb72d9..d2acebb75 100644
--- a/src/stores/RecipesStore.js
+++ b/src/stores/RecipesStore.js
@@ -1,13 +1,12 @@
1import { action, computed, observable } from 'mobx'; 1import { action, computed, observable } from 'mobx';
2import fs from 'fs-extra'; 2import { readJSONSync } from 'fs-extra';
3import path from 'path';
4import semver from 'semver'; 3import semver from 'semver';
5 4
6import Store from './lib/Store'; 5import Store from './lib/Store';
7import CachedRequest from './lib/CachedRequest'; 6import CachedRequest from './lib/CachedRequest';
8import Request from './lib/Request'; 7import Request from './lib/Request';
9import { matchRoute } from '../helpers/routing-helpers'; 8import { matchRoute } from '../helpers/routing-helpers';
10import { RECIPES_PATH } from '../environment'; 9import { asarRecipesPath } from '../environment';
11 10
12const debug = require('debug')('Ferdi:RecipeStore'); 11const debug = require('debug')('Ferdi:RecipeStore');
13 12
@@ -54,11 +53,11 @@ export default class RecipesStore extends Store {
54 } 53 }
55 54
56 @computed get recipeIdForServices() { 55 @computed get recipeIdForServices() {
57 return this.stores.services.all.map(s => s.recipe.id); 56 return this.stores.services.all.map((s) => s.recipe.id);
58 } 57 }
59 58
60 one(id) { 59 one(id) {
61 return this.all.find(recipe => recipe.id === id); 60 return this.all.find((recipe) => recipe.id === id);
62 } 61 }
63 62
64 isInstalled(id) { 63 isInstalled(id) {
@@ -78,7 +77,7 @@ export default class RecipesStore extends Store {
78 const recipes = {}; 77 const recipes = {};
79 78
80 // Hackfix, reference this.all to fetch services 79 // Hackfix, reference this.all to fetch services
81 debug(`Check Recipe updates for ${this.all.map(recipe => recipe.id)}`); 80 debug(`Check Recipe updates for ${this.all.map((recipe) => recipe.id)}`);
82 81
83 recipeIds.forEach((r) => { 82 recipeIds.forEach((r) => {
84 const recipe = this.one(r); 83 const recipe = this.one(r);
@@ -90,15 +89,15 @@ export default class RecipesStore extends Store {
90 const remoteUpdates = await this.getRecipeUpdatesRequest.execute(recipes)._promise; 89 const remoteUpdates = await this.getRecipeUpdatesRequest.execute(recipes)._promise;
91 90
92 // Check for local updates 91 // Check for local updates
93 const allJsonFile = path.join(RECIPES_PATH, 'all.json'); 92 const allJsonFile = asarRecipesPath('all.json');
94 const allJson = await fs.readJSON(allJsonFile); 93 const allJson = readJSONSync(allJsonFile);
95 const localUpdates = []; 94 const localUpdates = [];
96 95
97 Object.keys(recipes).forEach((recipe) => { 96 Object.keys(recipes).forEach((recipe) => {
98 const version = recipes[recipe]; 97 const version = recipes[recipe];
99 98
100 // Find recipe in local recipe repository 99 // Find recipe in local recipe repository
101 const localRecipe = allJson.find(r => r.id === recipe); 100 const localRecipe = allJson.find((r) => r.id === recipe);
102 101
103 if (localRecipe && semver.lt(version, localRecipe.version)) { 102 if (localRecipe && semver.lt(version, localRecipe.version)) {
104 localUpdates.push(recipe); 103 localUpdates.push(recipe);
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 9b69cb7c6..4ccb995ae 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -1,25 +1,21 @@
1import { shell } from 'electron'; 1import { shell } from 'electron';
2import { 2import { action, reaction, computed, observable } from 'mobx';
3 action,
4 reaction,
5 computed,
6 observable,
7} from 'mobx';
8import { debounce, remove } from 'lodash'; 3import { debounce, remove } from 'lodash';
9import ms from 'ms'; 4import ms from 'ms';
10import { app } from '@electron/remote'; 5import { app } from '@electron/remote';
11import fs from 'fs-extra'; 6import { ensureFileSync, pathExistsSync, writeFileSync } from 'fs-extra';
12import path from 'path'; 7import { join } from 'path';
13 8
14import Store from './lib/Store'; 9import Store from './lib/Store';
15import Request from './lib/Request'; 10import Request from './lib/Request';
16import CachedRequest from './lib/CachedRequest'; 11import CachedRequest from './lib/CachedRequest';
17import { matchRoute } from '../helpers/routing-helpers'; 12import { matchRoute } from '../helpers/routing-helpers';
18import { isInTimeframe } from '../helpers/schedule-helpers'; 13import { isInTimeframe } from '../helpers/schedule-helpers';
19import { getRecipeDirectory, getDevRecipeDirectory } from '../helpers/recipe-helpers'; 14import {
15 getRecipeDirectory,
16 getDevRecipeDirectory,
17} from '../helpers/recipe-helpers';
20import { workspaceStore } from '../features/workspaces'; 18import { workspaceStore } from '../features/workspaces';
21import { serviceLimitStore } from '../features/serviceLimit';
22import { RESTRICTION_TYPES } from '../models/Service';
23import { KEEP_WS_LOADED_USID } from '../config'; 19import { KEEP_WS_LOADED_USID } from '../config';
24import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 20import { SPELLCHECKER_LOCALES } from '../i18n/languages';
25 21
@@ -32,7 +28,10 @@ export default class ServicesStore extends Store {
32 28
33 @observable updateServiceRequest = new Request(this.api.services, 'update'); 29 @observable updateServiceRequest = new Request(this.api.services, 'update');
34 30
35 @observable reorderServicesRequest = new Request(this.api.services, 'reorder'); 31 @observable reorderServicesRequest = new Request(
32 this.api.services,
33 'reorder',
34 );
36 35
37 @observable deleteServiceRequest = new Request(this.api.services, 'delete'); 36 @observable deleteServiceRequest = new Request(this.api.services, 'delete');
38 37
@@ -53,22 +52,36 @@ export default class ServicesStore extends Store {
53 this.actions.service.blurActive.listen(this._blurActive.bind(this)); 52 this.actions.service.blurActive.listen(this._blurActive.bind(this));
54 this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this)); 53 this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this));
55 this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this)); 54 this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this));
56 this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); 55 this.actions.service.showAddServiceInterface.listen(
56 this._showAddServiceInterface.bind(this),
57 );
57 this.actions.service.createService.listen(this._createService.bind(this)); 58 this.actions.service.createService.listen(this._createService.bind(this));
58 this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this)); 59 this.actions.service.createFromLegacyService.listen(
60 this._createFromLegacyService.bind(this),
61 );
59 this.actions.service.updateService.listen(this._updateService.bind(this)); 62 this.actions.service.updateService.listen(this._updateService.bind(this));
60 this.actions.service.deleteService.listen(this._deleteService.bind(this)); 63 this.actions.service.deleteService.listen(this._deleteService.bind(this));
61 this.actions.service.openRecipeFile.listen(this._openRecipeFile.bind(this)); 64 this.actions.service.openRecipeFile.listen(this._openRecipeFile.bind(this));
62 this.actions.service.clearCache.listen(this._clearCache.bind(this)); 65 this.actions.service.clearCache.listen(this._clearCache.bind(this));
63 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); 66 this.actions.service.setWebviewReference.listen(
67 this._setWebviewReference.bind(this),
68 );
64 this.actions.service.detachService.listen(this._detachService.bind(this)); 69 this.actions.service.detachService.listen(this._detachService.bind(this));
65 this.actions.service.focusService.listen(this._focusService.bind(this)); 70 this.actions.service.focusService.listen(this._focusService.bind(this));
66 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); 71 this.actions.service.focusActiveService.listen(
72 this._focusActiveService.bind(this),
73 );
67 this.actions.service.toggleService.listen(this._toggleService.bind(this)); 74 this.actions.service.toggleService.listen(this._toggleService.bind(this));
68 this.actions.service.handleIPCMessage.listen(this._handleIPCMessage.bind(this)); 75 this.actions.service.handleIPCMessage.listen(
76 this._handleIPCMessage.bind(this),
77 );
69 this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this)); 78 this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this));
70 this.actions.service.sendIPCMessageToAllServices.listen(this._sendIPCMessageToAllServices.bind(this)); 79 this.actions.service.sendIPCMessageToAllServices.listen(
71 this.actions.service.setUnreadMessageCount.listen(this._setUnreadMessageCount.bind(this)); 80 this._sendIPCMessageToAllServices.bind(this),
81 );
82 this.actions.service.setUnreadMessageCount.listen(
83 this._setUnreadMessageCount.bind(this),
84 );
72 this.actions.service.openWindow.listen(this._openWindow.bind(this)); 85 this.actions.service.openWindow.listen(this._openWindow.bind(this));
73 this.actions.service.filter.listen(this._filter.bind(this)); 86 this.actions.service.filter.listen(this._filter.bind(this));
74 this.actions.service.resetFilter.listen(this._resetFilter.bind(this)); 87 this.actions.service.resetFilter.listen(this._resetFilter.bind(this));
@@ -76,16 +89,27 @@ export default class ServicesStore extends Store {
76 this.actions.service.reload.listen(this._reload.bind(this)); 89 this.actions.service.reload.listen(this._reload.bind(this));
77 this.actions.service.reloadActive.listen(this._reloadActive.bind(this)); 90 this.actions.service.reloadActive.listen(this._reloadActive.bind(this));
78 this.actions.service.reloadAll.listen(this._reloadAll.bind(this)); 91 this.actions.service.reloadAll.listen(this._reloadAll.bind(this));
79 this.actions.service.reloadUpdatedServices.listen(this._reloadUpdatedServices.bind(this)); 92 this.actions.service.reloadUpdatedServices.listen(
93 this._reloadUpdatedServices.bind(this),
94 );
80 this.actions.service.reorder.listen(this._reorder.bind(this)); 95 this.actions.service.reorder.listen(this._reorder.bind(this));
81 this.actions.service.toggleNotifications.listen(this._toggleNotifications.bind(this)); 96 this.actions.service.toggleNotifications.listen(
97 this._toggleNotifications.bind(this),
98 );
82 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this)); 99 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this));
100 this.actions.service.toggleDarkMode.listen(this._toggleDarkMode.bind(this));
83 this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); 101 this.actions.service.openDevTools.listen(this._openDevTools.bind(this));
84 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); 102 this.actions.service.openDevToolsForActiveService.listen(
103 this._openDevToolsForActiveService.bind(this),
104 );
85 this.actions.service.hibernate.listen(this._hibernate.bind(this)); 105 this.actions.service.hibernate.listen(this._hibernate.bind(this));
86 this.actions.service.awake.listen(this._awake.bind(this)); 106 this.actions.service.awake.listen(this._awake.bind(this));
87 this.actions.service.resetLastPollTimer.listen(this._resetLastPollTimer.bind(this)); 107 this.actions.service.resetLastPollTimer.listen(
88 this.actions.service.shareSettingsWithServiceProcess.listen(this._shareSettingsWithServiceProcess.bind(this)); 108 this._resetLastPollTimer.bind(this),
109 );
110 this.actions.service.shareSettingsWithServiceProcess.listen(
111 this._shareSettingsWithServiceProcess.bind(this),
112 );
89 113
90 this.registerReactions([ 114 this.registerReactions([
91 this._focusServiceReaction.bind(this), 115 this._focusServiceReaction.bind(this),
@@ -94,7 +118,6 @@ export default class ServicesStore extends Store {
94 this._saveActiveService.bind(this), 118 this._saveActiveService.bind(this),
95 this._logoutReaction.bind(this), 119 this._logoutReaction.bind(this),
96 this._handleMuteSettings.bind(this), 120 this._handleMuteSettings.bind(this),
97 this._restrictServiceAccess.bind(this),
98 this._checkForActiveService.bind(this), 121 this._checkForActiveService.bind(this),
99 ]); 122 ]);
100 123
@@ -167,18 +190,42 @@ export default class ServicesStore extends Store {
167 * Run various maintenance tasks on services 190 * Run various maintenance tasks on services
168 */ 191 */
169 _serviceMaintenance() { 192 _serviceMaintenance() {
170 this.all.forEach((service) => { 193 this.all.forEach(service => {
171 // Defines which services should be hibernated. 194 // Defines which services should be hibernated or woken up
172 if (!service.isActive && (Date.now() - service.lastUsed > ms('5m'))) { 195 if (!service.isActive) {
173 // If service is stale for 5 min, hibernate it. 196 if (
174 this._hibernate({ serviceId: service.id }); 197 !service.lastHibernated &&
198 Date.now() - service.lastUsed >
199 ms(`${this.stores.settings.all.app.hibernationStrategy}s`)
200 ) {
201 // If service is stale, hibernate it.
202 this._hibernate({ serviceId: service.id });
203 }
204
205 if (
206 service.lastHibernated &&
207 Number(this.stores.settings.all.app.wakeUpStrategy) > 0
208 ) {
209 // If service is in hibernation and the wakeup time has elapsed, wake it.
210 if (
211 Date.now() - service.lastHibernated >
212 ms(`${this.stores.settings.all.app.wakeUpStrategy}s`)
213 ) {
214 this._awake({ serviceId: service.id });
215 }
216 }
175 } 217 }
176 218
177 if (service.lastPoll && (service.lastPoll - service.lastPollAnswer > ms('1m'))) { 219 if (
220 service.lastPoll &&
221 service.lastPoll - service.lastPollAnswer > ms('1m')
222 ) {
178 // If service did not reply for more than 1m try to reload. 223 // If service did not reply for more than 1m try to reload.
179 if (!service.isActive) { 224 if (!service.isActive) {
180 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) { 225 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) {
181 debug(`Reloading service: ${service.name} (${service.id}). Attempt: ${service.lostRecipeReloadAttempt}`); 226 debug(
227 `Reloading service: ${service.name} (${service.id}). Attempt: ${service.lostRecipeReloadAttempt}`,
228 );
182 // service.webview.reload(); 229 // service.webview.reload();
183 service.lostRecipeReloadAttempt += 1; 230 service.lostRecipeReloadAttempt += 1;
184 231
@@ -200,10 +247,16 @@ export default class ServicesStore extends Store {
200 if (this.stores.user.isLoggedIn) { 247 if (this.stores.user.isLoggedIn) {
201 const services = this.allServicesRequest.execute().result; 248 const services = this.allServicesRequest.execute().result;
202 if (services) { 249 if (services) {
203 return observable(services.slice().slice().sort((a, b) => a.order - b.order).map((s, index) => { 250 return observable(
204 s.index = index; 251 services
205 return s; 252 .slice()
206 })); 253 .slice()
254 .sort((a, b) => a.order - b.order)
255 .map((s, index) => {
256 s.index = index;
257 return s;
258 }),
259 );
207 } 260 }
208 } 261 }
209 return []; 262 return [];
@@ -214,7 +267,9 @@ export default class ServicesStore extends Store {
214 } 267 }
215 268
216 @computed get allDisplayed() { 269 @computed get allDisplayed() {
217 const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; 270 const services = this.stores.settings.all.app.showDisabledServices
271 ? this.all
272 : this.enabled;
218 return workspaceStore.filterServicesByActiveWorkspace(services); 273 return workspaceStore.filterServicesByActiveWorkspace(services);
219 } 274 }
220 275
@@ -223,7 +278,9 @@ export default class ServicesStore extends Store {
223 const { showDisabledServices } = this.stores.settings.all.app; 278 const { showDisabledServices } = this.stores.settings.all.app;
224 const { keepAllWorkspacesLoaded } = this.stores.workspaces.settings; 279 const { keepAllWorkspacesLoaded } = this.stores.workspaces.settings;
225 const services = this.allServicesRequest.execute().result || []; 280 const services = this.allServicesRequest.execute().result || [];
226 const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); 281 const filteredServices = showDisabledServices
282 ? services
283 : services.filter(service => service.isEnabled);
227 284
228 let displayedServices; 285 let displayedServices;
229 if (keepAllWorkspacesLoaded) { 286 if (keepAllWorkspacesLoaded) {
@@ -231,32 +288,38 @@ export default class ServicesStore extends Store {
231 displayedServices = filteredServices; 288 displayedServices = filteredServices;
232 } else { 289 } else {
233 // Keep all services in current workspace loaded 290 // Keep all services in current workspace loaded
234 displayedServices = workspaceStore.filterServicesByActiveWorkspace(filteredServices); 291 displayedServices =
292 workspaceStore.filterServicesByActiveWorkspace(filteredServices);
235 293
236 // Keep all services active in workspaces that should be kept loaded 294 // Keep all services active in workspaces that should be kept loaded
237 for (const workspace of this.stores.workspaces.workspaces) { 295 for (const workspace of this.stores.workspaces.workspaces) {
238 // Check if workspace needs to be kept loaded 296 // Check if workspace needs to be kept loaded
239 if (workspace.services.includes(KEEP_WS_LOADED_USID)) { 297 if (workspace.services.includes(KEEP_WS_LOADED_USID)) {
240 // Get services for workspace 298 // Get services for workspace
241 const serviceIDs = workspace.services.filter(i => i !== KEEP_WS_LOADED_USID); 299 const serviceIDs = workspace.services.filter(
242 const wsServices = filteredServices.filter(service => serviceIDs.includes(service.id)); 300 i => i !== KEEP_WS_LOADED_USID,
243 301 );
244 displayedServices = [ 302 const wsServices = filteredServices.filter(service =>
245 ...displayedServices, 303 serviceIDs.includes(service.id),
246 ...wsServices, 304 );
247 ]; 305
306 displayedServices = [...displayedServices, ...wsServices];
248 } 307 }
249 } 308 }
250 309
251 // Make sure every service is in the list only once 310 // Make sure every service is in the list only once
252 displayedServices = displayedServices.filter((v, i, a) => a.indexOf(v) === i); 311 displayedServices = displayedServices.filter(
312 (v, i, a) => a.indexOf(v) === i,
313 );
253 } 314 }
254 315
255 return displayedServices; 316 return displayedServices;
256 } 317 }
257 318
258 @computed get filtered() { 319 @computed get filtered() {
259 return this.all.filter(service => service.name.toLowerCase().includes(this.filterNeedle.toLowerCase())); 320 return this.all.filter(service =>
321 service.name.toLowerCase().includes(this.filterNeedle.toLowerCase()),
322 );
260 } 323 }
261 324
262 @computed get active() { 325 @computed get active() {
@@ -264,7 +327,10 @@ export default class ServicesStore extends Store {
264 } 327 }
265 328
266 @computed get activeSettings() { 329 @computed get activeSettings() {
267 const match = matchRoute('/settings/services/edit/:id', this.stores.router.location.pathname); 330 const match = matchRoute(
331 '/settings/services/edit/:id',
332 this.stores.router.location.pathname,
333 );
268 if (match) { 334 if (match) {
269 const activeService = this.one(match.id); 335 const activeService = this.one(match.id);
270 if (activeService) { 336 if (activeService) {
@@ -278,7 +344,11 @@ export default class ServicesStore extends Store {
278 } 344 }
279 345
280 @computed get isTodosServiceAdded() { 346 @computed get isTodosServiceAdded() {
281 return this.allDisplayed.find(service => service.isTodosService && service.isEnabled) || false; 347 return (
348 this.allDisplayed.find(
349 service => service.isTodosService && service.isEnabled,
350 ) || false
351 );
282 } 352 }
283 353
284 @computed get isTodosServiceActive() { 354 @computed get isTodosServiceActive() {
@@ -295,10 +365,11 @@ export default class ServicesStore extends Store {
295 365
296 // Actions 366 // Actions
297 async _createService({ 367 async _createService({
298 recipeId, serviceData, redirect = true, skipCleanup = false, 368 recipeId,
369 serviceData,
370 redirect = true,
371 skipCleanup = false,
299 }) { 372 }) {
300 if (serviceLimitStore.userHasReachedServiceLimit) return;
301
302 if (!this.stores.recipes.isInstalled(recipeId)) { 373 if (!this.stores.recipes.isInstalled(recipeId)) {
303 debug(`Recipe "${recipeId}" is not installed, installing recipe`); 374 debug(`Recipe "${recipeId}" is not installed, installing recipe`);
304 await this.stores.recipes._install({ recipeId }); 375 await this.stores.recipes._install({ recipeId });
@@ -307,17 +378,21 @@ export default class ServicesStore extends Store {
307 378
308 // set default values for serviceData 379 // set default values for serviceData
309 // eslint-disable-next-line prefer-object-spread 380 // eslint-disable-next-line prefer-object-spread
310 Object.assign({ 381 Object.assign(
311 isEnabled: true, 382 {
312 isHibernationEnabled: false, 383 isEnabled: true,
313 isNotificationEnabled: true, 384 isHibernationEnabled: false,
314 isBadgeEnabled: true, 385 isNotificationEnabled: true,
315 isMuted: false, 386 isBadgeEnabled: true,
316 customIcon: false, 387 isMuted: false,
317 isDarkModeEnabled: false, 388 customIcon: false,
318 spellcheckerLanguage: SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage], 389 isDarkModeEnabled: false,
319 userAgentPref: '', 390 spellcheckerLanguage:
320 }, serviceData); 391 SPELLCHECKER_LOCALES[this.stores.settings.app.spellcheckerLanguage],
392 userAgentPref: '',
393 },
394 serviceData,
395 );
321 396
322 let data = serviceData; 397 let data = serviceData;
323 398
@@ -325,9 +400,10 @@ export default class ServicesStore extends Store {
325 data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 400 data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
326 } 401 }
327 402
328 const response = await this.createServiceRequest.execute(recipeId, data)._promise; 403 const response = await this.createServiceRequest.execute(recipeId, data)
404 ._promise;
329 405
330 this.allServicesRequest.patch((result) => { 406 this.allServicesRequest.patch(result => {
331 if (!result) return; 407 if (!result) return;
332 result.push(response.data); 408 result.push(response.data);
333 }); 409 });
@@ -371,7 +447,10 @@ export default class ServicesStore extends Store {
371 447
372 @action async _updateService({ serviceId, serviceData, redirect = true }) { 448 @action async _updateService({ serviceId, serviceData, redirect = true }) {
373 const service = this.one(serviceId); 449 const service = this.one(serviceId);
374 const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); 450 const data = this._cleanUpTeamIdAndCustomUrl(
451 service.recipe.id,
452 serviceData,
453 );
375 const request = this.updateServiceRequest.execute(serviceId, data); 454 const request = this.updateServiceRequest.execute(serviceId, data);
376 455
377 const newData = serviceData; 456 const newData = serviceData;
@@ -382,7 +461,7 @@ export default class ServicesStore extends Store {
382 newData.hasCustomUploadedIcon = true; 461 newData.hasCustomUploadedIcon = true;
383 } 462 }
384 463
385 this.allServicesRequest.patch((result) => { 464 this.allServicesRequest.patch(result => {
386 if (!result) return; 465 if (!result) return;
387 466
388 // patch custom icon deletion 467 // patch custom icon deletion
@@ -396,7 +475,10 @@ export default class ServicesStore extends Store {
396 newData.iconUrl = data.customIconUrl; 475 newData.iconUrl = data.customIconUrl;
397 } 476 }
398 477
399 Object.assign(result.find(c => c.id === serviceId), newData); 478 Object.assign(
479 result.find(c => c.id === serviceId),
480 newData,
481 );
400 }); 482 });
401 483
402 await request._promise; 484 await request._promise;
@@ -429,7 +511,7 @@ export default class ServicesStore extends Store {
429 this.stores.router.push(redirect); 511 this.stores.router.push(redirect);
430 } 512 }
431 513
432 this.allServicesRequest.patch((result) => { 514 this.allServicesRequest.patch(result => {
433 remove(result, c => c.id === serviceId); 515 remove(result, c => c.id === serviceId);
434 }); 516 });
435 517
@@ -443,9 +525,9 @@ export default class ServicesStore extends Store {
443 const devDirectory = getDevRecipeDirectory(recipe); 525 const devDirectory = getDevRecipeDirectory(recipe);
444 let directory; 526 let directory;
445 527
446 if (await fs.pathExists(normalDirectory)) { 528 if (pathExistsSync(normalDirectory)) {
447 directory = normalDirectory; 529 directory = normalDirectory;
448 } else if (await fs.pathExists(devDirectory)) { 530 } else if (pathExistsSync(devDirectory)) {
449 directory = devDirectory; 531 directory = devDirectory;
450 } else { 532 } else {
451 // Recipe cannot be found on drive 533 // Recipe cannot be found on drive
@@ -453,17 +535,19 @@ export default class ServicesStore extends Store {
453 } 535 }
454 536
455 // Create and open file 537 // Create and open file
456 const filePath = path.join(directory, file); 538 const filePath = join(directory, file);
457 if (file === 'user.js') { 539 if (file === 'user.js') {
458 if (!await fs.exists(filePath)) { 540 if (!pathExistsSync(filePath)) {
459 await fs.writeFile(filePath, `module.exports = (config, Ferdi) => { 541 writeFileSync(
542 filePath,
543 `module.exports = (config, Ferdi) => {
460 // Write your scripts here 544 // Write your scripts here
461 console.log("Hello, World!", config); 545 console.log("Hello, World!", config);
462} 546};
463`); 547`);
464 } 548 }
465 } else { 549 } else {
466 await fs.ensureFile(filePath); 550 ensureFileSync(filePath);
467 } 551 }
468 shell.showItemInFolder(filePath); 552 shell.showItemInFolder(filePath);
469 } 553 }
@@ -474,23 +558,27 @@ export default class ServicesStore extends Store {
474 await request._promise; 558 await request._promise;
475 } 559 }
476 560
477 @action _setActive({ serviceId, keepActiveRoute }) { 561 @action _setActive({ serviceId, keepActiveRoute = null }) {
478 if (!keepActiveRoute) this.stores.router.push('/'); 562 if (!keepActiveRoute) this.stores.router.push('/');
479 const service = this.one(serviceId); 563 const service = this.one(serviceId);
480 564
481 this.all.forEach((s, index) => { 565 this.all.forEach(s => {
482 this.all[index].isActive = false; 566 s.isActive = false;
483 }); 567 });
484 service.isActive = true; 568 service.isActive = true;
485 this._awake({ serviceId: service.id }); 569 this._awake({ serviceId: service.id });
486 service.lastUsed = Date.now();
487 570
488 if (this.isTodosServiceActive && !this.stores.todos.settings.isFeatureEnabledByUser) { 571 if (
572 this.isTodosServiceActive &&
573 !this.stores.todos.settings.isFeatureEnabledByUser
574 ) {
489 this.actions.todos.toggleTodosFeatureVisibility(); 575 this.actions.todos.toggleTodosFeatureVisibility();
490 } 576 }
491 577
492 // Update list of last used services 578 // Update list of last used services
493 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId); 579 this.lastUsedServices = this.lastUsedServices.filter(
580 id => id !== serviceId,
581 );
494 this.lastUsedServices.unshift(serviceId); 582 this.lastUsedServices.unshift(serviceId);
495 583
496 this._focusActiveService(); 584 this._focusActiveService();
@@ -502,7 +590,11 @@ export default class ServicesStore extends Store {
502 } 590 }
503 591
504 @action _setActiveNext() { 592 @action _setActiveNext() {
505 const nextIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), 1, this.allDisplayed.length); 593 const nextIndex = this._wrapIndex(
594 this.allDisplayed.findIndex(service => service.isActive),
595 1,
596 this.allDisplayed.length,
597 );
506 598
507 // TODO: simplify this; 599 // TODO: simplify this;
508 this.all.forEach((s, index) => { 600 this.all.forEach((s, index) => {
@@ -512,7 +604,11 @@ export default class ServicesStore extends Store {
512 } 604 }
513 605
514 @action _setActivePrev() { 606 @action _setActivePrev() {
515 const prevIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), -1, this.allDisplayed.length); 607 const prevIndex = this._wrapIndex(
608 this.allDisplayed.findIndex(service => service.isActive),
609 -1,
610 this.allDisplayed.length,
611 );
516 612
517 // TODO: simplify this; 613 // TODO: simplify this;
518 this.all.forEach((s, index) => { 614 this.all.forEach((s, index) => {
@@ -603,17 +699,21 @@ export default class ServicesStore extends Store {
603 const { options } = args[0]; 699 const { options } = args[0];
604 700
605 // Check if we are in scheduled Do-not-Disturb time 701 // Check if we are in scheduled Do-not-Disturb time
606 const { 702 const { scheduledDNDEnabled, scheduledDNDStart, scheduledDNDEnd } =
607 scheduledDNDEnabled, 703 this.stores.settings.all.app;
608 scheduledDNDStart,
609 scheduledDNDEnd,
610 } = this.stores.settings.all.app;
611 704
612 if (scheduledDNDEnabled && isInTimeframe(scheduledDNDStart, scheduledDNDEnd)) { 705 if (
706 scheduledDNDEnabled &&
707 isInTimeframe(scheduledDNDStart, scheduledDNDEnd)
708 ) {
613 return; 709 return;
614 } 710 }
615 711
616 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.app.isAppMuted) { 712 if (
713 service.recipe.hasNotificationSound ||
714 service.isMuted ||
715 this.stores.settings.all.app.isAppMuted
716 ) {
617 Object.assign(options, { 717 Object.assign(options, {
618 silent: true, 718 silent: true,
619 }); 719 });
@@ -623,7 +723,8 @@ export default class ServicesStore extends Store {
623 let title = `Notification from ${service.name}`; 723 let title = `Notification from ${service.name}`;
624 if (!this.stores.settings.all.app.privateNotifications) { 724 if (!this.stores.settings.all.app.privateNotifications) {
625 options.body = typeof options.body === 'string' ? options.body : ''; 725 options.body = typeof options.body === 'string' ? options.body : '';
626 title = typeof args[0].title === 'string' ? args[0].title : service.name; 726 title =
727 typeof args[0].title === 'string' ? args[0].title : service.name;
627 } else { 728 } else {
628 // Remove message data from notification in private mode 729 // Remove message data from notification in private mode
629 options.body = ''; 730 options.body = '';
@@ -686,11 +787,13 @@ export default class ServicesStore extends Store {
686 } 787 }
687 788
688 @action _sendIPCMessageToAllServices({ channel, args }) { 789 @action _sendIPCMessageToAllServices({ channel, args }) {
689 this.all.forEach(s => this.actions.service.sendIPCMessage({ 790 this.all.forEach(s =>
690 serviceId: s.id, 791 this.actions.service.sendIPCMessage({
691 channel, 792 serviceId: s.id,
692 args, 793 channel,
693 })); 794 args,
795 }),
796 );
694 } 797 }
695 798
696 @action _openWindow({ event }) { 799 @action _openWindow({ event }) {
@@ -737,9 +840,11 @@ export default class ServicesStore extends Store {
737 } 840 }
738 841
739 @action _reloadAll() { 842 @action _reloadAll() {
740 this.enabled.forEach(s => this._reload({ 843 this.enabled.forEach(s =>
741 serviceId: s.id, 844 this._reload({
742 })); 845 serviceId: s.id,
846 }),
847 );
743 } 848 }
744 849
745 @action _reloadUpdatedServices() { 850 @action _reloadUpdatedServices() {
@@ -758,10 +863,18 @@ export default class ServicesStore extends Store {
758 863
759 @action _reorderService({ oldIndex, newIndex }) { 864 @action _reorderService({ oldIndex, newIndex }) {
760 const { showDisabledServices } = this.stores.settings.all.app; 865 const { showDisabledServices } = this.stores.settings.all.app;
761 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]); 866 const oldEnabledSortIndex = showDisabledServices
762 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]); 867 ? oldIndex
763 868 : this.all.indexOf(this.enabled[oldIndex]);
764 this.all.splice(newEnabledSortIndex, 0, this.all.splice(oldEnabledSortIndex, 1)[0]); 869 const newEnabledSortIndex = showDisabledServices
870 ? newIndex
871 : this.all.indexOf(this.enabled[newIndex]);
872
873 this.all.splice(
874 newEnabledSortIndex,
875 0,
876 this.all.splice(oldEnabledSortIndex, 1)[0],
877 );
765 878
766 const services = {}; 879 const services = {};
767 this.all.forEach((s, index) => { 880 this.all.forEach((s, index) => {
@@ -769,8 +882,8 @@ export default class ServicesStore extends Store {
769 }); 882 });
770 883
771 this.reorderServicesRequest.execute(services); 884 this.reorderServicesRequest.execute(services);
772 this.allServicesRequest.patch((data) => { 885 this.allServicesRequest.patch(data => {
773 data.forEach((s) => { 886 data.forEach(s => {
774 const service = s; 887 const service = s;
775 888
776 service.order = services[s.id]; 889 service.order = services[s.id];
@@ -804,6 +917,18 @@ export default class ServicesStore extends Store {
804 }); 917 });
805 } 918 }
806 919
920 @action _toggleDarkMode({ serviceId }) {
921 const service = this.one(serviceId);
922
923 this.actions.service.updateService({
924 serviceId,
925 serviceData: {
926 isDarkModeEnabled: !service.isDarkModeEnabled,
927 },
928 redirect: false,
929 });
930 }
931
807 @action _openDevTools({ serviceId }) { 932 @action _openDevTools({ serviceId }) {
808 const service = this.one(serviceId); 933 const service = this.one(serviceId);
809 if (service.isTodosService) { 934 if (service.isTodosService) {
@@ -825,26 +950,36 @@ export default class ServicesStore extends Store {
825 950
826 @action _hibernate({ serviceId }) { 951 @action _hibernate({ serviceId }) {
827 const service = this.one(serviceId); 952 const service = this.one(serviceId);
828 if (service.isActive || !service.isHibernationEnabled) { 953 if (!service.canHibernate) {
829 debug('Skipping service hibernation'); 954 return;
955 }
956 if (service.isActive) {
957 debug(`Skipping service hibernation for ${service.name}`);
830 return; 958 return;
831 } 959 }
832 960
833 debug(`Hibernate ${service.name}`); 961 debug(`Hibernate ${service.name}`);
834 962
835 service.isHibernating = true; 963 service.isHibernationRequested = true;
964 service.lastHibernated = Date.now();
836 } 965 }
837 966
838 @action _awake({ serviceId }) { 967 @action _awake({ serviceId }) {
839 const service = this.one(serviceId); 968 const service = this.one(serviceId);
840 service.isHibernating = false; 969 debug(`Waking up from service hibernation for ${service.name}`);
841 service.liveFrom = Date.now(); 970 service.isHibernationRequested = false;
971 service.lastUsed = Date.now();
972 service.lastHibernated = null;
842 } 973 }
843 974
844 @action _resetLastPollTimer({ serviceId = null }) { 975 @action _resetLastPollTimer({ serviceId = null }) {
845 debug(`Reset last poll timer for ${serviceId ? `service: "${serviceId}"` : 'all services'}`); 976 debug(
977 `Reset last poll timer for ${
978 serviceId ? `service: "${serviceId}"` : 'all services'
979 }`,
980 );
846 981
847 const resetTimer = (service) => { 982 const resetTimer = service => {
848 service.lastPollAnswer = Date.now(); 983 service.lastPollAnswer = Date.now();
849 service.lastPoll = Date.now(); 984 service.lastPoll = Date.now();
850 }; 985 };
@@ -884,9 +1019,13 @@ export default class ServicesStore extends Store {
884 _mapActiveServiceToServiceModelReaction() { 1019 _mapActiveServiceToServiceModelReaction() {
885 const { activeService } = this.stores.settings.all.service; 1020 const { activeService } = this.stores.settings.all.service;
886 if (this.allDisplayed.length) { 1021 if (this.allDisplayed.length) {
887 this.allDisplayed.map(service => Object.assign(service, { 1022 this.allDisplayed.map(service =>
888 isActive: activeService ? activeService === service.id : this.allDisplayed[0].id === service.id, 1023 Object.assign(service, {
889 })); 1024 isActive: activeService
1025 ? activeService === service.id
1026 : this.allDisplayed[0].id === service.id,
1027 }),
1028 );
890 } 1029 }
891 } 1030 }
892 1031
@@ -895,12 +1034,23 @@ export default class ServicesStore extends Store {
895 const { showMessageBadgesEvenWhenMuted } = this.stores.ui; 1034 const { showMessageBadgesEvenWhenMuted } = this.stores.ui;
896 1035
897 const unreadDirectMessageCount = this.allDisplayed 1036 const unreadDirectMessageCount = this.allDisplayed
898 .filter(s => (showMessageBadgeWhenMuted || s.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && s.isBadgeEnabled) 1037 .filter(
1038 s =>
1039 (showMessageBadgeWhenMuted || s.isNotificationEnabled) &&
1040 showMessageBadgesEvenWhenMuted &&
1041 s.isBadgeEnabled,
1042 )
899 .map(s => s.unreadDirectMessageCount) 1043 .map(s => s.unreadDirectMessageCount)
900 .reduce((a, b) => a + b, 0); 1044 .reduce((a, b) => a + b, 0);
901 1045
902 const unreadIndirectMessageCount = this.allDisplayed 1046 const unreadIndirectMessageCount = this.allDisplayed
903 .filter(s => (showMessageBadgeWhenMuted && showMessageBadgesEvenWhenMuted) && (s.isBadgeEnabled && s.isIndirectMessageBadgeEnabled)) 1047 .filter(
1048 s =>
1049 showMessageBadgeWhenMuted &&
1050 showMessageBadgesEvenWhenMuted &&
1051 s.isBadgeEnabled &&
1052 s.isIndirectMessageBadgeEnabled,
1053 )
904 .map(s => s.unreadIndirectMessageCount) 1054 .map(s => s.unreadIndirectMessageCount)
905 .reduce((a, b) => a + b, 0); 1055 .reduce((a, b) => a + b, 0);
906 1056
@@ -927,7 +1077,7 @@ export default class ServicesStore extends Store {
927 const { enabled } = this; 1077 const { enabled } = this;
928 const { isAppMuted } = this.stores.settings.app; 1078 const { isAppMuted } = this.stores.settings.app;
929 1079
930 enabled.forEach((service) => { 1080 enabled.forEach(service => {
931 const { isAttached } = service; 1081 const { isAttached } = service;
932 const isMuted = isAppMuted || service.isMuted; 1082 const isMuted = isAppMuted || service.isMuted;
933 1083
@@ -954,48 +1104,30 @@ export default class ServicesStore extends Store {
954 1104
955 if (!recipe) return; 1105 if (!recipe) return;
956 1106
957 if (recipe.hasTeamId && recipe.hasCustomUrl && data.team && data.customUrl) { 1107 if (
1108 recipe.hasTeamId &&
1109 recipe.hasCustomUrl &&
1110 data.team &&
1111 data.customUrl
1112 ) {
958 delete serviceData.team; 1113 delete serviceData.team;
959 } 1114 }
960 1115
961 return serviceData; 1116 return serviceData;
962 } 1117 }
963 1118
964 _restrictServiceAccess() {
965 const { features } = this.stores.features;
966 const { userHasReachedServiceLimit, serviceLimit } = this.stores.serviceLimit;
967
968 this.all.map((service, index) => {
969 if (userHasReachedServiceLimit) {
970 service.isServiceAccessRestricted = index >= serviceLimit;
971
972 if (service.isServiceAccessRestricted) {
973 service.restrictionType = RESTRICTION_TYPES.SERVICE_LIMIT;
974
975 debug('Restricting access to server due to service limit');
976 }
977 }
978
979 if (service.isUsingCustomUrl) {
980 service.isServiceAccessRestricted = !features.isCustomUrlIncludedInCurrentPlan;
981
982 if (service.isServiceAccessRestricted) {
983 service.restrictionType = RESTRICTION_TYPES.CUSTOM_URL;
984
985 debug('Restricting access to server due to custom url');
986 }
987 }
988
989 return service;
990 });
991 }
992
993 _checkForActiveService() { 1119 _checkForActiveService() {
994 if (!this.stores.router.location || this.stores.router.location.pathname.includes('auth/signup')) { 1120 if (
1121 !this.stores.router.location ||
1122 this.stores.router.location.pathname.includes('auth/signup')
1123 ) {
995 return; 1124 return;
996 } 1125 }
997 1126
998 if (this.allDisplayed.findIndex(service => service.isActive) === -1 && this.allDisplayed.length !== 0) { 1127 if (
1128 this.allDisplayed.findIndex(service => service.isActive) === -1 &&
1129 this.allDisplayed.length !== 0
1130 ) {
999 debug('No active service found, setting active service to index 0'); 1131 debug('No active service found, setting active service to index 0');
1000 1132
1001 this._setActive({ serviceId: this.allDisplayed[0].id }); 1133 this._setActive({ serviceId: this.allDisplayed[0].id });
@@ -1008,13 +1140,19 @@ export default class ServicesStore extends Store {
1008 1140
1009 if (service.webview) { 1141 if (service.webview) {
1010 // We need to completely clone the object, otherwise Electron won't be able to send the object via IPC 1142 // We need to completely clone the object, otherwise Electron won't be able to send the object via IPC
1011 const shareWithWebview = JSON.parse(JSON.stringify(service.shareWithWebview)); 1143 const shareWithWebview = JSON.parse(
1144 JSON.stringify(service.shareWithWebview),
1145 );
1012 1146
1013 debug('Initialize recipe', service.recipe.id, service.name); 1147 debug('Initialize recipe', service.recipe.id, service.name);
1014 service.webview.send('initialize-recipe', { 1148 service.webview.send(
1015 ...shareWithWebview, 1149 'initialize-recipe',
1016 franzVersion: app.getVersion(), 1150 {
1017 }, service.recipe); 1151 ...shareWithWebview,
1152 franzVersion: app.getVersion(),
1153 },
1154 service.recipe,
1155 );
1018 } 1156 }
1019 } 1157 }
1020 1158
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index 292242552..cd627c2b8 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -164,11 +164,24 @@ export default class SettingsStore extends Store {
164 } 164 }
165 } 165 }
166 166
167 _ensureMigrationAndMarkDone(migrationName, callback) {
168 if (!this.all.migration[migrationName]) {
169 callback();
170
171 const data = {};
172 data[migrationName] = true;
173 this.actions.settings.update({
174 type: 'migration',
175 data,
176 });
177 }
178 }
179
167 // Helper 180 // Helper
168 async _migrate() { 181 async _migrate() {
169 const legacySettings = localStorage.getItem('app') || {}; 182 const legacySettings = localStorage.getItem('app') || {};
170 183
171 if (!this.all.migration['5.0.0-beta.17-settings']) { 184 this._ensureMigrationAndMarkDone('5.0.0-beta.17-settings', () => {
172 this.actions.settings.update({ 185 this.actions.settings.update({
173 type: 'app', 186 type: 'app',
174 data: { 187 data: {
@@ -193,19 +206,12 @@ export default class SettingsStore extends Store {
193 }, 206 },
194 }); 207 });
195 208
196 this.actions.settings.update({
197 type: 'migration',
198 data: {
199 '5.0.0-beta.17-settings': true,
200 },
201 });
202
203 localStorage.removeItem('app'); 209 localStorage.removeItem('app');
204 210
205 debug('Migrated settings to split stores'); 211 debug('Migrated settings to split stores');
206 } 212 });
207 213
208 if (!this.all.migration['5.0.0-beta.19-settings']) { 214 this._ensureMigrationAndMarkDone('5.0.0-beta.19-settings', () => {
209 const spellcheckerLanguage = getLocale({ 215 const spellcheckerLanguage = getLocale({
210 locale: this.stores.settings.app.locale, 216 locale: this.stores.settings.app.locale,
211 locales: SPELLCHECKER_LOCALES, 217 locales: SPELLCHECKER_LOCALES,
@@ -219,16 +225,9 @@ export default class SettingsStore extends Store {
219 spellcheckerLanguage, 225 spellcheckerLanguage,
220 }, 226 },
221 }); 227 });
228 });
222 229
223 this.actions.settings.update({ 230 this._ensureMigrationAndMarkDone('5.4.4-beta.2-settings', () => {
224 type: 'migration',
225 data: {
226 '5.0.0-beta.19-settings': true,
227 },
228 });
229 }
230
231 if (!this.all.migration['5.4.4-beta.2-settings']) {
232 const { 231 const {
233 showServiceNavigationBar, 232 showServiceNavigationBar,
234 } = this.all.app; 233 } = this.all.app;
@@ -239,53 +238,22 @@ export default class SettingsStore extends Store {
239 navigationBarBehaviour: showServiceNavigationBar ? 'custom' : 'never', 238 navigationBarBehaviour: showServiceNavigationBar ? 'custom' : 'never',
240 }, 239 },
241 }); 240 });
241 });
242 242
243 this.actions.settings.update({ 243 this._ensureMigrationAndMarkDone('5.4.4-beta.4-settings', () => {
244 type: 'migration',
245 data: {
246 '5.4.4-beta.2-settings': true,
247 },
248 });
249 }
250
251 if (!this.all.migration['5.4.4-beta.4-settings']) {
252 this.actions.settings.update({ 244 this.actions.settings.update({
253 type: 'app', 245 type: 'app',
254 data: { 246 data: {
255 todoServer: 'isUsingCustomTodoService', 247 todoServer: 'isUsingCustomTodoService',
256 customTodoServer: legacySettings.todoServer, 248 customTodoServer: legacySettings.todoServer,
257 },
258 });
259
260 this.actions.settings.update({
261 type: 'migration',
262 data: {
263 '5.4.4-beta.4-settings': true,
264 },
265 });
266
267 debug('Migrated old todo setting to new custom todo setting');
268 }
269
270 if (!this.all.migration['5.4.4-beta.4-settings']) {
271 this.actions.settings.update({
272 type: 'app',
273 data: {
274 automaticUpdates: !(legacySettings.noUpdates), 249 automaticUpdates: !(legacySettings.noUpdates),
275 }, 250 },
276 }); 251 });
277 252
278 this.actions.settings.update({ 253 debug('Migrated old todo setting to new custom todo setting');
279 type: 'migration', 254 });
280 data: {
281 '5.4.4-beta.4-settings': true,
282 },
283 });
284
285 debug('Migrated updates settings');
286 }
287 255
288 if (!this.all.migration['password-hashing']) { 256 this._ensureMigrationAndMarkDone('password-hashing', () => {
289 if (this.stores.settings.app.lockedPassword !== '') { 257 if (this.stores.settings.app.lockedPassword !== '') {
290 this.actions.settings.update({ 258 this.actions.settings.update({
291 type: 'app', 259 type: 'app',
@@ -295,46 +263,25 @@ export default class SettingsStore extends Store {
295 }); 263 });
296 } 264 }
297 265
298 this.actions.settings.update({
299 type: 'migration',
300 data: {
301 'password-hashing': true,
302 },
303 });
304
305 debug('Migrated updates settings'); 266 debug('Migrated updates settings');
306 } 267 });
307 268
308 if (!this.all.migration['5.6.0-beta.6-settings']) { 269 this._ensureMigrationAndMarkDone('5.6.0-beta.6-settings', () => {
309 this.actions.settings.update({ 270 this.actions.settings.update({
310 type: 'app', 271 type: 'app',
311 data: { 272 data: {
312 searchEngine: SEARCH_ENGINE_DDG, 273 searchEngine: SEARCH_ENGINE_DDG,
313 }, 274 },
314 }); 275 });
276 });
315 277
316 this.actions.settings.update({ 278 this._ensureMigrationAndMarkDone('user-agent-settings', () => {
317 type: 'migration',
318 data: {
319 '5.6.0-beta.6-settings': true,
320 },
321 });
322 }
323
324 if (!this.all.migration['user-agent-settings']) {
325 this.actions.settings.update({ 279 this.actions.settings.update({
326 type: 'app', 280 type: 'app',
327 data: { 281 data: {
328 userAgentPref: DEFAULT_APP_SETTINGS.userAgentPref, 282 userAgentPref: DEFAULT_APP_SETTINGS.userAgentPref,
329 }, 283 },
330 }); 284 });
331 285 });
332 this.actions.settings.update({
333 type: 'migration',
334 data: {
335 'user-agent-settings': true,
336 },
337 });
338 }
339 } 286 }
340} 287}
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
index 0ca61046a..adcd776c1 100644
--- a/src/stores/UIStore.js
+++ b/src/stores/UIStore.js
@@ -71,7 +71,7 @@ export default class UIStore extends Store {
71 71
72 @computed get theme() { 72 @computed get theme() {
73 const themeId = (this.isDarkThemeActive || this.stores.settings.app.darkMode) ? 'dark' : 'default'; 73 const themeId = (this.isDarkThemeActive || this.stores.settings.app.darkMode) ? 'dark' : 'default';
74 const accentColor = this.stores.settings.app.accentColor; 74 const { accentColor } = this.stores.settings.app;
75 return theme(themeId, accentColor); 75 return theme(themeId, accentColor);
76 } 76 }
77 77
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 7947e5a27..2e009893a 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -2,16 +2,13 @@ import { observable, computed, action } from 'mobx';
2import moment from 'moment'; 2import moment from 'moment';
3import jwt from 'jsonwebtoken'; 3import jwt from 'jsonwebtoken';
4import localStorage from 'mobx-localstorage'; 4import localStorage from 'mobx-localstorage';
5import ms from 'ms';
6import { session } from '@electron/remote'; 5import { session } from '@electron/remote';
7 6
8import { isDevMode } from '../environment'; 7import { isDevMode } from '../environment';
9import Store from './lib/Store'; 8import Store from './lib/Store';
10import Request from './lib/Request'; 9import Request from './lib/Request';
11import CachedRequest from './lib/CachedRequest'; 10import CachedRequest from './lib/CachedRequest';
12import { sleep } from '../helpers/async-helpers'; 11import { TODOS_PARTITION_ID } from '../config';
13import { getPlan } from '../helpers/plan-helpers';
14import { PLANS, TODOS_PARTITION_ID } from '../config';
15 12
16const debug = require('debug')('Ferdi:UserStore'); 13const debug = require('debug')('Ferdi:UserStore');
17 14
@@ -27,8 +24,6 @@ export default class UserStore extends Store {
27 24
28 SIGNUP_ROUTE = `${this.BASE_ROUTE}/signup`; 25 SIGNUP_ROUTE = `${this.BASE_ROUTE}/signup`;
29 26
30 PRICING_ROUTE = `${this.BASE_ROUTE}/signup/pricing`;
31
32 SETUP_ROUTE = `${this.BASE_ROUTE}/signup/setup`; 27 SETUP_ROUTE = `${this.BASE_ROUTE}/signup/setup`;
33 28
34 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`; 29 IMPORT_ROUTE = `${this.BASE_ROUTE}/signup/import`;
@@ -45,8 +40,6 @@ export default class UserStore extends Store {
45 40
46 @observable passwordRequest = new Request(this.api.user, 'password'); 41 @observable passwordRequest = new Request(this.api.user, 'password');
47 42
48 @observable activateTrialRequest = new Request(this.api.user, 'activateTrial');
49
50 @observable inviteRequest = new Request(this.api.user, 'invite'); 43 @observable inviteRequest = new Request(this.api.user, 'invite');
51 44
52 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); 45 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo');
@@ -71,8 +64,6 @@ export default class UserStore extends Store {
71 64
72 @observable hasCompletedSignup = false; 65 @observable hasCompletedSignup = false;
73 66
74 @observable hasActivatedTrial = false;
75
76 @observable userData = {}; 67 @observable userData = {};
77 68
78 @observable actionStatus = []; 69 @observable actionStatus = [];
@@ -93,7 +84,6 @@ export default class UserStore extends Store {
93 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this)); 84 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this));
94 this.actions.user.logout.listen(this._logout.bind(this)); 85 this.actions.user.logout.listen(this._logout.bind(this));
95 this.actions.user.signup.listen(this._signup.bind(this)); 86 this.actions.user.signup.listen(this._signup.bind(this));
96 this.actions.user.activateTrial.listen(this._activateTrial.bind(this));
97 this.actions.user.invite.listen(this._invite.bind(this)); 87 this.actions.user.invite.listen(this._invite.bind(this));
98 this.actions.user.update.listen(this._update.bind(this)); 88 this.actions.user.update.listen(this._update.bind(this));
99 this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); 89 this.actions.user.resetStatus.listen(this._resetStatus.bind(this));
@@ -104,7 +94,6 @@ export default class UserStore extends Store {
104 this.registerReactions([ 94 this.registerReactions([
105 this._requireAuthenticatedUser.bind(this), 95 this._requireAuthenticatedUser.bind(this),
106 this._getUserData.bind(this), 96 this._getUserData.bind(this),
107 this._resetTrialActivationState.bind(this),
108 ]); 97 ]);
109 } 98 }
110 99
@@ -126,10 +115,6 @@ export default class UserStore extends Store {
126 return this.SIGNUP_ROUTE; 115 return this.SIGNUP_ROUTE;
127 } 116 }
128 117
129 get pricingRoute() {
130 return this.PRICING_ROUTE;
131 }
132
133 get setupRoute() { 118 get setupRoute() {
134 return this.SETUP_ROUTE; 119 return this.SETUP_ROUTE;
135 } 120 }
@@ -172,31 +157,6 @@ export default class UserStore extends Store {
172 return this.data.team || null; 157 return this.data.team || null;
173 } 158 }
174 159
175 @computed get isPremium() {
176 return true;
177 }
178
179 @computed get isPremiumOverride() {
180 return ((!this.team || !this.team.plan) && this.isPremium) || (this.team && this.team.state === 'expired' && this.isPremium);
181 }
182
183 @computed get isPersonal() {
184 if (!this.team || !this.team.plan) return false;
185 const plan = getPlan(this.team.plan);
186
187 return plan === PLANS.PERSONAL;
188 }
189
190 @computed get isPro() {
191 return true;
192 // if (this.isPremiumOverride) return true;
193
194 // if (!this.team || (!this.team.plan || this.team.state === 'expired')) return false;
195 // const plan = getPlan(this.team.plan);
196
197 // return plan === PLANS.PRO || plan === PLANS.LEGACY;
198 }
199
200 @computed get legacyServices() { 160 @computed get legacyServices() {
201 return this.getLegacyServicesRequest.execute() || {}; 161 return this.getLegacyServicesRequest.execute() || {};
202 } 162 }
@@ -244,23 +204,8 @@ export default class UserStore extends Store {
244 this.actionStatus = request.result.status || []; 204 this.actionStatus = request.result.status || [];
245 } 205 }
246 206
247 @action async _activateTrial({ planId }) {
248 debug('activate trial', planId);
249
250 this.activateTrialRequest.execute({
251 plan: planId,
252 });
253
254 await this.activateTrialRequest._promise;
255
256 this.hasActivatedTrial = true;
257
258 this.stores.features.featuresRequest.invalidate({ immediately: true });
259 this.stores.user.getUserInfoRequest.invalidate({ immediately: true });
260 }
261
262 @action async _invite({ invites }) { 207 @action async _invite({ invites }) {
263 const data = invites.filter(invite => invite.email !== ''); 208 const data = invites.filter((invite) => invite.email !== '');
264 209
265 const response = await this.inviteRequest.execute(data)._promise; 210 const response = await this.inviteRequest.execute(data)._promise;
266 211
@@ -305,7 +250,7 @@ export default class UserStore extends Store {
305 this.isImportLegacyServicesExecuting = true; 250 this.isImportLegacyServicesExecuting = true;
306 251
307 // Reduces recipe duplicates 252 // Reduces recipe duplicates
308 const recipes = services.filter((obj, pos, arr) => arr.map(mapObj => mapObj.recipe.id).indexOf(obj.recipe.id) === pos).map(s => s.recipe.id); 253 const recipes = services.filter((obj, pos, arr) => arr.map((mapObj) => mapObj.recipe.id).indexOf(obj.recipe.id) === pos).map((s) => s.recipe.id);
309 254
310 // Install recipes 255 // Install recipes
311 for (const recipe of recipes) { // eslint-disable-line no-unused-vars 256 for (const recipe of recipes) { // eslint-disable-line no-unused-vars
@@ -386,14 +331,6 @@ export default class UserStore extends Store {
386 } 331 }
387 } 332 }
388 333
389 async _resetTrialActivationState() {
390 if (this.hasActivatedTrial) {
391 await sleep(ms('12s'));
392
393 this.hasActivatedTrial = false;
394 }
395 }
396
397 // Helpers 334 // Helpers
398 _parseToken(authToken) { 335 _parseToken(authToken) {
399 try { 336 try {
diff --git a/src/stores/index.js b/src/stores/index.js
index 4eeef7982..b6e481e8a 100644
--- a/src/stores/index.js
+++ b/src/stores/index.js
@@ -6,16 +6,13 @@ import ServicesStore from './ServicesStore';
6import RecipesStore from './RecipesStore'; 6import RecipesStore from './RecipesStore';
7import RecipePreviewsStore from './RecipePreviewsStore'; 7import RecipePreviewsStore from './RecipePreviewsStore';
8import UIStore from './UIStore'; 8import UIStore from './UIStore';
9import PaymentStore from './PaymentStore';
10import NewsStore from './NewsStore'; 9import NewsStore from './NewsStore';
11import RequestStore from './RequestStore'; 10import RequestStore from './RequestStore';
12import GlobalErrorStore from './GlobalErrorStore'; 11import GlobalErrorStore from './GlobalErrorStore';
13import { workspaceStore } from '../features/workspaces'; 12import { workspaceStore } from '../features/workspaces';
14import { announcementsStore } from '../features/announcements'; 13import { announcementsStore } from '../features/announcements';
15import { serviceLimitStore } from '../features/serviceLimit';
16import { communityRecipesStore } from '../features/communityRecipes'; 14import { communityRecipesStore } from '../features/communityRecipes';
17import { todosStore } from '../features/todos'; 15import { todosStore } from '../features/todos';
18import { planSelectionStore } from '../features/planSelection';
19 16
20export default (api, actions, router) => { 17export default (api, actions, router) => {
21 const stores = {}; 18 const stores = {};
@@ -29,16 +26,13 @@ export default (api, actions, router) => {
29 recipes: new RecipesStore(stores, api, actions), 26 recipes: new RecipesStore(stores, api, actions),
30 recipePreviews: new RecipePreviewsStore(stores, api, actions), 27 recipePreviews: new RecipePreviewsStore(stores, api, actions),
31 ui: new UIStore(stores, api, actions), 28 ui: new UIStore(stores, api, actions),
32 payment: new PaymentStore(stores, api, actions),
33 news: new NewsStore(stores, api, actions), 29 news: new NewsStore(stores, api, actions),
34 requests: new RequestStore(stores, api, actions), 30 requests: new RequestStore(stores, api, actions),
35 globalError: new GlobalErrorStore(stores, api, actions), 31 globalError: new GlobalErrorStore(stores, api, actions),
36 workspaces: workspaceStore, 32 workspaces: workspaceStore,
37 announcements: announcementsStore, 33 announcements: announcementsStore,
38 serviceLimit: serviceLimitStore,
39 communityRecipes: communityRecipesStore, 34 communityRecipes: communityRecipesStore,
40 todos: todosStore, 35 todos: todosStore,
41 planSelection: planSelectionStore,
42 }); 36 });
43 // Initialize all stores 37 // Initialize all stores
44 Object.keys(stores).forEach((name) => { 38 Object.keys(stores).forEach((name) => {
diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.js
index 31c7ce241..94f615144 100644
--- a/src/stores/lib/CachedRequest.js
+++ b/src/stores/lib/CachedRequest.js
@@ -92,7 +92,7 @@ export default class CachedRequest extends Request {
92 } 92 }
93 93
94 removeCacheForCallWith(...args) { 94 removeCacheForCallWith(...args) {
95 remove(this._apiCalls, c => isEqual(c.args, args)); 95 remove(this._apiCalls, (c) => isEqual(c.args, args));
96 } 96 }
97 97
98 _addApiCall(args) { 98 _addApiCall(args) {
@@ -102,6 +102,6 @@ export default class CachedRequest extends Request {
102 } 102 }
103 103
104 _findApiCall(args) { 104 _findApiCall(args) {
105 return this._apiCalls.find(c => isEqual(c.args, args)); 105 return this._apiCalls.find((c) => isEqual(c.args, args));
106 } 106 }
107} 107}
diff --git a/src/stores/lib/Reaction.js b/src/stores/lib/Reaction.js
index f8009b7f6..7e1bc685e 100644
--- a/src/stores/lib/Reaction.js
+++ b/src/stores/lib/Reaction.js
@@ -26,6 +26,6 @@ export default class Reaction {
26 } 26 }
27} 27}
28 28
29export const createReactions = reactions => ( 29export const createReactions = (reactions) => (
30 reactions.map(r => new Reaction(r)) 30 reactions.map((r) => new Reaction(r))
31); 31);
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js
index cfc857c2e..32ffe4367 100644
--- a/src/stores/lib/Request.js
+++ b/src/stores/lib/Request.js
@@ -107,7 +107,7 @@ export default class Request {
107 } 107 }
108 108
109 _triggerHooks() { 109 _triggerHooks() {
110 Request._hooks.forEach(hook => hook(this)); 110 Request._hooks.forEach((hook) => hook(this));
111 } 111 }
112 112
113 reset = () => { 113 reset = () => {
diff --git a/src/stores/lib/Store.js b/src/stores/lib/Store.js
index 8d2fb4066..b03a7e725 100644
--- a/src/stores/lib/Store.js
+++ b/src/stores/lib/Store.js
@@ -28,18 +28,18 @@ export default class Store {
28 } 28 }
29 29
30 registerReactions(reactions) { 30 registerReactions(reactions) {
31 reactions.forEach(reaction => this._reactions.push(new Reaction(reaction))); 31 reactions.forEach((reaction) => this._reactions.push(new Reaction(reaction)));
32 } 32 }
33 33
34 setup() {} 34 setup() {}
35 35
36 initialize() { 36 initialize() {
37 this.setup(); 37 this.setup();
38 this._reactions.forEach(reaction => reaction.start()); 38 this._reactions.forEach((reaction) => reaction.start());
39 } 39 }
40 40
41 teardown() { 41 teardown() {
42 this._reactions.forEach(reaction => reaction.stop()); 42 this._reactions.forEach((reaction) => reaction.stop());
43 } 43 }
44 44
45 resetStatus() { 45 resetStatus() {