aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/ServicesStore.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores/ServicesStore.js')
-rw-r--r--src/stores/ServicesStore.js133
1 files changed, 109 insertions, 24 deletions
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 }