aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores')
-rw-r--r--src/stores/AppStore.js28
-rw-r--r--src/stores/ServicesStore.js80
-rw-r--r--src/stores/SettingsStore.js38
-rw-r--r--src/stores/UIStore.js23
-rw-r--r--src/stores/UserStore.js8
5 files changed, 156 insertions, 21 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 593bf341e..da6055e5f 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -52,7 +52,7 @@ export default class AppStore extends Store {
52 52
53 @observable getAppCacheSizeRequest = new Request(this.api.local, 'getAppCacheSize'); 53 @observable getAppCacheSizeRequest = new Request(this.api.local, 'getAppCacheSize');
54 54
55 @observable clearAppCacheRequest = new Request(this.api.local, 'clearAppCache'); 55 @observable clearAppCacheRequest = new Request(this.api.local, 'clearCache');
56 56
57 @observable autoLaunchOnStart = true; 57 @observable autoLaunchOnStart = true;
58 58
@@ -179,6 +179,10 @@ export default class AppStore extends Store {
179 this.stores.router.push(url); 179 this.stores.router.push(url);
180 }); 180 });
181 181
182 ipcRenderer.on('muteApp', () => {
183 this._toggleMuteApp();
184 });
185
182 this.locale = this._getDefaultLocale(); 186 this.locale = this._getDefaultLocale();
183 187
184 setTimeout(() => { 188 setTimeout(() => {
@@ -214,13 +218,16 @@ export default class AppStore extends Store {
214 // macOS catalina notifications hack 218 // macOS catalina notifications hack
215 // notifications got stuck after upgrade but forcing a notification 219 // notifications got stuck after upgrade but forcing a notification
216 // via `new Notification` triggered the permission request 220 // via `new Notification` triggered the permission request
217 if (isMac && !localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) { 221 if (isMac) {
218 // eslint-disable-next-line no-new 222 if (!localStorage.getItem(CATALINA_NOTIFICATION_HACK_KEY)) {
219 new window.Notification('Welcome to Franz 5', { 223 debug('Triggering macOS Catalina notification permission trigger');
220 body: 'Have a wonderful day & happy messaging.', 224 // eslint-disable-next-line no-new
221 }); 225 new window.Notification('Welcome to Franz 5', {
226 body: 'Have a wonderful day & happy messaging.',
227 });
222 228
223 localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true); 229 localStorage.setItem(CATALINA_NOTIFICATION_HACK_KEY, true);
230 }
224 } 231 }
225 } 232 }
226 233
@@ -374,8 +381,11 @@ export default class AppStore extends Store {
374 const allServiceIds = await getServiceIdsFromPartitions(); 381 const allServiceIds = await getServiceIdsFromPartitions();
375 const allOrphanedServiceIds = allServiceIds.filter(id => !this.stores.services.all.find(s => id.replace('service-', '') === s.id)); 382 const allOrphanedServiceIds = allServiceIds.filter(id => !this.stores.services.all.find(s => id.replace('service-', '') === s.id));
376 383
377 await Promise.all(allOrphanedServiceIds.map(id => removeServicePartitionDirectory(id))); 384 try {
378 385 await Promise.all(allOrphanedServiceIds.map(id => removeServicePartitionDirectory(id)));
386 } catch (ex) {
387 console.log('Error while deleting service partition directory - ', ex);
388 }
379 await Promise.all(this.stores.services.all.map(s => this.actions.service.clearCache({ serviceId: s.id }))); 389 await Promise.all(this.stores.services.all.map(s => this.actions.service.clearCache({ serviceId: s.id })));
380 390
381 await clearAppCache._promise; 391 await clearAppCache._promise;
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 985887d2d..80c7d7e81 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -5,7 +5,7 @@ import {
5 computed, 5 computed,
6 observable, 6 observable,
7} from 'mobx'; 7} from 'mobx';
8import { remove } from 'lodash'; 8import { debounce, remove } from 'lodash';
9import ms from 'ms'; 9import ms from 'ms';
10import fs from 'fs-extra'; 10import fs from 'fs-extra';
11import path from 'path'; 11import path from 'path';
@@ -127,6 +127,60 @@ export default class ServicesStore extends Store {
127 ); 127 );
128 } 128 }
129 129
130 initialize() {
131 super.initialize();
132
133 // Check services to become hibernated
134 this.serviceMaintenanceTick();
135 }
136
137 teardown() {
138 super.teardown();
139
140 // Stop checking services for hibernation
141 this.serviceMaintenanceTick.cancel();
142 }
143
144 /**
145 * Сheck for services to become hibernated.
146 */
147 serviceMaintenanceTick = debounce(() => {
148 this._serviceMaintenance();
149 this.serviceMaintenanceTick();
150 debug('Service maintenance tick');
151 }, ms('10s'));
152
153 /**
154 * Run various maintenance tasks on services
155 */
156 _serviceMaintenance() {
157 this.all.forEach((service) => {
158 // Defines which services should be hibernated.
159 if (!service.isActive && (Date.now() - service.lastUsed > ms('5m'))) {
160 // If service is stale for 5 min, hibernate it.
161 this._hibernate({ serviceId: service.id });
162 }
163
164 if (service.lastPoll && (service.lastPoll) - service.lastPollAnswer > ms('30s')) {
165 // If service did not reply for more than 30s try to reload.
166 if (!service.isActive) {
167 if (this.stores.app.isOnline && service.lostRecipeReloadAttempt < 3) {
168 service.webview.reload();
169 service.lostRecipeReloadAttempt += 1;
170
171 service.lostRecipeConnection = false;
172 }
173 } else {
174 service.lostRecipeConnection = true;
175 }
176 } else {
177 service.lostRecipeConnection = false;
178 service.lostRecipeReloadAttempt = 0;
179 }
180 });
181 }
182
183 // Computed props
130 @computed get all() { 184 @computed get all() {
131 if (this.stores.user.isLoggedIn) { 185 if (this.stores.user.isLoggedIn) {
132 const services = this.allServicesRequest.execute().result; 186 const services = this.allServicesRequest.execute().result;
@@ -351,7 +405,17 @@ export default class ServicesStore extends Store {
351 405
352 // Create and open file 406 // Create and open file
353 const filePath = path.join(directory, file); 407 const filePath = path.join(directory, file);
354 await fs.ensureFile(filePath); 408 if (file === 'user.js') {
409 if (!await fs.exists(filePath)) {
410 await fs.writeFile(filePath, `module.exports = (config, Ferdi) => {
411 // Write your scripts here
412 console.log("Hello, World!", config);
413}
414`);
415 }
416 } else {
417 await fs.ensureFile(filePath);
418 }
355 shell.showItemInFolder(filePath); 419 shell.showItemInFolder(filePath);
356 } 420 }
357 421
@@ -369,6 +433,7 @@ export default class ServicesStore extends Store {
369 this.all[index].isActive = false; 433 this.all[index].isActive = false;
370 }); 434 });
371 service.isActive = true; 435 service.isActive = true;
436 service.lastUsed = Date.now();
372 437
373 // Update list of last used services 438 // Update list of last used services
374 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId); 439 this.lastUsedServices = this.lastUsedServices.filter(id => id !== serviceId);
@@ -465,10 +530,16 @@ export default class ServicesStore extends Store {
465 const service = this.one(serviceId); 530 const service = this.one(serviceId);
466 531
467 if (channel === 'hello') { 532 if (channel === 'hello') {
533 debug('Received hello event from', serviceId);
534
468 this._initRecipePolling(service.id); 535 this._initRecipePolling(service.id);
469 this._initializeServiceRecipeInWebview(serviceId); 536 this._initializeServiceRecipeInWebview(serviceId);
470 this._shareSettingsWithServiceProcess(); 537 this._shareSettingsWithServiceProcess();
538 } else if (channel === 'alive') {
539 service.lastPollAnswer = Date.now();
471 } else if (channel === 'messages') { 540 } else if (channel === 'messages') {
541 debug(`Received unread message info from '${serviceId}'`, args[0]);
542
472 this.actions.service.setUnreadMessageCount({ 543 this.actions.service.setUnreadMessageCount({
473 serviceId, 544 serviceId,
474 count: { 545 count: {
@@ -590,6 +661,7 @@ export default class ServicesStore extends Store {
590 if (!service.isEnabled) return; 661 if (!service.isEnabled) return;
591 662
592 service.resetMessageCount(); 663 service.resetMessageCount();
664 service.lostRecipeConnection = false;
593 665
594 // service.webview.loadURL(service.url); 666 // service.webview.loadURL(service.url);
595 service.webview.reload(); 667 service.webview.reload();
@@ -699,6 +771,7 @@ export default class ServicesStore extends Store {
699 const service = this.active; 771 const service = this.active;
700 if (service) { 772 if (service) {
701 this.actions.service.focusService({ serviceId: service.id }); 773 this.actions.service.focusService({ serviceId: service.id });
774 document.title = `Ferdi - ${service.name}`;
702 } 775 }
703 } 776 }
704 777
@@ -766,7 +839,7 @@ export default class ServicesStore extends Store {
766 const isMuted = isAppMuted || service.isMuted; 839 const isMuted = isAppMuted || service.isMuted;
767 840
768 if (isAttached) { 841 if (isAttached) {
769 service.webview.setAudioMuted(isMuted); 842 service.webview.audioMuted = isMuted;
770 } 843 }
771 }); 844 });
772 } 845 }
@@ -852,6 +925,7 @@ export default class ServicesStore extends Store {
852 service.webview.send('poll'); 925 service.webview.send('poll');
853 926
854 service.timer = setTimeout(loop, delay); 927 service.timer = setTimeout(loop, delay);
928 service.lastPoll = Date.now();
855 }; 929 };
856 930
857 loop(); 931 loop();
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index 71d4e1702..227eb2145 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -110,6 +110,7 @@ export default class SettingsStore extends Store {
110 } 110 }
111 debug('Get appSettings resolves', resp.type, resp.data); 111 debug('Get appSettings resolves', resp.type, resp.data);
112 Object.assign(this._fileSystemSettingsCache[resp.type], resp.data); 112 Object.assign(this._fileSystemSettingsCache[resp.type], resp.data);
113 ipcRenderer.send('initialAppSettings', resp);
113 }); 114 });
114 115
115 this.fileSystemSettingsTypes.forEach((type) => { 116 this.fileSystemSettingsTypes.forEach((type) => {
@@ -263,5 +264,42 @@ export default class SettingsStore extends Store {
263 }, 264 },
264 }); 265 });
265 } 266 }
267
268 if (!this.all.migration['5.4.4-beta.4-settings']) {
269 this.actions.settings.update({
270 type: 'app',
271 data: {
272 todoServer: 'isUsingCustomTodoService',
273 customTodoServer: legacySettings.todoServer,
274 },
275 });
276
277 this.actions.settings.update({
278 type: 'migration',
279 data: {
280 '5.4.4-beta.4-settings': true,
281 },
282 });
283
284 debug('Migrated old todo setting to new custom todo setting');
285 }
286
287 if (!this.all.migration['5.4.4-beta.4-settings']) {
288 this.actions.settings.update({
289 type: 'app',
290 data: {
291 automaticUpdates: !(legacySettings.noUpdates),
292 },
293 });
294
295 this.actions.settings.update({
296 type: 'migration',
297 data: {
298 '5.4.4-beta.4-settings': true,
299 },
300 });
301
302 debug('Migrated updates settings');
303 }
266 } 304 }
267} 305}
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
index 6941cf086..f6e059bfb 100644
--- a/src/stores/UIStore.js
+++ b/src/stores/UIStore.js
@@ -5,14 +5,14 @@ import { theme } from '@meetfranz/theme';
5import { remote } from 'electron'; 5import { remote } from 'electron';
6 6
7import Store from './lib/Store'; 7import Store from './lib/Store';
8import { isMac } from '../environment'; 8import { isMac, isWindows } from '../environment';
9 9
10const { nativeTheme, systemPreferences } = remote; 10const { nativeTheme, systemPreferences } = remote;
11 11
12export default class UIStore extends Store { 12export default class UIStore extends Store {
13 @observable showServicesUpdatedInfoBar = false; 13 @observable showServicesUpdatedInfoBar = false;
14 14
15 @observable isOsDarkThemeActive = isMac 15 @observable isOsDarkThemeActive = (isMac || isWindows)
16 ? nativeTheme.shouldUseDarkColors 16 ? nativeTheme.shouldUseDarkColors
17 : false; 17 : false;
18 18
@@ -36,6 +36,13 @@ export default class UIStore extends Store {
36 }, 36 },
37 ); 37 );
38 } 38 }
39
40 if (isWindows) {
41 nativeTheme.on('updated', () => {
42 this.isOsDarkThemeActive = nativeTheme.shouldUseDarkColors;
43 this.actions.service.shareSettingsWithServiceProcess();
44 });
45 }
39 } 46 }
40 47
41 setup() { 48 setup() {
@@ -56,16 +63,16 @@ export default class UIStore extends Store {
56 } 63 }
57 64
58 @computed get isDarkThemeActive() { 65 @computed get isDarkThemeActive() {
59 const isMacWithAdaptableInDarkMode = isMac 66 const isMacOrWindowsWithAdaptableInDarkMode = (isMac || isWindows)
60 && this.stores.settings.all.app.adaptableDarkMode 67 && this.stores.settings.all.app.adaptableDarkMode
61 && this.isOsDarkThemeActive; 68 && this.isOsDarkThemeActive;
62 const isMacWithoutAdaptableInDarkMode = isMac 69 const isMacOrWindowsWithoutAdaptableInDarkMode = (isMac || isWindows)
63 && this.stores.settings.all.app.darkMode 70 && this.stores.settings.all.app.darkMode
64 && !this.stores.settings.all.app.adaptableDarkMode; 71 && !this.stores.settings.all.app.adaptableDarkMode;
65 const isNotMacInDarkMode = !isMac && this.stores.settings.all.app.darkMode; 72 const isMacOrWindowsNotInDarkMode = !(isMac || isWindows) && this.stores.settings.all.app.darkMode;
66 return !!(isMacWithAdaptableInDarkMode 73 return !!(isMacOrWindowsWithAdaptableInDarkMode
67 || isMacWithoutAdaptableInDarkMode 74 || isMacOrWindowsWithoutAdaptableInDarkMode
68 || isNotMacInDarkMode); 75 || isMacOrWindowsNotInDarkMode);
69 } 76 }
70 77
71 @computed get theme() { 78 @computed get theme() {
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index ec0b0cf8d..3a53d150d 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -34,6 +34,8 @@ export default class UserStore extends Store {
34 34
35 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`; 35 PASSWORD_ROUTE = `${this.BASE_ROUTE}/password`;
36 36
37 CHANGE_SERVER_ROUTE = `${this.BASE_ROUTE}/server`;
38
37 @observable loginRequest = new Request(this.api.user, 'login'); 39 @observable loginRequest = new Request(this.api.user, 'login');
38 40
39 @observable signupRequest = new Request(this.api.user, 'signup'); 41 @observable signupRequest = new Request(this.api.user, 'signup');
@@ -97,7 +99,7 @@ export default class UserStore extends Store {
97 99
98 // Reactions 100 // Reactions
99 this.registerReactions([ 101 this.registerReactions([
100 // this._requireAuthenticatedUser, 102 this._requireAuthenticatedUser,
101 this._getUserData.bind(this), 103 this._getUserData.bind(this),
102 this._resetTrialActivationState.bind(this), 104 this._resetTrialActivationState.bind(this),
103 ]); 105 ]);
@@ -137,6 +139,10 @@ export default class UserStore extends Store {
137 return this.PASSWORD_ROUTE; 139 return this.PASSWORD_ROUTE;
138 } 140 }
139 141
142 get changeServerRoute() {
143 return this.CHANGE_SERVER_ROUTE;
144 }
145
140 // Data 146 // Data
141 @computed get isLoggedIn() { 147 @computed get isLoggedIn() {
142 return Boolean(localStorage.getItem('authToken')); 148 return Boolean(localStorage.getItem('authToken'));