aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores')
-rw-r--r--src/stores/AppStore.js20
-rw-r--r--src/stores/FeaturesStore.js4
-rw-r--r--src/stores/GlobalErrorStore.js5
-rw-r--r--src/stores/PaymentStore.js3
-rw-r--r--src/stores/RecipePreviewsStore.js10
-rw-r--r--src/stores/RecipesStore.js4
-rw-r--r--src/stores/RequestStore.js11
-rw-r--r--src/stores/ServicesStore.js133
-rw-r--r--src/stores/SettingsStore.js66
-rw-r--r--src/stores/UserStore.js41
10 files changed, 212 insertions, 85 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 0a6309092..6ce79f2e2 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -1,6 +1,6 @@
1import { remote, ipcRenderer, shell } from 'electron'; 1import { remote, ipcRenderer, shell } from 'electron';
2import { 2import {
3 action, computed, observable, reaction, 3 action, computed, observable,
4} from 'mobx'; 4} from 'mobx';
5import moment from 'moment'; 5import moment from 'moment';
6import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; 6import { getDoNotDisturb } from '@meetfranz/electron-notification-state';
@@ -17,7 +17,6 @@ import Request from './lib/Request';
17import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; 17import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config';
18import { isMac } from '../environment'; 18import { isMac } from '../environment';
19import locales from '../i18n/translations'; 19import locales from '../i18n/translations';
20import { gaEvent, gaPage, statsEvent } from '../lib/analytics';
21import { onVisibilityChange } from '../helpers/visibility-helper'; 20import { onVisibilityChange } from '../helpers/visibility-helper';
22import { getLocale } from '../helpers/i18n-helpers'; 21import { getLocale } from '../helpers/i18n-helpers';
23 22
@@ -25,7 +24,7 @@ import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '..
25import { isValidExternalURL } from '../helpers/url-helpers'; 24import { isValidExternalURL } from '../helpers/url-helpers';
26import { sleep } from '../helpers/async-helpers'; 25import { sleep } from '../helpers/async-helpers';
27 26
28const debug = require('debug')('Franz:AppStore'); 27const debug = require('debug')('Ferdi:AppStore');
29 28
30const { 29const {
31 app, systemPreferences, screen, powerMonitor, 30 app, systemPreferences, screen, powerMonitor,
@@ -35,7 +34,7 @@ const mainWindow = remote.getCurrentWindow();
35 34
36const defaultLocale = DEFAULT_APP_SETTINGS.locale; 35const defaultLocale = DEFAULT_APP_SETTINGS.locale;
37const autoLauncher = new AutoLaunch({ 36const autoLauncher = new AutoLaunch({
38 name: 'Franz', 37 name: 'Ferdi',
39}); 38});
40 39
41const CATALINA_NOTIFICATION_HACK_KEY = '_temp_askedForCatalinaNotificationPermissions'; 40const CATALINA_NOTIFICATION_HACK_KEY = '_temp_askedForCatalinaNotificationPermissions';
@@ -59,6 +58,8 @@ export default class AppStore extends Store {
59 58
60 @observable isOnline = navigator.onLine; 59 @observable isOnline = navigator.onLine;
61 60
61 @observable authRequestFailed = false;
62
62 @observable timeSuspensionStart; 63 @observable timeSuspensionStart;
63 64
64 @observable timeOfflineStart; 65 @observable timeOfflineStart;
@@ -123,7 +124,7 @@ export default class AppStore extends Store {
123 124
124 this.isOnline = navigator.onLine; 125 this.isOnline = navigator.onLine;
125 126
126 // Check if Franz should launch on start 127 // Check if Ferdi should launch on start
127 // Needs to be delayed a bit 128 // Needs to be delayed a bit
128 this._autoStart(); 129 this._autoStart();
129 130
@@ -190,11 +191,6 @@ export default class AppStore extends Store {
190 debug('Window is visible/focused', isVisible); 191 debug('Window is visible/focused', isVisible);
191 }); 192 });
192 193
193 // analytics autorun
194 reaction(() => this.stores.router.location.pathname, (pathname) => {
195 gaPage(pathname);
196 });
197
198 powerMonitor.on('suspend', () => { 194 powerMonitor.on('suspend', () => {
199 debug('System suspended starting timer'); 195 debug('System suspended starting timer');
200 196
@@ -315,8 +311,6 @@ export default class AppStore extends Store {
315 } catch (err) { 311 } catch (err) {
316 console.warn(err); 312 console.warn(err);
317 } 313 }
318
319 gaEvent('App', enable ? 'enable autostart' : 'disable autostart');
320 } 314 }
321 315
322 @action _openExternalUrl({ url }) { 316 @action _openExternalUrl({ url }) {
@@ -421,7 +415,7 @@ export default class AppStore extends Store {
421 } 415 }
422 416
423 _muteAppHandler() { 417 _muteAppHandler() {
424 const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; 418 const { showMessageBadgesEvenWhenMuted } = this.stores.ui;
425 419
426 if (!showMessageBadgesEvenWhenMuted) { 420 if (!showMessageBadgesEvenWhenMuted) {
427 this.actions.app.setBadge({ unreadDirectMessageCount: 0, unreadIndirectMessageCount: 0 }); 421 this.actions.app.setBadge({ unreadDirectMessageCount: 0, unreadIndirectMessageCount: 0 });
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index adbd401b4..ab5d762c7 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -13,12 +13,14 @@ import spellchecker from '../features/spellchecker';
13import serviceProxy from '../features/serviceProxy'; 13import serviceProxy from '../features/serviceProxy';
14import basicAuth from '../features/basicAuth'; 14import basicAuth from '../features/basicAuth';
15import workspaces from '../features/workspaces'; 15import workspaces from '../features/workspaces';
16import quickSwitch from '../features/quickSwitch';
16import shareFranz from '../features/shareFranz'; 17import shareFranz from '../features/shareFranz';
17import announcements from '../features/announcements'; 18import announcements from '../features/announcements';
18import settingsWS from '../features/settingsWS'; 19import settingsWS from '../features/settingsWS';
19import serviceLimit from '../features/serviceLimit'; 20import serviceLimit from '../features/serviceLimit';
20import communityRecipes from '../features/communityRecipes'; 21import communityRecipes from '../features/communityRecipes';
21import todos from '../features/todos'; 22import todos from '../features/todos';
23import accentColor from '../features/accentColor';
22import planSelection from '../features/planSelection'; 24import planSelection from '../features/planSelection';
23import trialStatusBar from '../features/trialStatusBar'; 25import trialStatusBar from '../features/trialStatusBar';
24 26
@@ -78,12 +80,14 @@ export default class FeaturesStore extends Store {
78 serviceProxy(this.stores, this.actions); 80 serviceProxy(this.stores, this.actions);
79 basicAuth(this.stores, this.actions); 81 basicAuth(this.stores, this.actions);
80 workspaces(this.stores, this.actions); 82 workspaces(this.stores, this.actions);
83 quickSwitch(this.stores, this.actions);
81 shareFranz(this.stores, this.actions); 84 shareFranz(this.stores, this.actions);
82 announcements(this.stores, this.actions); 85 announcements(this.stores, this.actions);
83 settingsWS(this.stores, this.actions); 86 settingsWS(this.stores, this.actions);
84 serviceLimit(this.stores, this.actions); 87 serviceLimit(this.stores, this.actions);
85 communityRecipes(this.stores, this.actions); 88 communityRecipes(this.stores, this.actions);
86 todos(this.stores, this.actions); 89 todos(this.stores, this.actions);
90 accentColor(this.stores, this.actions);
87 planSelection(this.stores, this.actions); 91 planSelection(this.stores, this.actions);
88 trialStatusBar(this.stores, this.actions); 92 trialStatusBar(this.stores, this.actions);
89 } 93 }
diff --git a/src/stores/GlobalErrorStore.js b/src/stores/GlobalErrorStore.js
index 7a85c2daa..8bdafb68c 100644
--- a/src/stores/GlobalErrorStore.js
+++ b/src/stores/GlobalErrorStore.js
@@ -24,9 +24,12 @@ export default class GlobalErrorStore extends Store {
24 this.response = {}; 24 this.response = {};
25 } 25 }
26 if (this.error.status === 401) { 26 if (this.error.status === 401) {
27 this.actions.user.logout({ serverLogout: true }); 27 window.ferdi.stores.app.authRequestFailed = true;
28 // this.actions.user.logout({ serverLogout: true });
28 } 29 }
29 } 30 }
31 } else {
32 window.ferdi.stores.app.authRequestFailed = false;
30 } 33 }
31 }); 34 });
32} 35}
diff --git a/src/stores/PaymentStore.js b/src/stores/PaymentStore.js
index eb42ae10e..69e6eb9c3 100644
--- a/src/stores/PaymentStore.js
+++ b/src/stores/PaymentStore.js
@@ -4,7 +4,6 @@ import { remote } from 'electron';
4import Store from './lib/Store'; 4import Store from './lib/Store';
5import CachedRequest from './lib/CachedRequest'; 5import CachedRequest from './lib/CachedRequest';
6import Request from './lib/Request'; 6import Request from './lib/Request';
7import { gaEvent } from '../lib/analytics';
8 7
9const { BrowserWindow } = remote; 8const { BrowserWindow } = remote;
10 9
@@ -30,8 +29,6 @@ export default class PaymentStore extends Store {
30 @action _createHostedPage({ planId }) { 29 @action _createHostedPage({ planId }) {
31 const request = this.createHostedPageRequest.execute(planId); 30 const request = this.createHostedPageRequest.execute(planId);
32 31
33 gaEvent('Payment', 'createHostedPage', planId);
34
35 return request; 32 return request;
36 } 33 }
37 34
diff --git a/src/stores/RecipePreviewsStore.js b/src/stores/RecipePreviewsStore.js
index 382820d58..989e1124a 100644
--- a/src/stores/RecipePreviewsStore.js
+++ b/src/stores/RecipePreviewsStore.js
@@ -1,11 +1,8 @@
1import { action, computed, observable } from 'mobx'; 1import { action, computed, observable } from 'mobx';
2import { debounce } from 'lodash';
3import ms from 'ms';
4 2
5import Store from './lib/Store'; 3import Store from './lib/Store';
6import CachedRequest from './lib/CachedRequest'; 4import CachedRequest from './lib/CachedRequest';
7import Request from './lib/Request'; 5import Request from './lib/Request';
8import { gaEvent } from '../lib/analytics';
9 6
10export default class RecipePreviewsStore extends Store { 7export default class RecipePreviewsStore extends Store {
11 @observable allRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'all'); 8 @observable allRecipePreviewsRequest = new CachedRequest(this.api.recipePreviews, 'all');
@@ -41,13 +38,6 @@ export default class RecipePreviewsStore extends Store {
41 @action _search({ needle }) { 38 @action _search({ needle }) {
42 if (needle !== '') { 39 if (needle !== '') {
43 this.searchRecipePreviewsRequest.execute(needle); 40 this.searchRecipePreviewsRequest.execute(needle);
44
45 this._analyticsSearch(needle);
46 } 41 }
47 } 42 }
48
49 // Helper
50 _analyticsSearch = debounce((needle) => {
51 gaEvent('Recipe', 'search', needle);
52 }, ms('3s'));
53} 43}
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
index d51192078..8b2bde5df 100644
--- a/src/stores/RecipesStore.js
+++ b/src/stores/RecipesStore.js
@@ -5,7 +5,7 @@ import CachedRequest from './lib/CachedRequest';
5import Request from './lib/Request'; 5import Request from './lib/Request';
6import { matchRoute } from '../helpers/routing-helpers'; 6import { matchRoute } from '../helpers/routing-helpers';
7 7
8const debug = require('debug')('Franz:RecipeStore'); 8const debug = require('debug')('Ferdi:RecipeStore');
9 9
10export default class RecipesStore extends Store { 10export default class RecipesStore extends Store {
11 @observable allRecipesRequest = new CachedRequest(this.api.recipes, 'all'); 11 @observable allRecipesRequest = new CachedRequest(this.api.recipes, 'all');
@@ -108,7 +108,7 @@ export default class RecipesStore extends Store {
108 async _checkIfRecipeIsInstalled() { 108 async _checkIfRecipeIsInstalled() {
109 const { router } = this.stores; 109 const { router } = this.stores;
110 110
111 const match = matchRoute('/settings/services/add/:id', router.location.pathname); 111 const match = router.location && matchRoute('/settings/services/add/:id', router.location.pathname);
112 if (match) { 112 if (match) {
113 const recipeId = match.id; 113 const recipeId = match.id;
114 114
diff --git a/src/stores/RequestStore.js b/src/stores/RequestStore.js
index 9254e3223..a92f4c685 100644
--- a/src/stores/RequestStore.js
+++ b/src/stores/RequestStore.js
@@ -1,9 +1,10 @@
1import { ipcRenderer } from 'electron';
1import { action, computed, observable } from 'mobx'; 2import { action, computed, observable } from 'mobx';
2import ms from 'ms'; 3import ms from 'ms';
3 4
4import Store from './lib/Store'; 5import Store from './lib/Store';
5 6
6const debug = require('debug')('Franz:RequestsStore'); 7const debug = require('debug')('Ferdi:RequestsStore');
7 8
8export default class RequestStore extends Store { 9export default class RequestStore extends Store {
9 @observable userInfoRequest; 10 @observable userInfoRequest;
@@ -12,6 +13,8 @@ export default class RequestStore extends Store {
12 13
13 @observable showRequiredRequestsError = false; 14 @observable showRequiredRequestsError = false;
14 15
16 @observable localServerPort = 45569;
17
15 retries = 0; 18 retries = 0;
16 19
17 retryDelay = ms('2s'); 20 retryDelay = ms('2s');
@@ -29,6 +32,12 @@ export default class RequestStore extends Store {
29 setup() { 32 setup() {
30 this.userInfoRequest = this.stores.user.getUserInfoRequest; 33 this.userInfoRequest = this.stores.user.getUserInfoRequest;
31 this.servicesRequest = this.stores.services.allServicesRequest; 34 this.servicesRequest = this.stores.services.allServicesRequest;
35
36 ipcRenderer.on('localServerPort', (event, data) => {
37 if (data.port) {
38 this.localServerPort = data.port;
39 }
40 });
32 } 41 }
33 42
34 @computed get areRequiredRequestsSuccessful() { 43 @computed get areRequiredRequestsSuccessful() {
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 65e68e4d7..934a8a6e0 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -1,23 +1,27 @@
1import { shell, remote } from 'electron';
1import { 2import {
2 action, 3 action,
3 reaction, 4 reaction,
4 computed, 5 computed,
5 observable, 6 observable,
6} from 'mobx'; 7} from 'mobx';
7import { debounce, remove } from 'lodash'; 8import { remove } from 'lodash';
8import ms from 'ms'; 9import ms from 'ms';
9import { remote } from 'electron'; 10import fs from 'fs-extra';
11import path from 'path';
10 12
11import Store from './lib/Store'; 13import Store from './lib/Store';
12import Request from './lib/Request'; 14import Request from './lib/Request';
13import CachedRequest from './lib/CachedRequest'; 15import CachedRequest from './lib/CachedRequest';
14import { matchRoute } from '../helpers/routing-helpers'; 16import { matchRoute } from '../helpers/routing-helpers';
15import { gaEvent, statsEvent } from '../lib/analytics'; 17import { isInTimeframe } from '../helpers/schedule-helpers';
18import { getRecipeDirectory, getDevRecipeDirectory } from '../helpers/recipe-helpers';
16import { workspaceStore } from '../features/workspaces'; 19import { workspaceStore } from '../features/workspaces';
17import { serviceLimitStore } from '../features/serviceLimit'; 20import { serviceLimitStore } from '../features/serviceLimit';
18import { RESTRICTION_TYPES } from '../models/Service'; 21import { RESTRICTION_TYPES } from '../models/Service';
22import { KEEP_WS_LOADED_USID } from '../config';
19 23
20const debug = require('debug')('Franz:ServiceStore'); 24const debug = require('debug')('Ferdi:ServiceStore');
21 25
22const { app } = remote; 26const { app } = remote;
23 27
@@ -36,6 +40,11 @@ export default class ServicesStore extends Store {
36 40
37 @observable filterNeedle = null; 41 @observable filterNeedle = null;
38 42
43 // Array of service IDs that have recently been used
44 // [0] => Most recent, [n] => Least recent
45 // No service ID should be in the list multiple times, not all service IDs have to be in the list
46 @observable lastUsedServices = [];
47
39 constructor(...args) { 48 constructor(...args) {
40 super(...args); 49 super(...args);
41 50
@@ -49,6 +58,7 @@ export default class ServicesStore extends Store {
49 this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this)); 58 this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this));
50 this.actions.service.updateService.listen(this._updateService.bind(this)); 59 this.actions.service.updateService.listen(this._updateService.bind(this));
51 this.actions.service.deleteService.listen(this._deleteService.bind(this)); 60 this.actions.service.deleteService.listen(this._deleteService.bind(this));
61 this.actions.service.openDarkmodeCss.listen(this._openDarkmodeCss.bind(this));
52 this.actions.service.clearCache.listen(this._clearCache.bind(this)); 62 this.actions.service.clearCache.listen(this._clearCache.bind(this));
53 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); 63 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this));
54 this.actions.service.detachService.listen(this._detachService.bind(this)); 64 this.actions.service.detachService.listen(this._detachService.bind(this));
@@ -72,6 +82,7 @@ export default class ServicesStore extends Store {
72 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this)); 82 this.actions.service.toggleAudio.listen(this._toggleAudio.bind(this));
73 this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); 83 this.actions.service.openDevTools.listen(this._openDevTools.bind(this));
74 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); 84 this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this));
85 this.actions.service.setHibernation.listen(this._setHibernation.bind(this));
75 86
76 this.registerReactions([ 87 this.registerReactions([
77 this._focusServiceReaction.bind(this), 88 this._focusServiceReaction.bind(this),
@@ -98,6 +109,16 @@ export default class ServicesStore extends Store {
98 () => this.stores.settings.app.spellcheckerLanguage, 109 () => this.stores.settings.app.spellcheckerLanguage,
99 () => this._shareSettingsWithServiceProcess(), 110 () => this._shareSettingsWithServiceProcess(),
100 ); 111 );
112
113 reaction(
114 () => this.stores.settings.app.darkMode,
115 () => this._shareSettingsWithServiceProcess(),
116 );
117
118 reaction(
119 () => this.stores.settings.app.universalDarkMode,
120 () => this._shareSettingsWithServiceProcess(),
121 );
101 } 122 }
102 123
103 @computed get all() { 124 @computed get all() {
@@ -128,7 +149,35 @@ export default class ServicesStore extends Store {
128 const { keepAllWorkspacesLoaded } = this.stores.workspaces.settings; 149 const { keepAllWorkspacesLoaded } = this.stores.workspaces.settings;
129 const services = this.allServicesRequest.execute().result || []; 150 const services = this.allServicesRequest.execute().result || [];
130 const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); 151 const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled);
131 return keepAllWorkspacesLoaded ? filteredServices : workspaceStore.filterServicesByActiveWorkspace(filteredServices); 152
153 let displayedServices;
154 if (keepAllWorkspacesLoaded) {
155 // Keep all enabled services loaded
156 displayedServices = filteredServices;
157 } else {
158 // Keep all services in current workspace loaded
159 displayedServices = workspaceStore.filterServicesByActiveWorkspace(filteredServices);
160
161 // Keep all services active in workspaces that should be kept loaded
162 for (const workspace of this.stores.workspaces.workspaces) {
163 // Check if workspace needs to be kept loaded
164 if (workspace.services.includes(KEEP_WS_LOADED_USID)) {
165 // Get services for workspace
166 const serviceIDs = workspace.services.filter(i => i !== KEEP_WS_LOADED_USID);
167 const wsServices = filteredServices.filter(service => serviceIDs.includes(service.id));
168
169 displayedServices = [
170 ...displayedServices,
171 ...wsServices,
172 ];
173 }
174 }
175
176 // Make sure every service is in the list only once
177 displayedServices = displayedServices.filter((v, i, a) => a.indexOf(v) === i);
178 }
179
180 return displayedServices;
132 } 181 }
133 182
134 @computed get filtered() { 183 @computed get filtered() {
@@ -185,7 +234,6 @@ export default class ServicesStore extends Store {
185 234
186 if (redirect) { 235 if (redirect) {
187 this.stores.router.push('/settings/recipes'); 236 this.stores.router.push('/settings/recipes');
188 gaEvent('Service', 'create', recipeId);
189 } 237 }
190 } 238 }
191 239
@@ -262,7 +310,6 @@ export default class ServicesStore extends Store {
262 310
263 if (redirect) { 311 if (redirect) {
264 this.stores.router.push('/settings/services'); 312 this.stores.router.push('/settings/services');
265 gaEvent('Service', 'update', service.recipe.id);
266 } 313 }
267 } 314 }
268 315
@@ -277,19 +324,35 @@ export default class ServicesStore extends Store {
277 remove(result, c => c.id === serviceId); 324 remove(result, c => c.id === serviceId);
278 }); 325 });
279 326
280 const service = this.one(serviceId);
281
282 await request._promise; 327 await request._promise;
283 this.actionStatus = request.result.status; 328 this.actionStatus = request.result.status;
329 }
330
331 @action async _openDarkmodeCss({ recipe }) {
332 // Get directory for recipe
333 const normalDirectory = getRecipeDirectory(recipe);
334 const devDirectory = getDevRecipeDirectory(recipe);
335 let directory;
336
337 if (await fs.pathExists(normalDirectory)) {
338 directory = normalDirectory;
339 } else if (await fs.pathExists(devDirectory)) {
340 directory = devDirectory;
341 } else {
342 // Recipe cannot be found on drive
343 return;
344 }
284 345
285 gaEvent('Service', 'delete', service.recipe.id); 346 // Create and open darkmode.css
347 const file = path.join(directory, 'darkmode.css');
348 await fs.ensureFile(file);
349 shell.showItemInFolder(file);
286 } 350 }
287 351
288 @action async _clearCache({ serviceId }) { 352 @action async _clearCache({ serviceId }) {
289 this.clearCacheRequest.reset(); 353 this.clearCacheRequest.reset();
290 const request = this.clearCacheRequest.execute(serviceId); 354 const request = this.clearCacheRequest.execute(serviceId);
291 await request._promise; 355 await request._promise;
292 gaEvent('Service', 'clear cache');
293 } 356 }
294 357
295 @action _setActive({ serviceId, keepActiveRoute }) { 358 @action _setActive({ serviceId, keepActiveRoute }) {
@@ -301,7 +364,9 @@ export default class ServicesStore extends Store {
301 }); 364 });
302 service.isActive = true; 365 service.isActive = true;
303 366
304 statsEvent('activate-service', service.recipe.id); 367 // Update list of last used services
368 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId);
369 this.lastUsedServices.unshift(serviceId);
305 370
306 this._focusActiveService(); 371 this._focusActiveService();
307 } 372 }
@@ -406,7 +471,19 @@ export default class ServicesStore extends Store {
406 }, 471 },
407 }); 472 });
408 } else if (channel === 'notification') { 473 } else if (channel === 'notification') {
409 const options = args[0].options; 474 const { options } = args[0];
475
476 // Check if we are in scheduled Do-not-Disturb time
477 const {
478 scheduledDNDEnabled,
479 scheduledDNDStart,
480 scheduledDNDEnd,
481 } = this.stores.settings.all.app;
482
483 if (scheduledDNDEnabled && isInTimeframe(scheduledDNDStart, scheduledDNDEnd)) {
484 return;
485 }
486
410 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.app.isAppMuted) { 487 if (service.recipe.hasNotificationSound || service.isMuted || this.stores.settings.all.app.isAppMuted) {
411 Object.assign(options, { 488 Object.assign(options, {
412 silent: true, 489 silent: true,
@@ -414,8 +491,17 @@ export default class ServicesStore extends Store {
414 } 491 }
415 492
416 if (service.isNotificationEnabled) { 493 if (service.isNotificationEnabled) {
417 const title = typeof args[0].title === 'string' ? args[0].title : service.name; 494 let title = `Notification from ${service.name}`;
418 options.body = typeof options.body === 'string' ? options.body : ''; 495 if (!this.stores.settings.all.app.privateNotifications) {
496 options.body = typeof options.body === 'string' ? options.body : '';
497 title = typeof args[0].title === 'string' ? args[0].title : service.name;
498 } else {
499 // Remove message data from notification in private mode
500 options.body = '';
501 options.icon = '/assets/img/notification-badge.gif';
502 }
503
504 console.log(title, options);
419 505
420 this.actions.app.notify({ 506 this.actions.app.notify({
421 notificationId: args[0].notificationId, 507 notificationId: args[0].notificationId,
@@ -533,7 +619,7 @@ export default class ServicesStore extends Store {
533 } 619 }
534 620
535 @action _reorderService({ oldIndex, newIndex }) { 621 @action _reorderService({ oldIndex, newIndex }) {
536 const showDisabledServices = this.stores.settings.all.app.showDisabledServices; 622 const { showDisabledServices } = this.stores.settings.all.app;
537 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]); 623 const oldEnabledSortIndex = showDisabledServices ? oldIndex : this.all.indexOf(this.enabled[oldIndex]);
538 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]); 624 const newEnabledSortIndex = showDisabledServices ? newIndex : this.all.indexOf(this.enabled[newIndex]);
539 625
@@ -552,8 +638,6 @@ export default class ServicesStore extends Store {
552 service.order = services[s.id]; 638 service.order = services[s.id];
553 }); 639 });
554 }); 640 });
555
556 this._reorderAnalytics();
557 } 641 }
558 642
559 @action _toggleNotifications({ serviceId }) { 643 @action _toggleNotifications({ serviceId }) {
@@ -598,6 +682,11 @@ export default class ServicesStore extends Store {
598 } 682 }
599 } 683 }
600 684
685 @action _setHibernation({ serviceId, hibernating }) {
686 const service = this.one(serviceId);
687 service.isHibernating = hibernating;
688 }
689
601 // Reactions 690 // Reactions
602 _focusServiceReaction() { 691 _focusServiceReaction() {
603 const service = this.active; 692 const service = this.active;
@@ -629,8 +718,8 @@ export default class ServicesStore extends Store {
629 } 718 }
630 719
631 _getUnreadMessageCountReaction() { 720 _getUnreadMessageCountReaction() {
632 const showMessageBadgeWhenMuted = this.stores.settings.all.app.showMessageBadgeWhenMuted; 721 const { showMessageBadgeWhenMuted } = this.stores.settings.all.app;
633 const showMessageBadgesEvenWhenMuted = this.stores.ui.showMessageBadgesEvenWhenMuted; 722 const { showMessageBadgesEvenWhenMuted } = this.stores.ui;
634 723
635 const unreadDirectMessageCount = this.allDisplayed 724 const unreadDirectMessageCount = this.allDisplayed
636 .filter(s => (showMessageBadgeWhenMuted || s.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && s.isBadgeEnabled) 725 .filter(s => (showMessageBadgeWhenMuted || s.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && s.isBadgeEnabled)
@@ -759,10 +848,6 @@ export default class ServicesStore extends Store {
759 } 848 }
760 } 849 }
761 850
762 _reorderAnalytics = debounce(() => {
763 gaEvent('Service', 'order');
764 }, ms('5s'));
765
766 _wrapIndex(index, delta, size) { 851 _wrapIndex(index, delta, size) {
767 return (((index + delta) % size) + size) % size; 852 return (((index + delta) % size) + size) % size;
768 } 853 }
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index 75bb38fe0..df0fc77e9 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -1,17 +1,18 @@
1import { ipcRenderer } from 'electron'; 1import { ipcRenderer, remote } from 'electron';
2import { 2import {
3 action, computed, observable, 3 action, computed, observable, reaction,
4} from 'mobx'; 4} from 'mobx';
5import localStorage from 'mobx-localstorage'; 5import localStorage from 'mobx-localstorage';
6 6
7import Store from './lib/Store'; 7import Store from './lib/Store';
8import Request from './lib/Request'; 8import Request from './lib/Request';
9import { getLocale } from '../helpers/i18n-helpers'; 9import { getLocale } from '../helpers/i18n-helpers';
10import { API } from '../environment';
10 11
11import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES } from '../config'; 12import { DEFAULT_APP_SETTINGS, FILE_SYSTEM_SETTINGS_TYPES, LOCAL_SERVER } from '../config';
12import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 13import { SPELLCHECKER_LOCALES } from '../i18n/languages';
13 14
14const debug = require('debug')('Franz:SettingsStore'); 15const debug = require('debug')('Ferdi:SettingsStore');
15 16
16export default class SettingsStore extends Store { 17export default class SettingsStore extends Store {
17 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings'); 18 @observable updateAppSettingsRequest = new Request(this.api.local, 'updateAppSettings');
@@ -43,6 +44,62 @@ export default class SettingsStore extends Store {
43 44
44 async setup() { 45 async setup() {
45 await this._migrate(); 46 await this._migrate();
47
48 reaction(
49 () => this.all.app.autohideMenuBar,
50 () => remote.getCurrentWindow().setAutoHideMenuBar(
51 this.all.app.autohideMenuBar,
52 ),
53 );
54
55 reaction(
56 () => this.all.app.server,
57 (server) => {
58 if (server === LOCAL_SERVER) {
59 ipcRenderer.send('startLocalServer');
60 }
61 },
62 {
63 fireImmediately: true,
64 },
65 );
66
67 reaction(
68 () => this.all.app.locked,
69 () => {
70 const { router } = window.ferdi.stores;
71
72 if (this.all.app.locked && this.all.app.lockingFeatureEnabled) {
73 // App just got locked, redirect to unlock screen
74 router.push('/auth/locked');
75 } else if (router.location.pathname.includes('/auth/locked')) {
76 // App is unlocked but user is still on locked screen
77 // Redirect to homepage
78 router.push('/');
79 }
80 },
81 );
82
83 // Make sure to lock app on launch if locking feature is enabled
84 setTimeout(() => {
85 if (this.all.app.lockingFeatureEnabled) {
86 // Disable lock first - otherwise the lock might not get activated corrently
87 this.actions.settings.update({
88 type: 'app',
89 data: {
90 locked: false,
91 },
92 });
93 setTimeout(() => {
94 this.actions.settings.update({
95 type: 'app',
96 data: {
97 locked: true,
98 },
99 });
100 }, 0);
101 }
102 }, 1000);
46 } 103 }
47 104
48 @computed get app() { 105 @computed get app() {
@@ -121,6 +178,7 @@ export default class SettingsStore extends Store {
121 runInBackground: legacySettings.runInBackground, 178 runInBackground: legacySettings.runInBackground,
122 enableSystemTray: legacySettings.enableSystemTray, 179 enableSystemTray: legacySettings.enableSystemTray,
123 minimizeToSystemTray: legacySettings.minimizeToSystemTray, 180 minimizeToSystemTray: legacySettings.minimizeToSystemTray,
181 server: API,
124 isAppMuted: legacySettings.isAppMuted, 182 isAppMuted: legacySettings.isAppMuted,
125 enableGPUAcceleration: legacySettings.enableGPUAcceleration, 183 enableGPUAcceleration: legacySettings.enableGPUAcceleration,
126 showMessageBadgeWhenMuted: legacySettings.showMessageBadgeWhenMuted, 184 showMessageBadgeWhenMuted: legacySettings.showMessageBadgeWhenMuted,
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index 297ea1121..d6a2e5fde 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -8,12 +8,11 @@ import { isDevMode } from '../environment';
8import Store from './lib/Store'; 8import Store from './lib/Store';
9import Request from './lib/Request'; 9import Request from './lib/Request';
10import CachedRequest from './lib/CachedRequest'; 10import CachedRequest from './lib/CachedRequest';
11import { gaEvent } from '../lib/analytics';
12import { sleep } from '../helpers/async-helpers'; 11import { sleep } from '../helpers/async-helpers';
13import { getPlan } from '../helpers/plan-helpers'; 12import { getPlan } from '../helpers/plan-helpers';
14import { PLANS } from '../config'; 13import { PLANS } from '../config';
15 14
16const debug = require('debug')('Franz:UserStore'); 15const debug = require('debug')('Ferdi:UserStore');
17 16
18// TODO: split stores into UserStore and AuthStore 17// TODO: split stores into UserStore and AuthStore
19export default class UserStore extends Store { 18export default class UserStore extends Store {
@@ -57,6 +56,8 @@ export default class UserStore extends Store {
57 56
58 @observable isImportLegacyServicesCompleted = false; 57 @observable isImportLegacyServicesCompleted = false;
59 58
59 @observable isLoggingOut = false;
60
60 @observable id; 61 @observable id;
61 62
62 @observable authToken = localStorage.getItem('authToken') || null; 63 @observable authToken = localStorage.getItem('authToken') || null;
@@ -96,7 +97,7 @@ export default class UserStore extends Store {
96 97
97 // Reactions 98 // Reactions
98 this.registerReactions([ 99 this.registerReactions([
99 this._requireAuthenticatedUser, 100 // this._requireAuthenticatedUser,
100 this._getUserData.bind(this), 101 this._getUserData.bind(this),
101 this._resetTrialActivationState.bind(this), 102 this._resetTrialActivationState.bind(this),
102 ]); 103 ]);
@@ -159,7 +160,7 @@ export default class UserStore extends Store {
159 } 160 }
160 161
161 @computed get isPremium() { 162 @computed get isPremium() {
162 return !!this.data.isPremium; 163 return true;
163 } 164 }
164 165
165 @computed get isPremiumOverride() { 166 @computed get isPremiumOverride() {
@@ -174,12 +175,13 @@ export default class UserStore extends Store {
174 } 175 }
175 176
176 @computed get isPro() { 177 @computed get isPro() {
177 if (this.isPremiumOverride) return true; 178 return true;
179 // if (this.isPremiumOverride) return true;
178 180
179 if (!this.team || (!this.team.plan || this.team.state === 'expired')) return false; 181 // if (!this.team || (!this.team.plan || this.team.state === 'expired')) return false;
180 const plan = getPlan(this.team.plan); 182 // const plan = getPlan(this.team.plan);
181 183
182 return plan === PLANS.PRO || plan === PLANS.LEGACY; 184 // return plan === PLANS.PRO || plan === PLANS.LEGACY;
183 } 185 }
184 186
185 @computed get legacyServices() { 187 @computed get legacyServices() {
@@ -192,16 +194,12 @@ export default class UserStore extends Store {
192 this._setUserData(authToken); 194 this._setUserData(authToken);
193 195
194 this.stores.router.push('/'); 196 this.stores.router.push('/');
195
196 gaEvent('User', 'login');
197 } 197 }
198 198
199 @action _tokenLogin(authToken) { 199 @action _tokenLogin(authToken) {
200 this._setUserData(authToken); 200 this._setUserData(authToken);
201 201
202 this.stores.router.push('/'); 202 this.stores.router.push('/');
203
204 gaEvent('User', 'tokenLogin');
205 } 203 }
206 204
207 @action async _signup({ 205 @action async _signup({
@@ -219,13 +217,11 @@ export default class UserStore extends Store {
219 currency, 217 currency,
220 }); 218 });
221 219
222 this.hasCompletedSignup = false; 220 this.hasCompletedSignup = true;
223 221
224 this._setUserData(authToken); 222 this._setUserData(authToken);
225 223
226 this.stores.router.push(this.PRICING_ROUTE); 224 this.stores.router.push('/');
227
228 gaEvent('User', 'signup');
229 } 225 }
230 226
231 @action async _retrievePassword({ email }) { 227 @action async _retrievePassword({ email }) {
@@ -233,8 +229,6 @@ export default class UserStore extends Store {
233 229
234 await request._promise; 230 await request._promise;
235 this.actionStatus = request.result.status || []; 231 this.actionStatus = request.result.status || [];
236
237 gaEvent('User', 'retrievePassword');
238 } 232 }
239 233
240 @action async _activateTrial({ planId }) { 234 @action async _activateTrial({ planId }) {
@@ -250,9 +244,6 @@ export default class UserStore extends Store {
250 244
251 this.stores.features.featuresRequest.invalidate({ immediately: true }); 245 this.stores.features.featuresRequest.invalidate({ immediately: true });
252 this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); 246 this.stores.user.getUserInfoRequest.invalidate({ immediately: true });
253
254
255 gaEvent('User', 'activateTrial');
256 } 247 }
257 248
258 @action async _invite({ invites }) { 249 @action async _invite({ invites }) {
@@ -266,8 +257,6 @@ export default class UserStore extends Store {
266 if (this.stores.router.location.pathname.includes(this.INVITE_ROUTE)) { 257 if (this.stores.router.location.pathname.includes(this.INVITE_ROUTE)) {
267 this.stores.router.push('/'); 258 this.stores.router.push('/');
268 } 259 }
269
270 gaEvent('User', 'inviteUsers');
271 } 260 }
272 261
273 @action async _update({ userData }) { 262 @action async _update({ userData }) {
@@ -277,8 +266,6 @@ export default class UserStore extends Store {
277 266
278 this.getUserInfoRequest.patch(() => response.data); 267 this.getUserInfoRequest.patch(() => response.data);
279 this.actionStatus = response.status || []; 268 this.actionStatus = response.status || [];
280
281 gaEvent('User', 'update');
282 } 269 }
283 270
284 @action _resetStatus() { 271 @action _resetStatus() {
@@ -301,12 +288,12 @@ export default class UserStore extends Store {
301 const recipes = services.filter((obj, pos, arr) => arr.map(mapObj => mapObj.recipe.id).indexOf(obj.recipe.id) === pos).map(s => s.recipe.id); 288 const recipes = services.filter((obj, pos, arr) => arr.map(mapObj => mapObj.recipe.id).indexOf(obj.recipe.id) === pos).map(s => s.recipe.id);
302 289
303 // Install recipes 290 // Install recipes
304 for (const recipe of recipes) { 291 for (const recipe of recipes) { // eslint-disable-line no-unused-vars
305 // eslint-disable-next-line 292 // eslint-disable-next-line
306 await this.stores.recipes._install({ recipeId: recipe }); 293 await this.stores.recipes._install({ recipeId: recipe });
307 } 294 }
308 295
309 for (const service of services) { 296 for (const service of services) { // eslint-disable-line no-unused-vars
310 this.actions.service.createFromLegacyService({ 297 this.actions.service.createFromLegacyService({
311 data: service, 298 data: service,
312 }); 299 });