diff options
author | Stefan Malzner <stefan@adlk.io> | 2017-10-13 12:29:40 +0200 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2017-10-13 12:29:40 +0200 |
commit | 58cda9cc7fb79ca9df6746de7f9662bc08dc156a (patch) | |
tree | 1211600c2a5d3b5f81c435c6896618111a611720 /src/stores/ServicesStore.js | |
download | ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.gz ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.tar.zst ferdium-app-58cda9cc7fb79ca9df6746de7f9662bc08dc156a.zip |
initial commit
Diffstat (limited to 'src/stores/ServicesStore.js')
-rw-r--r-- | src/stores/ServicesStore.js | 503 |
1 files changed, 503 insertions, 0 deletions
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js new file mode 100644 index 000000000..77d2e7da4 --- /dev/null +++ b/src/stores/ServicesStore.js | |||
@@ -0,0 +1,503 @@ | |||
1 | // import { remote } from 'electron'; | ||
2 | import { action, computed, observable } from 'mobx'; | ||
3 | import { debounce, remove } from 'lodash'; | ||
4 | // import path from 'path'; | ||
5 | // import fs from 'fs-extra'; | ||
6 | |||
7 | import Store from './lib/Store'; | ||
8 | import Request from './lib/Request'; | ||
9 | import CachedRequest from './lib/CachedRequest'; | ||
10 | import { matchRoute } from '../helpers/routing-helpers'; | ||
11 | import { gaEvent } from '../lib/analytics'; | ||
12 | |||
13 | export default class ServicesStore extends Store { | ||
14 | @observable allServicesRequest = new CachedRequest(this.api.services, 'all'); | ||
15 | @observable createServiceRequest = new Request(this.api.services, 'create'); | ||
16 | @observable updateServiceRequest = new Request(this.api.services, 'update'); | ||
17 | @observable reorderServicesRequest = new Request(this.api.services, 'reorder'); | ||
18 | @observable deleteServiceRequest = new Request(this.api.services, 'delete'); | ||
19 | |||
20 | @observable filterNeedle = null; | ||
21 | |||
22 | constructor(...args) { | ||
23 | super(...args); | ||
24 | |||
25 | // Register action handlers | ||
26 | this.actions.service.setActive.listen(this._setActive.bind(this)); | ||
27 | this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); | ||
28 | this.actions.service.createService.listen(this._createService.bind(this)); | ||
29 | this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this)); | ||
30 | this.actions.service.updateService.listen(this._updateService.bind(this)); | ||
31 | this.actions.service.deleteService.listen(this._deleteService.bind(this)); | ||
32 | this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); | ||
33 | this.actions.service.focusService.listen(this._focusService.bind(this)); | ||
34 | this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); | ||
35 | this.actions.service.toggleService.listen(this._toggleService.bind(this)); | ||
36 | this.actions.service.handleIPCMessage.listen(this._handleIPCMessage.bind(this)); | ||
37 | this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this)); | ||
38 | this.actions.service.setUnreadMessageCount.listen(this._setUnreadMessageCount.bind(this)); | ||
39 | this.actions.service.openWindow.listen(this._openWindow.bind(this)); | ||
40 | this.actions.service.filter.listen(this._filter.bind(this)); | ||
41 | this.actions.service.resetFilter.listen(this._resetFilter.bind(this)); | ||
42 | this.actions.service.reload.listen(this._reload.bind(this)); | ||
43 | this.actions.service.reloadActive.listen(this._reloadActive.bind(this)); | ||
44 | this.actions.service.reloadAll.listen(this._reloadAll.bind(this)); | ||
45 | this.actions.service.reloadUpdatedServices.listen(this._reloadUpdatedServices.bind(this)); | ||
46 | this.actions.service.reorder.listen(this._reorder.bind(this)); | ||
47 | this.actions.service.toggleNotifications.listen(this._toggleNotifications.bind(this)); | ||
48 | this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); | ||
49 | this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); | ||
50 | |||
51 | this.registerReactions([ | ||
52 | this._focusServiceReaction.bind(this), | ||
53 | this._getUnreadMessageCountReaction.bind(this), | ||
54 | this._mapActiveServiceToServiceModelReaction.bind(this), | ||
55 | this._saveActiveService.bind(this), | ||
56 | this._logoutReaction.bind(this), | ||
57 | ]); | ||
58 | |||
59 | // Just bind this | ||
60 | this._initializeServiceRecipeInWebview.bind(this); | ||
61 | } | ||
62 | |||
63 | @computed get all() { | ||
64 | if (this.stores.user.isLoggedIn) { | ||
65 | const services = this.allServicesRequest.execute().result; | ||
66 | if (services) { | ||
67 | return observable(services.slice().slice().sort((a, b) => a.order - b.order)); | ||
68 | } | ||
69 | } | ||
70 | |||
71 | return []; | ||
72 | } | ||
73 | |||
74 | @computed get enabled() { | ||
75 | return this.all.filter(service => service.isEnabled); | ||
76 | } | ||
77 | |||
78 | @computed get filtered() { | ||
79 | return this.all.filter(service => service.name.toLowerCase().includes(this.filterNeedle.toLowerCase())); | ||
80 | } | ||
81 | |||
82 | @computed get active() { | ||
83 | return this.all.find(service => service.isActive); | ||
84 | } | ||
85 | |||
86 | @computed get activeSettings() { | ||
87 | const match = matchRoute('/settings/services/edit/:id', this.stores.router.location.pathname); | ||
88 | if (match) { | ||
89 | const activeService = this.one(match.id); | ||
90 | if (activeService) { | ||
91 | return activeService; | ||
92 | } | ||
93 | |||
94 | console.warn('Service not available'); | ||
95 | } | ||
96 | |||
97 | return null; | ||
98 | } | ||
99 | |||
100 | one(id) { | ||
101 | return this.all.find(service => service.id === id); | ||
102 | } | ||
103 | |||
104 | async _showAddServiceInterface({ recipeId }) { | ||
105 | const recipesStore = this.stores.recipes; | ||
106 | |||
107 | if (recipesStore.isInstalled(recipeId)) { | ||
108 | console.debug('Recipe is installed'); | ||
109 | this._redirectToAddServiceRoute(recipeId); | ||
110 | } else { | ||
111 | console.warn('Recipe is not installed'); | ||
112 | // We access the RecipeStore action directly | ||
113 | // returns Promise instead of action | ||
114 | await this.stores.recipes._install({ recipeId }); | ||
115 | this._redirectToAddServiceRoute(recipeId); | ||
116 | } | ||
117 | } | ||
118 | |||
119 | // Actions | ||
120 | @action async _createService({ recipeId, serviceData, redirect = true }) { | ||
121 | const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); | ||
122 | const response = await this.createServiceRequest.execute(recipeId, data)._promise; | ||
123 | |||
124 | this.allServicesRequest.patch((result) => { | ||
125 | if (!result) return; | ||
126 | result.push(response.data); | ||
127 | }); | ||
128 | |||
129 | this.actionStatus = response.status || []; | ||
130 | |||
131 | if (redirect) { | ||
132 | this.stores.router.push('/settings/recipes'); | ||
133 | gaEvent('Service', 'create', recipeId); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | @action async _createFromLegacyService({ data }) { | ||
138 | const { id } = data.recipe; | ||
139 | const serviceData = {}; | ||
140 | |||
141 | if (data.name) { | ||
142 | serviceData.name = data.name; | ||
143 | } | ||
144 | |||
145 | if (data.team) { | ||
146 | serviceData.team = data.team; | ||
147 | } | ||
148 | |||
149 | if (data.team) { | ||
150 | serviceData.customUrl = data.customURL; | ||
151 | } | ||
152 | |||
153 | this.actions.service.createService({ | ||
154 | recipeId: id, | ||
155 | serviceData, | ||
156 | redirect: false, | ||
157 | }); | ||
158 | |||
159 | return 'hello world'; | ||
160 | } | ||
161 | |||
162 | @action async _updateService({ serviceId, serviceData, redirect = true }) { | ||
163 | const service = this.one(serviceId); | ||
164 | const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); | ||
165 | const request = this.updateServiceRequest.execute(serviceId, data); | ||
166 | |||
167 | this.allServicesRequest.patch((result) => { | ||
168 | if (!result) return; | ||
169 | Object.assign(result.find(c => c.id === serviceId), serviceData); | ||
170 | }); | ||
171 | |||
172 | await request._promise; | ||
173 | this.actionStatus = request.result.status; | ||
174 | |||
175 | if (redirect) { | ||
176 | this.stores.router.push('/settings/services'); | ||
177 | gaEvent('Service', 'update', service.recipe.id); | ||
178 | } | ||
179 | } | ||
180 | |||
181 | @action async _deleteService({ serviceId, redirect }) { | ||
182 | const request = this.deleteServiceRequest.execute(serviceId); | ||
183 | |||
184 | if (redirect) { | ||
185 | this.stores.router.push(redirect); | ||
186 | } | ||
187 | |||
188 | this.allServicesRequest.patch((result) => { | ||
189 | remove(result, c => c.id === serviceId); | ||
190 | }); | ||
191 | |||
192 | const service = this.one(serviceId); | ||
193 | |||
194 | await request._promise; | ||
195 | this.actionStatus = request.result.status; | ||
196 | |||
197 | gaEvent('Service', 'delete', service.recipe.id); | ||
198 | } | ||
199 | |||
200 | @action _setActive({ serviceId }) { | ||
201 | const service = this.one(serviceId); | ||
202 | |||
203 | this.all.forEach((s, index) => { | ||
204 | this.all[index].isActive = false; | ||
205 | }); | ||
206 | service.isActive = true; | ||
207 | } | ||
208 | |||
209 | @action _setUnreadMessageCount({ serviceId, count }) { | ||
210 | const service = this.one(serviceId); | ||
211 | |||
212 | service.unreadDirectMessageCount = count.direct; | ||
213 | service.unreadIndirectMessageCount = count.indirect; | ||
214 | } | ||
215 | |||
216 | @action _setWebviewReference({ serviceId, webview }) { | ||
217 | const service = this.one(serviceId); | ||
218 | |||
219 | service.webview = webview; | ||
220 | |||
221 | if (!service.isAttached) { | ||
222 | service.initializeWebViewEvents(this); | ||
223 | service.initializeWebViewListener(); | ||
224 | } | ||
225 | |||
226 | service.isAttached = true; | ||
227 | } | ||
228 | |||
229 | @action _focusService({ serviceId }) { | ||
230 | const service = this.one(serviceId); | ||
231 | |||
232 | if (service.webview) { | ||
233 | service.webview.focus(); | ||
234 | } | ||
235 | } | ||
236 | |||
237 | @action _focusActiveService() { | ||
238 | if (this.stores.user.isLoggedIn) { | ||
239 | // TODO: add checks to not focus service when router path is /settings or /auth | ||
240 | const service = this.active; | ||
241 | if (service) { | ||
242 | this._focusService({ serviceId: service.id }); | ||
243 | } | ||
244 | } else { | ||
245 | this.allServicesRequest.invalidate(); | ||
246 | } | ||
247 | } | ||
248 | |||
249 | @action _toggleService({ serviceId }) { | ||
250 | const service = this.one(serviceId); | ||
251 | |||
252 | service.isEnabled = !service.isEnabled; | ||
253 | } | ||
254 | |||
255 | @action _handleIPCMessage({ serviceId, channel, args }) { | ||
256 | const service = this.one(serviceId); | ||
257 | |||
258 | if (channel === 'hello') { | ||
259 | this._initRecipePolling(service.id); | ||
260 | this._initializeServiceRecipeInWebview(serviceId); | ||
261 | } else if (channel === 'messages') { | ||
262 | this.actions.service.setUnreadMessageCount({ | ||
263 | serviceId, | ||
264 | count: { | ||
265 | direct: args[0].direct, | ||
266 | indirect: args[0].indirect, | ||
267 | }, | ||
268 | }); | ||
269 | } else if (channel === 'notification') { | ||
270 | const options = args[0].options; | ||
271 | if (service.recipe.hasNotificationSound) { | ||
272 | Object.assign(options, { | ||
273 | silent: true, | ||
274 | }); | ||
275 | } | ||
276 | |||
277 | if (service.isNotificationEnabled) { | ||
278 | this.actions.app.notify({ | ||
279 | notificationId: args[0].notificationId, | ||
280 | title: args[0].title, | ||
281 | options, | ||
282 | serviceId, | ||
283 | }); | ||
284 | } | ||
285 | } else if (channel === 'avatar') { | ||
286 | const url = args[0]; | ||
287 | if (service.customIconUrl !== url) { | ||
288 | service.customIconUrl = url; | ||
289 | |||
290 | this.actions.service.updateService({ | ||
291 | serviceId, | ||
292 | serviceData: { | ||
293 | customIconUrl: url, | ||
294 | }, | ||
295 | redirect: false, | ||
296 | }); | ||
297 | } | ||
298 | } | ||
299 | } | ||
300 | |||
301 | @action _sendIPCMessage({ serviceId, channel, args }) { | ||
302 | const service = this.one(serviceId); | ||
303 | |||
304 | service.webview.send(channel, args); | ||
305 | } | ||
306 | |||
307 | @action _openWindow({ event }) { | ||
308 | if (event.disposition !== 'new-window' && event.url !== 'about:blank') { | ||
309 | this.actions.app.openExternalUrl({ url: event.url }); | ||
310 | } | ||
311 | } | ||
312 | |||
313 | @action _filter({ needle }) { | ||
314 | this.filterNeedle = needle; | ||
315 | } | ||
316 | |||
317 | @action _resetFilter() { | ||
318 | this.filterNeedle = null; | ||
319 | } | ||
320 | |||
321 | @action _reload({ serviceId }) { | ||
322 | const service = this.one(serviceId); | ||
323 | service.resetMessageCount(); | ||
324 | |||
325 | service.webview.reload(); | ||
326 | } | ||
327 | |||
328 | @action _reloadActive() { | ||
329 | if (this.active) { | ||
330 | const service = this.one(this.active.id); | ||
331 | |||
332 | this._reload({ | ||
333 | serviceId: service.id, | ||
334 | }); | ||
335 | } | ||
336 | } | ||
337 | |||
338 | @action _reloadAll() { | ||
339 | this.enabled.forEach(s => this._reload({ | ||
340 | serviceId: s.id, | ||
341 | })); | ||
342 | } | ||
343 | |||
344 | @action _reloadUpdatedServices() { | ||
345 | this._reloadAll(); | ||
346 | this.actions.ui.toggleServiceUpdatedInfoBar({ visible: false }); | ||
347 | } | ||
348 | |||
349 | @action _reorder({ oldIndex, newIndex }) { | ||
350 | const oldEnabledSortIndex = this.all.indexOf(this.enabled[oldIndex]); | ||
351 | const newEnabledSortIndex = this.all.indexOf(this.enabled[newIndex]); | ||
352 | |||
353 | |||
354 | this.all.splice(newEnabledSortIndex, 0, this.all.splice(oldEnabledSortIndex, 1)[0]); | ||
355 | |||
356 | const services = {}; | ||
357 | this.all.forEach((s, index) => { | ||
358 | services[this.all[index].id] = index; | ||
359 | }); | ||
360 | |||
361 | this.reorderServicesRequest.execute(services); | ||
362 | this.allServicesRequest.patch((data) => { | ||
363 | data.forEach((s) => { | ||
364 | const service = s; | ||
365 | |||
366 | service.order = this.one(s.id).order; | ||
367 | }); | ||
368 | }); | ||
369 | |||
370 | this._reorderAnalytics(); | ||
371 | } | ||
372 | |||
373 | @action _toggleNotifications({ serviceId }) { | ||
374 | const service = this.one(serviceId); | ||
375 | |||
376 | service.isNotificationEnabled = !service.isNotificationEnabled; | ||
377 | |||
378 | this.actions.service.updateService({ | ||
379 | serviceId, | ||
380 | serviceData: service, | ||
381 | redirect: false, | ||
382 | }); | ||
383 | } | ||
384 | |||
385 | @action _openDevTools({ serviceId }) { | ||
386 | const service = this.one(serviceId); | ||
387 | |||
388 | service.webview.openDevTools(); | ||
389 | } | ||
390 | |||
391 | @action _openDevToolsForActiveService() { | ||
392 | const service = this.active; | ||
393 | |||
394 | if (service) { | ||
395 | service.webview.openDevTools(); | ||
396 | } else { | ||
397 | console.warn('No service is active'); | ||
398 | } | ||
399 | } | ||
400 | |||
401 | // Reactions | ||
402 | _focusServiceReaction() { | ||
403 | const service = this.active; | ||
404 | if (service) { | ||
405 | this.actions.service.focusService({ serviceId: service.id }); | ||
406 | } | ||
407 | } | ||
408 | |||
409 | _saveActiveService() { | ||
410 | const service = this.active; | ||
411 | |||
412 | if (service) { | ||
413 | this.stores.settings.updateSettingsRequest.execute({ | ||
414 | activeService: service.id, | ||
415 | }); | ||
416 | } | ||
417 | } | ||
418 | |||
419 | _mapActiveServiceToServiceModelReaction() { | ||
420 | const { activeService } = this.stores.settings.all; | ||
421 | const services = this.enabled; | ||
422 | if (services.length) { | ||
423 | services.map(service => Object.assign(service, { | ||
424 | isActive: activeService ? activeService === service.id : services[0].id === service.id, | ||
425 | })); | ||
426 | |||
427 | // if (!services.active) { | ||
428 | // | ||
429 | // } | ||
430 | } | ||
431 | // else if (!activeService && services.length) { | ||
432 | // services[0].isActive = true; | ||
433 | // } | ||
434 | } | ||
435 | |||
436 | _getUnreadMessageCountReaction() { | ||
437 | const unreadDirectMessageCount = this.enabled | ||
438 | .map(s => s.unreadDirectMessageCount) | ||
439 | .reduce((a, b) => a + b, 0); | ||
440 | |||
441 | const unreadIndirectMessageCount = this.enabled | ||
442 | .filter(s => s.isIndirectMessageBadgeEnabled) | ||
443 | .map(s => s.unreadIndirectMessageCount) | ||
444 | .reduce((a, b) => a + b, 0); | ||
445 | |||
446 | this.actions.app.setBadge({ | ||
447 | unreadDirectMessageCount, | ||
448 | unreadIndirectMessageCount, | ||
449 | }); | ||
450 | } | ||
451 | |||
452 | _logoutReaction() { | ||
453 | if (!this.stores.user.isLoggedIn) { | ||
454 | this.actions.settings.remove({ key: 'activeService' }); | ||
455 | this.allServicesRequest.invalidate().reset(); | ||
456 | } | ||
457 | } | ||
458 | |||
459 | _cleanUpTeamIdAndCustomUrl(recipeId, data) { | ||
460 | const serviceData = data; | ||
461 | const recipe = this.stores.recipes.one(recipeId); | ||
462 | |||
463 | if (recipe.hasTeamId && recipe.hasCustomUrl && data.team && data.customUrl) { | ||
464 | delete serviceData.team; | ||
465 | } | ||
466 | |||
467 | return serviceData; | ||
468 | } | ||
469 | |||
470 | // Helper | ||
471 | _redirectToAddServiceRoute(recipeId) { | ||
472 | const route = `/settings/services/add/${recipeId}`; | ||
473 | this.stores.router.push(route); | ||
474 | } | ||
475 | |||
476 | _initializeServiceRecipeInWebview(serviceId) { | ||
477 | const service = this.one(serviceId); | ||
478 | |||
479 | if (service.webview) { | ||
480 | service.webview.send('initializeRecipe', service); | ||
481 | } | ||
482 | } | ||
483 | |||
484 | _initRecipePolling(serviceId) { | ||
485 | const service = this.one(serviceId); | ||
486 | |||
487 | const delay = 1000; | ||
488 | |||
489 | if (service) { | ||
490 | const loop = () => { | ||
491 | service.webview.send('poll'); | ||
492 | |||
493 | setTimeout(loop, delay); | ||
494 | }; | ||
495 | |||
496 | loop(); | ||
497 | } | ||
498 | } | ||
499 | |||
500 | _reorderAnalytics = debounce(() => { | ||
501 | gaEvent('Service', 'order'); | ||
502 | }, 5000); | ||
503 | } | ||