diff options
author | Amine Mouafik <amine@mouafik.fr> | 2019-05-12 20:00:41 +0700 |
---|---|---|
committer | Amine Mouafik <amine@mouafik.fr> | 2019-05-12 20:00:41 +0700 |
commit | d8a1d5f9151cc31f4c2b5c0096a35e49b2c74d61 (patch) | |
tree | 3974d449d8ef389fc61bf880ae758b5debc22a80 /src/stores | |
parent | Use dark background in SVG logo (diff) | |
parent | Update CHANGELOG.md (diff) | |
download | ferdium-app-d8a1d5f9151cc31f4c2b5c0096a35e49b2c74d61.tar.gz ferdium-app-d8a1d5f9151cc31f4c2b5c0096a35e49b2c74d61.tar.zst ferdium-app-d8a1d5f9151cc31f4c2b5c0096a35e49b2c74d61.zip |
Merge tag 'v5.1.0'
# Conflicts:
# README.md
# src/components/layout/AppLayout.js
Diffstat (limited to 'src/stores')
-rw-r--r-- | src/stores/AppStore.js | 84 | ||||
-rw-r--r-- | src/stores/FeaturesStore.js | 35 | ||||
-rw-r--r-- | src/stores/PaymentStore.js | 17 | ||||
-rw-r--r-- | src/stores/RecipePreviewsStore.js | 3 | ||||
-rw-r--r-- | src/stores/RequestStore.js | 3 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 30 | ||||
-rw-r--r-- | src/stores/UIStore.js | 14 | ||||
-rw-r--r-- | src/stores/UserStore.js | 32 | ||||
-rw-r--r-- | src/stores/index.js | 4 | ||||
-rw-r--r-- | src/stores/lib/Reaction.js | 19 | ||||
-rw-r--r-- | src/stores/lib/Request.js | 6 |
11 files changed, 158 insertions, 89 deletions
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index dd4642d70..72c4b4d0b 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -1,21 +1,25 @@ | |||
1 | import { remote, ipcRenderer, shell } from 'electron'; | 1 | import { remote, ipcRenderer, shell } from 'electron'; |
2 | import { action, computed, observable } from 'mobx'; | 2 | import { |
3 | action, computed, observable, reaction, | ||
4 | } from 'mobx'; | ||
3 | import moment from 'moment'; | 5 | import moment from 'moment'; |
4 | import key from 'keymaster'; | ||
5 | import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; | 6 | import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; |
6 | import AutoLaunch from 'auto-launch'; | 7 | import AutoLaunch from 'auto-launch'; |
7 | import prettyBytes from 'pretty-bytes'; | 8 | import prettyBytes from 'pretty-bytes'; |
9 | import ms from 'ms'; | ||
10 | import { URL } from 'url'; | ||
8 | 11 | ||
9 | import Store from './lib/Store'; | 12 | import Store from './lib/Store'; |
10 | import Request from './lib/Request'; | 13 | import Request from './lib/Request'; |
11 | import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; | 14 | import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; |
12 | import { isMac, isLinux, isWindows } from '../environment'; | 15 | import { isMac } from '../environment'; |
13 | import locales from '../i18n/translations'; | 16 | import locales from '../i18n/translations'; |
14 | import { gaEvent } from '../lib/analytics'; | 17 | import { gaEvent, gaPage, statsEvent } from '../lib/analytics'; |
15 | import { onVisibilityChange } from '../helpers/visibility-helper'; | 18 | import { onVisibilityChange } from '../helpers/visibility-helper'; |
16 | import { getLocale } from '../helpers/i18n-helpers'; | 19 | import { getLocale } from '../helpers/i18n-helpers'; |
17 | 20 | ||
18 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; | 21 | import { getServiceIdsFromPartitions, removeServicePartitionDirectory } from '../helpers/service-helpers.js'; |
22 | import { isValidExternalURL } from '../helpers/url-helpers'; | ||
19 | 23 | ||
20 | const debug = require('debug')('Franz:AppStore'); | 24 | const debug = require('debug')('Franz:AppStore'); |
21 | 25 | ||
@@ -63,6 +67,8 @@ export default class AppStore extends Store { | |||
63 | 67 | ||
64 | @observable isFocused = true; | 68 | @observable isFocused = true; |
65 | 69 | ||
70 | @observable nextAppReleaseVersion = null; | ||
71 | |||
66 | dictionaries = []; | 72 | dictionaries = []; |
67 | 73 | ||
68 | constructor(...args) { | 74 | constructor(...args) { |
@@ -110,16 +116,16 @@ export default class AppStore extends Store { | |||
110 | // Check if system is muted | 116 | // Check if system is muted |
111 | // There are no events to subscribe so we need to poll everey 5s | 117 | // There are no events to subscribe so we need to poll everey 5s |
112 | this._systemDND(); | 118 | this._systemDND(); |
113 | setInterval(() => this._systemDND(), 5000); | 119 | setInterval(() => this._systemDND(), ms('5s')); |
114 | 120 | ||
115 | // Check for updates once every 4 hours | 121 | // Check for updates once every 4 hours |
116 | setInterval(() => this._checkForUpdates(), CHECK_INTERVAL); | 122 | setInterval(() => this._checkForUpdates(), CHECK_INTERVAL); |
117 | // Check for an update in 30s (need a delay to prevent Squirrel Installer lock file issues) | 123 | // Check for an update in 30s (need a delay to prevent Squirrel Installer lock file issues) |
118 | setTimeout(() => this._checkForUpdates(), 30000); | 124 | setTimeout(() => this._checkForUpdates(), ms('30s')); |
119 | ipcRenderer.on('autoUpdate', (event, data) => { | 125 | ipcRenderer.on('autoUpdate', (event, data) => { |
120 | if (data.available) { | 126 | if (data.available) { |
121 | this.updateStatus = this.updateStatusTypes.AVAILABLE; | 127 | this.updateStatus = this.updateStatusTypes.AVAILABLE; |
122 | 128 | this.nextAppReleaseVersion = data.version; | |
123 | if (isMac) { | 129 | if (isMac) { |
124 | app.dock.bounce(); | 130 | app.dock.bounce(); |
125 | } | 131 | } |
@@ -143,32 +149,14 @@ export default class AppStore extends Store { | |||
143 | 149 | ||
144 | // Handle deep linking (franz://) | 150 | // Handle deep linking (franz://) |
145 | ipcRenderer.on('navigateFromDeepLink', (event, data) => { | 151 | ipcRenderer.on('navigateFromDeepLink', (event, data) => { |
146 | const { url } = data; | 152 | debug('Navigate from deep link', data); |
153 | let { url } = data; | ||
147 | if (!url) return; | 154 | if (!url) return; |
148 | 155 | ||
149 | this.stores.router.push(data.url); | 156 | url = url.replace(/\/$/, ''); |
150 | }); | ||
151 | |||
152 | // Set active the next service | ||
153 | key( | ||
154 | '⌘+pagedown, ctrl+pagedown, ⌘+alt+right, ctrl+tab', () => { | ||
155 | this.actions.service.setActiveNext(); | ||
156 | }, | ||
157 | ); | ||
158 | 157 | ||
159 | // Set active the prev service | 158 | this.stores.router.push(url); |
160 | key( | 159 | }); |
161 | '⌘+pageup, ctrl+pageup, ⌘+alt+left, ctrl+shift+tab', () => { | ||
162 | this.actions.service.setActivePrev(); | ||
163 | }, | ||
164 | ); | ||
165 | |||
166 | // Global Mute | ||
167 | key( | ||
168 | '⌘+shift+m ctrl+shift+m', () => { | ||
169 | this.actions.app.toggleMuteApp(); | ||
170 | }, | ||
171 | ); | ||
172 | 160 | ||
173 | this.locale = this._getDefaultLocale(); | 161 | this.locale = this._getDefaultLocale(); |
174 | 162 | ||
@@ -181,6 +169,13 @@ export default class AppStore extends Store { | |||
181 | 169 | ||
182 | debug('Window is visible/focused', isVisible); | 170 | debug('Window is visible/focused', isVisible); |
183 | }); | 171 | }); |
172 | |||
173 | // analytics autorun | ||
174 | reaction(() => this.stores.router.location.pathname, (pathname) => { | ||
175 | gaPage(pathname); | ||
176 | }); | ||
177 | |||
178 | statsEvent('app-start'); | ||
184 | } | 179 | } |
185 | 180 | ||
186 | @computed get cacheSize() { | 181 | @computed get cacheSize() { |
@@ -193,7 +188,15 @@ export default class AppStore extends Store { | |||
193 | }) { | 188 | }) { |
194 | if (this.stores.settings.all.app.isAppMuted) return; | 189 | if (this.stores.settings.all.app.isAppMuted) return; |
195 | 190 | ||
191 | // TODO: is there a simple way to use blobs for notifications without storing them on disk? | ||
192 | if (options.icon.startsWith('blob:')) { | ||
193 | delete options.icon; | ||
194 | } | ||
195 | |||
196 | const notification = new window.Notification(title, options); | 196 | const notification = new window.Notification(title, options); |
197 | |||
198 | debug('New notification', title, options); | ||
199 | |||
197 | notification.onclick = (e) => { | 200 | notification.onclick = (e) => { |
198 | if (serviceId) { | 201 | if (serviceId) { |
199 | this.actions.service.sendIPCMessage({ | 202 | this.actions.service.sendIPCMessage({ |
@@ -203,12 +206,13 @@ export default class AppStore extends Store { | |||
203 | }); | 206 | }); |
204 | 207 | ||
205 | this.actions.service.setActive({ serviceId }); | 208 | this.actions.service.setActive({ serviceId }); |
206 | 209 | mainWindow.show(); | |
207 | if (isWindows) { | 210 | if (app.mainWindow.isMinimized()) { |
208 | mainWindow.restore(); | 211 | mainWindow.restore(); |
209 | } else if (isLinux) { | ||
210 | mainWindow.show(); | ||
211 | } | 212 | } |
213 | mainWindow.focus(); | ||
214 | |||
215 | debug('Notification click handler'); | ||
212 | } | 216 | } |
213 | }; | 217 | }; |
214 | } | 218 | } |
@@ -244,7 +248,14 @@ export default class AppStore extends Store { | |||
244 | } | 248 | } |
245 | 249 | ||
246 | @action _openExternalUrl({ url }) { | 250 | @action _openExternalUrl({ url }) { |
247 | shell.openExternal(url); | 251 | const parsedUrl = new URL(url); |
252 | debug('open external url', parsedUrl); | ||
253 | |||
254 | if (isValidExternalURL(url)) { | ||
255 | shell.openExternal(url); | ||
256 | } | ||
257 | |||
258 | gaEvent('External URL', 'open', parsedUrl.host); | ||
248 | } | 259 | } |
249 | 260 | ||
250 | @action _checkForUpdates() { | 261 | @action _checkForUpdates() { |
@@ -268,7 +279,6 @@ export default class AppStore extends Store { | |||
268 | 279 | ||
269 | @action _muteApp({ isMuted, overrideSystemMute = true }) { | 280 | @action _muteApp({ isMuted, overrideSystemMute = true }) { |
270 | this.isSystemMuteOverridden = overrideSystemMute; | 281 | this.isSystemMuteOverridden = overrideSystemMute; |
271 | |||
272 | this.actions.settings.update({ | 282 | this.actions.settings.update({ |
273 | type: 'app', | 283 | type: 'app', |
274 | data: { | 284 | data: { |
@@ -305,7 +315,7 @@ export default class AppStore extends Store { | |||
305 | } else { | 315 | } else { |
306 | const deltaTime = moment().diff(this.timeOfflineStart); | 316 | const deltaTime = moment().diff(this.timeOfflineStart); |
307 | 317 | ||
308 | if (deltaTime > 30 * 60 * 1000) { | 318 | if (deltaTime > ms('30m')) { |
309 | this.actions.service.reloadAll(); | 319 | this.actions.service.reloadAll(); |
310 | } | 320 | } |
311 | } | 321 | } |
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 0adee6adf..e7832088b 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js | |||
@@ -1,4 +1,9 @@ | |||
1 | import { computed, observable, reaction } from 'mobx'; | 1 | import { |
2 | computed, | ||
3 | observable, | ||
4 | reaction, | ||
5 | runInAction, | ||
6 | } from 'mobx'; | ||
2 | 7 | ||
3 | import Store from './lib/Store'; | 8 | import Store from './lib/Store'; |
4 | import CachedRequest from './lib/CachedRequest'; | 9 | import CachedRequest from './lib/CachedRequest'; |
@@ -7,6 +12,10 @@ import delayApp from '../features/delayApp'; | |||
7 | import spellchecker from '../features/spellchecker'; | 12 | import spellchecker from '../features/spellchecker'; |
8 | import serviceProxy from '../features/serviceProxy'; | 13 | import serviceProxy from '../features/serviceProxy'; |
9 | import basicAuth from '../features/basicAuth'; | 14 | import basicAuth from '../features/basicAuth'; |
15 | import workspaces from '../features/workspaces'; | ||
16 | import shareFranz from '../features/shareFranz'; | ||
17 | import announcements from '../features/announcements'; | ||
18 | import settingsWS from '../features/settingsWS'; | ||
10 | 19 | ||
11 | import { DEFAULT_FEATURES_CONFIG } from '../config'; | 20 | import { DEFAULT_FEATURES_CONFIG } from '../config'; |
12 | 21 | ||
@@ -15,13 +24,16 @@ export default class FeaturesStore extends Store { | |||
15 | 24 | ||
16 | @observable featuresRequest = new CachedRequest(this.api.features, 'features'); | 25 | @observable featuresRequest = new CachedRequest(this.api.features, 'features'); |
17 | 26 | ||
27 | @observable features = Object.assign({}, DEFAULT_FEATURES_CONFIG); | ||
28 | |||
18 | async setup() { | 29 | async setup() { |
19 | this.registerReactions([ | 30 | this.registerReactions([ |
31 | this._updateFeatures, | ||
20 | this._monitorLoginStatus.bind(this), | 32 | this._monitorLoginStatus.bind(this), |
21 | ]); | 33 | ]); |
22 | 34 | ||
23 | await this.featuresRequest._promise; | 35 | await this.featuresRequest._promise; |
24 | setTimeout(this._enableFeatures.bind(this), 1); | 36 | setTimeout(this._setupFeatures.bind(this), 1); |
25 | 37 | ||
26 | // single key reaction | 38 | // single key reaction |
27 | reaction(() => this.stores.user.data.isPremium, () => { | 39 | reaction(() => this.stores.user.data.isPremium, () => { |
@@ -35,13 +47,16 @@ export default class FeaturesStore extends Store { | |||
35 | return this.defaultFeaturesRequest.execute().result || DEFAULT_FEATURES_CONFIG; | 47 | return this.defaultFeaturesRequest.execute().result || DEFAULT_FEATURES_CONFIG; |
36 | } | 48 | } |
37 | 49 | ||
38 | @computed get features() { | 50 | _updateFeatures = () => { |
51 | const features = Object.assign({}, DEFAULT_FEATURES_CONFIG); | ||
39 | if (this.stores.user.isLoggedIn) { | 52 | if (this.stores.user.isLoggedIn) { |
40 | return this.featuresRequest.execute().result || DEFAULT_FEATURES_CONFIG; | 53 | const requestResult = this.featuresRequest.execute().result; |
54 | Object.assign(features, requestResult); | ||
41 | } | 55 | } |
42 | 56 | runInAction('FeaturesStore::_updateFeatures', () => { | |
43 | return DEFAULT_FEATURES_CONFIG; | 57 | this.features = features; |
44 | } | 58 | }); |
59 | }; | ||
45 | 60 | ||
46 | _monitorLoginStatus() { | 61 | _monitorLoginStatus() { |
47 | if (this.stores.user.isLoggedIn) { | 62 | if (this.stores.user.isLoggedIn) { |
@@ -51,10 +66,14 @@ export default class FeaturesStore extends Store { | |||
51 | } | 66 | } |
52 | } | 67 | } |
53 | 68 | ||
54 | _enableFeatures() { | 69 | _setupFeatures() { |
55 | delayApp(this.stores, this.actions); | 70 | delayApp(this.stores, this.actions); |
56 | spellchecker(this.stores, this.actions); | 71 | spellchecker(this.stores, this.actions); |
57 | serviceProxy(this.stores, this.actions); | 72 | serviceProxy(this.stores, this.actions); |
58 | basicAuth(this.stores, this.actions); | 73 | basicAuth(this.stores, this.actions); |
74 | workspaces(this.stores, this.actions); | ||
75 | shareFranz(this.stores, this.actions); | ||
76 | announcements(this.stores, this.actions); | ||
77 | settingsWS(this.stores, this.actions); | ||
59 | } | 78 | } |
60 | } | 79 | } |
diff --git a/src/stores/PaymentStore.js b/src/stores/PaymentStore.js index 4cabee194..d4de476c8 100644 --- a/src/stores/PaymentStore.js +++ b/src/stores/PaymentStore.js | |||
@@ -10,15 +10,10 @@ export default class PaymentStore extends Store { | |||
10 | 10 | ||
11 | @observable createHostedPageRequest = new Request(this.api.payment, 'getHostedPage'); | 11 | @observable createHostedPageRequest = new Request(this.api.payment, 'getHostedPage'); |
12 | 12 | ||
13 | @observable createDashboardUrlRequest = new Request(this.api.payment, 'getDashboardUrl'); | ||
14 | |||
15 | @observable ordersDataRequest = new CachedRequest(this.api.payment, 'getOrders'); | ||
16 | |||
17 | constructor(...args) { | 13 | constructor(...args) { |
18 | super(...args); | 14 | super(...args); |
19 | 15 | ||
20 | this.actions.payment.createHostedPage.listen(this._createHostedPage.bind(this)); | 16 | this.actions.payment.createHostedPage.listen(this._createHostedPage.bind(this)); |
21 | this.actions.payment.createDashboardUrl.listen(this._createDashboardUrl.bind(this)); | ||
22 | } | 17 | } |
23 | 18 | ||
24 | @computed get plan() { | 19 | @computed get plan() { |
@@ -28,10 +23,6 @@ export default class PaymentStore extends Store { | |||
28 | return this.plansRequest.execute().result || {}; | 23 | return this.plansRequest.execute().result || {}; |
29 | } | 24 | } |
30 | 25 | ||
31 | @computed get orders() { | ||
32 | return this.ordersDataRequest.execute().result || []; | ||
33 | } | ||
34 | |||
35 | @action _createHostedPage({ planId }) { | 26 | @action _createHostedPage({ planId }) { |
36 | const request = this.createHostedPageRequest.execute(planId); | 27 | const request = this.createHostedPageRequest.execute(planId); |
37 | 28 | ||
@@ -39,12 +30,4 @@ export default class PaymentStore extends Store { | |||
39 | 30 | ||
40 | return request; | 31 | return request; |
41 | } | 32 | } |
42 | |||
43 | @action _createDashboardUrl() { | ||
44 | const request = this.createDashboardUrlRequest.execute(); | ||
45 | |||
46 | gaEvent('Payment', 'createDashboardUrl'); | ||
47 | |||
48 | return request; | ||
49 | } | ||
50 | } | 33 | } |
diff --git a/src/stores/RecipePreviewsStore.js b/src/stores/RecipePreviewsStore.js index 10b2928e3..382820d58 100644 --- a/src/stores/RecipePreviewsStore.js +++ b/src/stores/RecipePreviewsStore.js | |||
@@ -1,5 +1,6 @@ | |||
1 | import { action, computed, observable } from 'mobx'; | 1 | import { action, computed, observable } from 'mobx'; |
2 | import { debounce } from 'lodash'; | 2 | import { debounce } from 'lodash'; |
3 | import ms from 'ms'; | ||
3 | 4 | ||
4 | import Store from './lib/Store'; | 5 | import Store from './lib/Store'; |
5 | import CachedRequest from './lib/CachedRequest'; | 6 | import CachedRequest from './lib/CachedRequest'; |
@@ -48,5 +49,5 @@ export default class RecipePreviewsStore extends Store { | |||
48 | // Helper | 49 | // Helper |
49 | _analyticsSearch = debounce((needle) => { | 50 | _analyticsSearch = debounce((needle) => { |
50 | gaEvent('Recipe', 'search', needle); | 51 | gaEvent('Recipe', 'search', needle); |
51 | }, 3000); | 52 | }, ms('3s')); |
52 | } | 53 | } |
diff --git a/src/stores/RequestStore.js b/src/stores/RequestStore.js index 2629e0a38..9254e3223 100644 --- a/src/stores/RequestStore.js +++ b/src/stores/RequestStore.js | |||
@@ -1,4 +1,5 @@ | |||
1 | import { action, computed, observable } from 'mobx'; | 1 | import { action, computed, observable } from 'mobx'; |
2 | import ms from 'ms'; | ||
2 | 3 | ||
3 | import Store from './lib/Store'; | 4 | import Store from './lib/Store'; |
4 | 5 | ||
@@ -13,7 +14,7 @@ export default class RequestStore extends Store { | |||
13 | 14 | ||
14 | retries = 0; | 15 | retries = 0; |
15 | 16 | ||
16 | retryDelay = 2000; | 17 | retryDelay = ms('2s'); |
17 | 18 | ||
18 | constructor(...args) { | 19 | constructor(...args) { |
19 | super(...args); | 20 | super(...args); |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index efd57a09d..13f929c2f 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -5,12 +5,14 @@ import { | |||
5 | observable, | 5 | observable, |
6 | } from 'mobx'; | 6 | } from 'mobx'; |
7 | import { debounce, remove } from 'lodash'; | 7 | import { debounce, remove } from 'lodash'; |
8 | import ms from 'ms'; | ||
8 | 9 | ||
9 | import Store from './lib/Store'; | 10 | import Store from './lib/Store'; |
10 | import Request from './lib/Request'; | 11 | import Request from './lib/Request'; |
11 | import CachedRequest from './lib/CachedRequest'; | 12 | import CachedRequest from './lib/CachedRequest'; |
12 | import { matchRoute } from '../helpers/routing-helpers'; | 13 | import { matchRoute } from '../helpers/routing-helpers'; |
13 | import { gaEvent } from '../lib/analytics'; | 14 | import { gaEvent, statsEvent } from '../lib/analytics'; |
15 | import { workspaceStore } from '../features/workspaces'; | ||
14 | 16 | ||
15 | const debug = require('debug')('Franz:ServiceStore'); | 17 | const debug = require('debug')('Franz:ServiceStore'); |
16 | 18 | ||
@@ -34,6 +36,7 @@ export default class ServicesStore extends Store { | |||
34 | 36 | ||
35 | // Register action handlers | 37 | // Register action handlers |
36 | this.actions.service.setActive.listen(this._setActive.bind(this)); | 38 | this.actions.service.setActive.listen(this._setActive.bind(this)); |
39 | this.actions.service.blurActive.listen(this._blurActive.bind(this)); | ||
37 | this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this)); | 40 | this.actions.service.setActiveNext.listen(this._setActiveNext.bind(this)); |
38 | this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this)); | 41 | this.actions.service.setActivePrev.listen(this._setActivePrev.bind(this)); |
39 | this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); | 42 | this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); |
@@ -43,6 +46,7 @@ export default class ServicesStore extends Store { | |||
43 | this.actions.service.deleteService.listen(this._deleteService.bind(this)); | 46 | this.actions.service.deleteService.listen(this._deleteService.bind(this)); |
44 | this.actions.service.clearCache.listen(this._clearCache.bind(this)); | 47 | this.actions.service.clearCache.listen(this._clearCache.bind(this)); |
45 | this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); | 48 | this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); |
49 | this.actions.service.detachService.listen(this._detachService.bind(this)); | ||
46 | this.actions.service.focusService.listen(this._focusService.bind(this)); | 50 | this.actions.service.focusService.listen(this._focusService.bind(this)); |
47 | this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); | 51 | this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); |
48 | this.actions.service.toggleService.listen(this._toggleService.bind(this)); | 52 | this.actions.service.toggleService.listen(this._toggleService.bind(this)); |
@@ -97,7 +101,6 @@ export default class ServicesStore extends Store { | |||
97 | return observable(services.slice().slice().sort((a, b) => a.order - b.order)); | 101 | return observable(services.slice().slice().sort((a, b) => a.order - b.order)); |
98 | } | 102 | } |
99 | } | 103 | } |
100 | |||
101 | return []; | 104 | return []; |
102 | } | 105 | } |
103 | 106 | ||
@@ -106,13 +109,16 @@ export default class ServicesStore extends Store { | |||
106 | } | 109 | } |
107 | 110 | ||
108 | @computed get allDisplayed() { | 111 | @computed get allDisplayed() { |
109 | return this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; | 112 | const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; |
113 | return workspaceStore.filterServicesByActiveWorkspace(services); | ||
110 | } | 114 | } |
111 | 115 | ||
112 | // This is just used to avoid unnecessary rerendering of resource-heavy webviews | 116 | // This is just used to avoid unnecessary rerendering of resource-heavy webviews |
113 | @computed get allDisplayedUnordered() { | 117 | @computed get allDisplayedUnordered() { |
118 | const { showDisabledServices } = this.stores.settings.all.app; | ||
114 | const services = this.allServicesRequest.execute().result || []; | 119 | const services = this.allServicesRequest.execute().result || []; |
115 | return this.stores.settings.all.app.showDisabledServices ? services : services.filter(service => service.isEnabled); | 120 | const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); |
121 | return workspaceStore.filterServicesByActiveWorkspace(filteredServices); | ||
116 | } | 122 | } |
117 | 123 | ||
118 | @computed get filtered() { | 124 | @computed get filtered() { |
@@ -293,9 +299,16 @@ export default class ServicesStore extends Store { | |||
293 | }); | 299 | }); |
294 | service.isActive = true; | 300 | service.isActive = true; |
295 | 301 | ||
302 | statsEvent('activate-service', service.recipe.id); | ||
303 | |||
296 | this._focusActiveService(); | 304 | this._focusActiveService(); |
297 | } | 305 | } |
298 | 306 | ||
307 | @action _blurActive() { | ||
308 | if (!this.active) return; | ||
309 | this.active.isActive = false; | ||
310 | } | ||
311 | |||
299 | @action _setActiveNext() { | 312 | @action _setActiveNext() { |
300 | const nextIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), 1, this.allDisplayed.length); | 313 | const nextIndex = this._wrapIndex(this.allDisplayed.findIndex(service => service.isActive), 1, this.allDisplayed.length); |
301 | 314 | ||
@@ -340,6 +353,11 @@ export default class ServicesStore extends Store { | |||
340 | service.isAttached = true; | 353 | service.isAttached = true; |
341 | } | 354 | } |
342 | 355 | ||
356 | @action _detachService({ service }) { | ||
357 | service.webview = null; | ||
358 | service.isAttached = false; | ||
359 | } | ||
360 | |||
343 | @action _focusService({ serviceId }) { | 361 | @action _focusService({ serviceId }) { |
344 | const service = this.one(serviceId); | 362 | const service = this.one(serviceId); |
345 | 363 | ||
@@ -679,7 +697,7 @@ export default class ServicesStore extends Store { | |||
679 | _initRecipePolling(serviceId) { | 697 | _initRecipePolling(serviceId) { |
680 | const service = this.one(serviceId); | 698 | const service = this.one(serviceId); |
681 | 699 | ||
682 | const delay = 2000; | 700 | const delay = ms('2s'); |
683 | 701 | ||
684 | if (service) { | 702 | if (service) { |
685 | if (service.timer !== null) { | 703 | if (service.timer !== null) { |
@@ -700,7 +718,7 @@ export default class ServicesStore extends Store { | |||
700 | 718 | ||
701 | _reorderAnalytics = debounce(() => { | 719 | _reorderAnalytics = debounce(() => { |
702 | gaEvent('Service', 'order'); | 720 | gaEvent('Service', 'order'); |
703 | }, 5000); | 721 | }, ms('5s')); |
704 | 722 | ||
705 | _wrapIndex(index, delta, size) { | 723 | _wrapIndex(index, delta, size) { |
706 | return (((index + delta) % size) + size) % size; | 724 | return (((index + delta) % size) + size) % size; |
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js index d37ebe4c7..a95a8e1e0 100644 --- a/src/stores/UIStore.js +++ b/src/stores/UIStore.js | |||
@@ -1,8 +1,7 @@ | |||
1 | import { action, observable, computed } from 'mobx'; | 1 | import { action, observable, computed } from 'mobx'; |
2 | import { theme } from '@meetfranz/theme'; | ||
2 | 3 | ||
3 | import Store from './lib/Store'; | 4 | import Store from './lib/Store'; |
4 | import * as themeDefault from '../theme/default'; | ||
5 | import * as themeDark from '../theme/dark'; | ||
6 | 5 | ||
7 | export default class UIStore extends Store { | 6 | export default class UIStore extends Store { |
8 | @observable showServicesUpdatedInfoBar = false; | 7 | @observable showServicesUpdatedInfoBar = false; |
@@ -22,12 +21,13 @@ export default class UIStore extends Store { | |||
22 | return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted; | 21 | return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted; |
23 | } | 22 | } |
24 | 23 | ||
25 | @computed get theme() { | 24 | @computed get isDarkThemeActive() { |
26 | if (this.stores.settings.all.app.darkMode) { | 25 | return this.stores.settings.all.app.darkMode; |
27 | return Object.assign({}, themeDefault, themeDark); | 26 | } |
28 | } | ||
29 | 27 | ||
30 | return themeDefault; | 28 | @computed get theme() { |
29 | if (this.isDarkThemeActive) return theme('dark'); | ||
30 | return theme('default'); | ||
31 | } | 31 | } |
32 | 32 | ||
33 | // Actions | 33 | // Actions |
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 7addb5760..31555dd5c 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -129,10 +129,6 @@ export default class UserStore extends Store { | |||
129 | return Boolean(localStorage.getItem('authToken')); | 129 | return Boolean(localStorage.getItem('authToken')); |
130 | } | 130 | } |
131 | 131 | ||
132 | // @computed get isTokenValid() { | ||
133 | // return this.authToken !== null && moment(this.tokenExpiry).isAfter(moment()); | ||
134 | // } | ||
135 | |||
136 | @computed get isTokenExpired() { | 132 | @computed get isTokenExpired() { |
137 | if (!this.authToken) return false; | 133 | if (!this.authToken) return false; |
138 | 134 | ||
@@ -146,6 +142,10 @@ export default class UserStore extends Store { | |||
146 | return this.getUserInfoRequest.execute().result || {}; | 142 | return this.getUserInfoRequest.execute().result || {}; |
147 | } | 143 | } |
148 | 144 | ||
145 | @computed get isPremium() { | ||
146 | return !!this.data.isPremium; | ||
147 | } | ||
148 | |||
149 | @computed get legacyServices() { | 149 | @computed get legacyServices() { |
150 | return this.getLegacyServicesRequest.execute() || {}; | 150 | return this.getLegacyServicesRequest.execute() || {}; |
151 | } | 151 | } |
@@ -160,6 +160,14 @@ export default class UserStore extends Store { | |||
160 | gaEvent('User', 'login'); | 160 | gaEvent('User', 'login'); |
161 | } | 161 | } |
162 | 162 | ||
163 | @action _tokenLogin(authToken) { | ||
164 | this._setUserData(authToken); | ||
165 | |||
166 | this.stores.router.push('/'); | ||
167 | |||
168 | gaEvent('User', 'tokenLogin'); | ||
169 | } | ||
170 | |||
163 | @action async _signup({ | 171 | @action async _signup({ |
164 | firstname, lastname, email, password, accountType, company, | 172 | firstname, lastname, email, password, accountType, company, |
165 | }) { | 173 | }) { |
@@ -170,6 +178,7 @@ export default class UserStore extends Store { | |||
170 | password, | 178 | password, |
171 | accountType, | 179 | accountType, |
172 | company, | 180 | company, |
181 | locale: this.stores.app.locale, | ||
173 | }); | 182 | }); |
174 | 183 | ||
175 | this.hasCompletedSignup = false; | 184 | this.hasCompletedSignup = false; |
@@ -206,6 +215,8 @@ export default class UserStore extends Store { | |||
206 | } | 215 | } |
207 | 216 | ||
208 | @action async _update({ userData }) { | 217 | @action async _update({ userData }) { |
218 | if (!this.isLoggedIn) return; | ||
219 | |||
209 | const response = await this.updateUserInfoRequest.execute(userData)._promise; | 220 | const response = await this.updateUserInfoRequest.execute(userData)._promise; |
210 | 221 | ||
211 | this.getUserInfoRequest.patch(() => response.data); | 222 | this.getUserInfoRequest.patch(() => response.data); |
@@ -222,6 +233,7 @@ export default class UserStore extends Store { | |||
222 | // workaround mobx issue | 233 | // workaround mobx issue |
223 | localStorage.removeItem('authToken'); | 234 | localStorage.removeItem('authToken'); |
224 | window.localStorage.removeItem('authToken'); | 235 | window.localStorage.removeItem('authToken'); |
236 | |||
225 | this.getUserInfoRequest.invalidate().reset(); | 237 | this.getUserInfoRequest.invalidate().reset(); |
226 | this.authToken = null; | 238 | this.authToken = null; |
227 | } | 239 | } |
@@ -262,6 +274,18 @@ export default class UserStore extends Store { | |||
262 | const { router } = this.stores; | 274 | const { router } = this.stores; |
263 | const currentRoute = router.location.pathname; | 275 | const currentRoute = router.location.pathname; |
264 | if (!this.isLoggedIn | 276 | if (!this.isLoggedIn |
277 | && currentRoute.includes('token=')) { | ||
278 | router.push(this.WELCOME_ROUTE); | ||
279 | const token = currentRoute.split('=')[1]; | ||
280 | |||
281 | const data = this._parseToken(token); | ||
282 | if (data) { | ||
283 | // Give this some time to sink | ||
284 | setTimeout(() => { | ||
285 | this._tokenLogin(token); | ||
286 | }, 1000); | ||
287 | } | ||
288 | } else if (!this.isLoggedIn | ||
265 | && !currentRoute.includes(this.BASE_ROUTE)) { | 289 | && !currentRoute.includes(this.BASE_ROUTE)) { |
266 | router.push(this.WELCOME_ROUTE); | 290 | router.push(this.WELCOME_ROUTE); |
267 | } else if (this.isLoggedIn | 291 | } else if (this.isLoggedIn |
diff --git a/src/stores/index.js b/src/stores/index.js index 96b844c95..1912418a2 100644 --- a/src/stores/index.js +++ b/src/stores/index.js | |||
@@ -10,6 +10,8 @@ import PaymentStore from './PaymentStore'; | |||
10 | import NewsStore from './NewsStore'; | 10 | import NewsStore from './NewsStore'; |
11 | import RequestStore from './RequestStore'; | 11 | import RequestStore from './RequestStore'; |
12 | import GlobalErrorStore from './GlobalErrorStore'; | 12 | import GlobalErrorStore from './GlobalErrorStore'; |
13 | import { workspaceStore } from '../features/workspaces'; | ||
14 | import { announcementsStore } from '../features/announcements'; | ||
13 | 15 | ||
14 | export default (api, actions, router) => { | 16 | export default (api, actions, router) => { |
15 | const stores = {}; | 17 | const stores = {}; |
@@ -27,6 +29,8 @@ export default (api, actions, router) => { | |||
27 | news: new NewsStore(stores, api, actions), | 29 | news: new NewsStore(stores, api, actions), |
28 | requests: new RequestStore(stores, api, actions), | 30 | requests: new RequestStore(stores, api, actions), |
29 | globalError: new GlobalErrorStore(stores, api, actions), | 31 | globalError: new GlobalErrorStore(stores, api, actions), |
32 | workspaces: workspaceStore, | ||
33 | announcements: announcementsStore, | ||
30 | }); | 34 | }); |
31 | // Initialize all stores | 35 | // Initialize all stores |
32 | Object.keys(stores).forEach((name) => { | 36 | Object.keys(stores).forEach((name) => { |
diff --git a/src/stores/lib/Reaction.js b/src/stores/lib/Reaction.js index 46aa4dae6..f2642908f 100644 --- a/src/stores/lib/Reaction.js +++ b/src/stores/lib/Reaction.js | |||
@@ -1,24 +1,31 @@ | |||
1 | // @flow | ||
2 | import { autorun } from 'mobx'; | 1 | import { autorun } from 'mobx'; |
3 | 2 | ||
4 | export default class Reaction { | 3 | export default class Reaction { |
5 | reaction; | 4 | reaction; |
6 | 5 | ||
7 | hasBeenStarted; | 6 | isRunning = false; |
8 | 7 | ||
9 | dispose; | 8 | dispose; |
10 | 9 | ||
11 | constructor(reaction) { | 10 | constructor(reaction) { |
12 | this.reaction = reaction; | 11 | this.reaction = reaction; |
13 | this.hasBeenStarted = false; | ||
14 | } | 12 | } |
15 | 13 | ||
16 | start() { | 14 | start() { |
17 | this.dispose = autorun(() => this.reaction()); | 15 | if (!this.isRunning) { |
18 | this.hasBeenStarted = true; | 16 | this.dispose = autorun(() => this.reaction()); |
17 | this.isActive = true; | ||
18 | } | ||
19 | } | 19 | } |
20 | 20 | ||
21 | stop() { | 21 | stop() { |
22 | if (this.hasBeenStarted) this.dispose(); | 22 | if (this.isRunning) { |
23 | this.dispose(); | ||
24 | this.isActive = false; | ||
25 | } | ||
23 | } | 26 | } |
24 | } | 27 | } |
28 | |||
29 | export const createReactions = reactions => ( | ||
30 | reactions.map(r => new Reaction(r)) | ||
31 | ); | ||
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js index 04f528156..486de8a49 100644 --- a/src/stores/lib/Request.js +++ b/src/stores/lib/Request.js | |||
@@ -85,6 +85,8 @@ export default class Request { | |||
85 | return this.execute(...this._currentApiCall.args); | 85 | return this.execute(...this._currentApiCall.args); |
86 | } | 86 | } |
87 | 87 | ||
88 | retry = () => this.reload(); | ||
89 | |||
88 | isExecutingWithArgs(...args) { | 90 | isExecutingWithArgs(...args) { |
89 | return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args); | 91 | return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args); |
90 | } | 92 | } |
@@ -107,7 +109,7 @@ export default class Request { | |||
107 | Request._hooks.forEach(hook => hook(this)); | 109 | Request._hooks.forEach(hook => hook(this)); |
108 | } | 110 | } |
109 | 111 | ||
110 | reset() { | 112 | reset = () => { |
111 | this.result = null; | 113 | this.result = null; |
112 | this.isExecuting = false; | 114 | this.isExecuting = false; |
113 | this.isError = false; | 115 | this.isError = false; |
@@ -116,5 +118,5 @@ export default class Request { | |||
116 | this._promise = Promise; | 118 | this._promise = Promise; |
117 | 119 | ||
118 | return this; | 120 | return this; |
119 | } | 121 | }; |
120 | } | 122 | } |