From 58cda9cc7fb79ca9df6746de7f9662bc08dc156a Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Fri, 13 Oct 2017 12:29:40 +0200 Subject: initial commit --- src/stores/ServicesStore.js | 503 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100644 src/stores/ServicesStore.js (limited to 'src/stores/ServicesStore.js') 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 @@ +// import { remote } from 'electron'; +import { action, computed, observable } from 'mobx'; +import { debounce, remove } from 'lodash'; +// import path from 'path'; +// import fs from 'fs-extra'; + +import Store from './lib/Store'; +import Request from './lib/Request'; +import CachedRequest from './lib/CachedRequest'; +import { matchRoute } from '../helpers/routing-helpers'; +import { gaEvent } from '../lib/analytics'; + +export default class ServicesStore extends Store { + @observable allServicesRequest = new CachedRequest(this.api.services, 'all'); + @observable createServiceRequest = new Request(this.api.services, 'create'); + @observable updateServiceRequest = new Request(this.api.services, 'update'); + @observable reorderServicesRequest = new Request(this.api.services, 'reorder'); + @observable deleteServiceRequest = new Request(this.api.services, 'delete'); + + @observable filterNeedle = null; + + constructor(...args) { + super(...args); + + // Register action handlers + this.actions.service.setActive.listen(this._setActive.bind(this)); + this.actions.service.showAddServiceInterface.listen(this._showAddServiceInterface.bind(this)); + this.actions.service.createService.listen(this._createService.bind(this)); + this.actions.service.createFromLegacyService.listen(this._createFromLegacyService.bind(this)); + this.actions.service.updateService.listen(this._updateService.bind(this)); + this.actions.service.deleteService.listen(this._deleteService.bind(this)); + this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); + this.actions.service.focusService.listen(this._focusService.bind(this)); + this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); + this.actions.service.toggleService.listen(this._toggleService.bind(this)); + this.actions.service.handleIPCMessage.listen(this._handleIPCMessage.bind(this)); + this.actions.service.sendIPCMessage.listen(this._sendIPCMessage.bind(this)); + this.actions.service.setUnreadMessageCount.listen(this._setUnreadMessageCount.bind(this)); + this.actions.service.openWindow.listen(this._openWindow.bind(this)); + this.actions.service.filter.listen(this._filter.bind(this)); + this.actions.service.resetFilter.listen(this._resetFilter.bind(this)); + this.actions.service.reload.listen(this._reload.bind(this)); + this.actions.service.reloadActive.listen(this._reloadActive.bind(this)); + this.actions.service.reloadAll.listen(this._reloadAll.bind(this)); + this.actions.service.reloadUpdatedServices.listen(this._reloadUpdatedServices.bind(this)); + this.actions.service.reorder.listen(this._reorder.bind(this)); + this.actions.service.toggleNotifications.listen(this._toggleNotifications.bind(this)); + this.actions.service.openDevTools.listen(this._openDevTools.bind(this)); + this.actions.service.openDevToolsForActiveService.listen(this._openDevToolsForActiveService.bind(this)); + + this.registerReactions([ + this._focusServiceReaction.bind(this), + this._getUnreadMessageCountReaction.bind(this), + this._mapActiveServiceToServiceModelReaction.bind(this), + this._saveActiveService.bind(this), + this._logoutReaction.bind(this), + ]); + + // Just bind this + this._initializeServiceRecipeInWebview.bind(this); + } + + @computed get all() { + if (this.stores.user.isLoggedIn) { + const services = this.allServicesRequest.execute().result; + if (services) { + return observable(services.slice().slice().sort((a, b) => a.order - b.order)); + } + } + + return []; + } + + @computed get enabled() { + return this.all.filter(service => service.isEnabled); + } + + @computed get filtered() { + return this.all.filter(service => service.name.toLowerCase().includes(this.filterNeedle.toLowerCase())); + } + + @computed get active() { + return this.all.find(service => service.isActive); + } + + @computed get activeSettings() { + const match = matchRoute('/settings/services/edit/:id', this.stores.router.location.pathname); + if (match) { + const activeService = this.one(match.id); + if (activeService) { + return activeService; + } + + console.warn('Service not available'); + } + + return null; + } + + one(id) { + return this.all.find(service => service.id === id); + } + + async _showAddServiceInterface({ recipeId }) { + const recipesStore = this.stores.recipes; + + if (recipesStore.isInstalled(recipeId)) { + console.debug('Recipe is installed'); + this._redirectToAddServiceRoute(recipeId); + } else { + console.warn('Recipe is not installed'); + // We access the RecipeStore action directly + // returns Promise instead of action + await this.stores.recipes._install({ recipeId }); + this._redirectToAddServiceRoute(recipeId); + } + } + + // Actions + @action async _createService({ recipeId, serviceData, redirect = true }) { + const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); + const response = await this.createServiceRequest.execute(recipeId, data)._promise; + + this.allServicesRequest.patch((result) => { + if (!result) return; + result.push(response.data); + }); + + this.actionStatus = response.status || []; + + if (redirect) { + this.stores.router.push('/settings/recipes'); + gaEvent('Service', 'create', recipeId); + } + } + + @action async _createFromLegacyService({ data }) { + const { id } = data.recipe; + const serviceData = {}; + + if (data.name) { + serviceData.name = data.name; + } + + if (data.team) { + serviceData.team = data.team; + } + + if (data.team) { + serviceData.customUrl = data.customURL; + } + + this.actions.service.createService({ + recipeId: id, + serviceData, + redirect: false, + }); + + return 'hello world'; + } + + @action async _updateService({ serviceId, serviceData, redirect = true }) { + const service = this.one(serviceId); + const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); + const request = this.updateServiceRequest.execute(serviceId, data); + + this.allServicesRequest.patch((result) => { + if (!result) return; + Object.assign(result.find(c => c.id === serviceId), serviceData); + }); + + await request._promise; + this.actionStatus = request.result.status; + + if (redirect) { + this.stores.router.push('/settings/services'); + gaEvent('Service', 'update', service.recipe.id); + } + } + + @action async _deleteService({ serviceId, redirect }) { + const request = this.deleteServiceRequest.execute(serviceId); + + if (redirect) { + this.stores.router.push(redirect); + } + + this.allServicesRequest.patch((result) => { + remove(result, c => c.id === serviceId); + }); + + const service = this.one(serviceId); + + await request._promise; + this.actionStatus = request.result.status; + + gaEvent('Service', 'delete', service.recipe.id); + } + + @action _setActive({ serviceId }) { + const service = this.one(serviceId); + + this.all.forEach((s, index) => { + this.all[index].isActive = false; + }); + service.isActive = true; + } + + @action _setUnreadMessageCount({ serviceId, count }) { + const service = this.one(serviceId); + + service.unreadDirectMessageCount = count.direct; + service.unreadIndirectMessageCount = count.indirect; + } + + @action _setWebviewReference({ serviceId, webview }) { + const service = this.one(serviceId); + + service.webview = webview; + + if (!service.isAttached) { + service.initializeWebViewEvents(this); + service.initializeWebViewListener(); + } + + service.isAttached = true; + } + + @action _focusService({ serviceId }) { + const service = this.one(serviceId); + + if (service.webview) { + service.webview.focus(); + } + } + + @action _focusActiveService() { + if (this.stores.user.isLoggedIn) { + // TODO: add checks to not focus service when router path is /settings or /auth + const service = this.active; + if (service) { + this._focusService({ serviceId: service.id }); + } + } else { + this.allServicesRequest.invalidate(); + } + } + + @action _toggleService({ serviceId }) { + const service = this.one(serviceId); + + service.isEnabled = !service.isEnabled; + } + + @action _handleIPCMessage({ serviceId, channel, args }) { + const service = this.one(serviceId); + + if (channel === 'hello') { + this._initRecipePolling(service.id); + this._initializeServiceRecipeInWebview(serviceId); + } else if (channel === 'messages') { + this.actions.service.setUnreadMessageCount({ + serviceId, + count: { + direct: args[0].direct, + indirect: args[0].indirect, + }, + }); + } else if (channel === 'notification') { + const options = args[0].options; + if (service.recipe.hasNotificationSound) { + Object.assign(options, { + silent: true, + }); + } + + if (service.isNotificationEnabled) { + this.actions.app.notify({ + notificationId: args[0].notificationId, + title: args[0].title, + options, + serviceId, + }); + } + } else if (channel === 'avatar') { + const url = args[0]; + if (service.customIconUrl !== url) { + service.customIconUrl = url; + + this.actions.service.updateService({ + serviceId, + serviceData: { + customIconUrl: url, + }, + redirect: false, + }); + } + } + } + + @action _sendIPCMessage({ serviceId, channel, args }) { + const service = this.one(serviceId); + + service.webview.send(channel, args); + } + + @action _openWindow({ event }) { + if (event.disposition !== 'new-window' && event.url !== 'about:blank') { + this.actions.app.openExternalUrl({ url: event.url }); + } + } + + @action _filter({ needle }) { + this.filterNeedle = needle; + } + + @action _resetFilter() { + this.filterNeedle = null; + } + + @action _reload({ serviceId }) { + const service = this.one(serviceId); + service.resetMessageCount(); + + service.webview.reload(); + } + + @action _reloadActive() { + if (this.active) { + const service = this.one(this.active.id); + + this._reload({ + serviceId: service.id, + }); + } + } + + @action _reloadAll() { + this.enabled.forEach(s => this._reload({ + serviceId: s.id, + })); + } + + @action _reloadUpdatedServices() { + this._reloadAll(); + this.actions.ui.toggleServiceUpdatedInfoBar({ visible: false }); + } + + @action _reorder({ oldIndex, newIndex }) { + const oldEnabledSortIndex = this.all.indexOf(this.enabled[oldIndex]); + const newEnabledSortIndex = this.all.indexOf(this.enabled[newIndex]); + + + this.all.splice(newEnabledSortIndex, 0, this.all.splice(oldEnabledSortIndex, 1)[0]); + + const services = {}; + this.all.forEach((s, index) => { + services[this.all[index].id] = index; + }); + + this.reorderServicesRequest.execute(services); + this.allServicesRequest.patch((data) => { + data.forEach((s) => { + const service = s; + + service.order = this.one(s.id).order; + }); + }); + + this._reorderAnalytics(); + } + + @action _toggleNotifications({ serviceId }) { + const service = this.one(serviceId); + + service.isNotificationEnabled = !service.isNotificationEnabled; + + this.actions.service.updateService({ + serviceId, + serviceData: service, + redirect: false, + }); + } + + @action _openDevTools({ serviceId }) { + const service = this.one(serviceId); + + service.webview.openDevTools(); + } + + @action _openDevToolsForActiveService() { + const service = this.active; + + if (service) { + service.webview.openDevTools(); + } else { + console.warn('No service is active'); + } + } + + // Reactions + _focusServiceReaction() { + const service = this.active; + if (service) { + this.actions.service.focusService({ serviceId: service.id }); + } + } + + _saveActiveService() { + const service = this.active; + + if (service) { + this.stores.settings.updateSettingsRequest.execute({ + activeService: service.id, + }); + } + } + + _mapActiveServiceToServiceModelReaction() { + const { activeService } = this.stores.settings.all; + const services = this.enabled; + if (services.length) { + services.map(service => Object.assign(service, { + isActive: activeService ? activeService === service.id : services[0].id === service.id, + })); + + // if (!services.active) { + // + // } + } + // else if (!activeService && services.length) { + // services[0].isActive = true; + // } + } + + _getUnreadMessageCountReaction() { + const unreadDirectMessageCount = this.enabled + .map(s => s.unreadDirectMessageCount) + .reduce((a, b) => a + b, 0); + + const unreadIndirectMessageCount = this.enabled + .filter(s => s.isIndirectMessageBadgeEnabled) + .map(s => s.unreadIndirectMessageCount) + .reduce((a, b) => a + b, 0); + + this.actions.app.setBadge({ + unreadDirectMessageCount, + unreadIndirectMessageCount, + }); + } + + _logoutReaction() { + if (!this.stores.user.isLoggedIn) { + this.actions.settings.remove({ key: 'activeService' }); + this.allServicesRequest.invalidate().reset(); + } + } + + _cleanUpTeamIdAndCustomUrl(recipeId, data) { + const serviceData = data; + const recipe = this.stores.recipes.one(recipeId); + + if (recipe.hasTeamId && recipe.hasCustomUrl && data.team && data.customUrl) { + delete serviceData.team; + } + + return serviceData; + } + + // Helper + _redirectToAddServiceRoute(recipeId) { + const route = `/settings/services/add/${recipeId}`; + this.stores.router.push(route); + } + + _initializeServiceRecipeInWebview(serviceId) { + const service = this.one(serviceId); + + if (service.webview) { + service.webview.send('initializeRecipe', service); + } + } + + _initRecipePolling(serviceId) { + const service = this.one(serviceId); + + const delay = 1000; + + if (service) { + const loop = () => { + service.webview.send('poll'); + + setTimeout(loop, delay); + }; + + loop(); + } + } + + _reorderAnalytics = debounce(() => { + gaEvent('Service', 'order'); + }, 5000); +} -- cgit v1.2.3-70-g09d2