From 47c1c99d893517efc679ab29d675cc0bf44be8be Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 11 Apr 2019 16:54:01 +0200 Subject: feat(App): Added Workspaces for all your daily routines 🥳 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * merge default and fetched feature configs * ignore intellij project files * basic setup for workspaces feature * define workspaces as premium feature * add workspaces menu item in settings dialog * basic setup of workspaces settings screen * fix eslint error * assign react key prop to workspace items * add styles for workspace table * setup logic to display workspace edit page * consolidate workspace feature for further development * prepare basic workspace edit form * add on enter key handler for form input component * add form for creating workspaces * small fixes * adds flow for deleting workspaces * stop tracking google analytics in components * pin gulp-sass-variables version to 1.1.1 * fix merge conflict * fix bug in form input library * improve workspace form setup * finish basic workspace settings * finish workspaces mvp * fix eslint issues * remove dev logs * detach service when underlying webview unmounts * disable no-param-reassign eslint rule * add workspace drawer * change workspace switch shortcuts to start with zero * add workspace drawer toggle menu item and shortcut * improve workspace switching ux * style add workspace icon in drawer like the sidebar icons * improve workspace drawer layout * add i18n messages for service loading and workspace switching * small fixes * add tooltip to add workspace button in drawer * add workspaces count badge in settings navigation * fix merge conflicts with latest develop * refactor state management for workspace feature * reset api requests when workspace feature is stopped * hide workspace feature if it is disabled * handle get workspaces request errors in the ui * show infobox when updating workspaces * indicate any server interaction with spinners and infoboxes * add analytic events for workspace actions * improve styling of workspace switch indicator * add workspace premium notice to dashboard * add workspace feature info in drawer for free users * add workspace premium badge in settings nav * fix premium workspace badge in settings menu for light theme * fix active workspaces settings premium badge in light theme * give upgrade account button a bit more padding * add open last used workspace logic * use mobx-localstorage directly in the store * fix wrong workspace tooltip shortcut in sidebar * fix bug in workspace feature initialization * show workspaces intro in drawer when user has none yet * fix issues for users that have workspace but downgraded to free * border radius for premium intro in workspace settings * close workspace drawer after clicking on a workspace * add hover effect for drawer workspace items * ensure drawer is open on workspace settings routes * add small text label for adding new workspace to drawer * make workspace settings list items taller * refactor workspace table css away from legacy styles * render workspace service list like services + toggle * change plus icon in workspace drawer to settings icon * autofocus create workspace input field * add css transition to drawer workspace item hover * fix drawer add workspace label styles * refactors workspace theme vars into object structure * improve contrast of workspace switching indicator * added generic pro badge component for settings nav * add premium badge to workspace drawer headline * add context menu for workspace drawer items * handle deleted services that are attached to workspaces --- src/stores/FeaturesStore.js | 29 +++++++++++++++++++++-------- src/stores/ServicesStore.js | 9 ++++++--- src/stores/UIStore.js | 9 +++++---- src/stores/UserStore.js | 4 ++++ src/stores/lib/Request.js | 6 ++++-- 5 files changed, 40 insertions(+), 17 deletions(-) (limited to 'src/stores') diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index d2842083c..8fe576813 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -1,4 +1,9 @@ -import { computed, observable, reaction } from 'mobx'; +import { + computed, + observable, + reaction, + runInAction, +} from 'mobx'; import Store from './lib/Store'; import CachedRequest from './lib/CachedRequest'; @@ -7,6 +12,7 @@ import delayApp from '../features/delayApp'; import spellchecker from '../features/spellchecker'; import serviceProxy from '../features/serviceProxy'; import basicAuth from '../features/basicAuth'; +import workspaces from '../features/workspaces'; import shareFranz from '../features/shareFranz'; import { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -16,13 +22,16 @@ export default class FeaturesStore extends Store { @observable featuresRequest = new CachedRequest(this.api.features, 'features'); + @observable features = Object.assign({}, DEFAULT_FEATURES_CONFIG); + async setup() { this.registerReactions([ + this._updateFeatures, this._monitorLoginStatus.bind(this), ]); await this.featuresRequest._promise; - setTimeout(this._enableFeatures.bind(this), 1); + setTimeout(this._setupFeatures.bind(this), 1); // single key reaction reaction(() => this.stores.user.data.isPremium, () => { @@ -36,13 +45,16 @@ export default class FeaturesStore extends Store { return this.defaultFeaturesRequest.execute().result || DEFAULT_FEATURES_CONFIG; } - @computed get features() { + _updateFeatures = () => { + const features = Object.assign({}, DEFAULT_FEATURES_CONFIG); if (this.stores.user.isLoggedIn) { - return this.featuresRequest.execute().result || DEFAULT_FEATURES_CONFIG; + const requestResult = this.featuresRequest.execute().result; + Object.assign(features, requestResult); } - - return DEFAULT_FEATURES_CONFIG; - } + runInAction('FeaturesStore::_updateFeatures', () => { + this.features = features; + }); + }; _monitorLoginStatus() { if (this.stores.user.isLoggedIn) { @@ -52,11 +64,12 @@ export default class FeaturesStore extends Store { } } - _enableFeatures() { + _setupFeatures() { delayApp(this.stores, this.actions); spellchecker(this.stores, this.actions); serviceProxy(this.stores, this.actions); basicAuth(this.stores, this.actions); + workspaces(this.stores, this.actions); shareFranz(this.stores, this.actions); } } diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 69e616f0c..0ec6bf550 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -12,6 +12,7 @@ import Request from './lib/Request'; import CachedRequest from './lib/CachedRequest'; import { matchRoute } from '../helpers/routing-helpers'; import { gaEvent } from '../lib/analytics'; +import { workspaceStore } from '../features/workspaces'; const debug = require('debug')('Franz:ServiceStore'); @@ -99,7 +100,6 @@ export default class ServicesStore extends Store { return observable(services.slice().slice().sort((a, b) => a.order - b.order)); } } - return []; } @@ -108,13 +108,16 @@ export default class ServicesStore extends Store { } @computed get allDisplayed() { - return this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; + const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; + return workspaceStore.filterServicesByActiveWorkspace(services); } // This is just used to avoid unnecessary rerendering of resource-heavy webviews @computed get allDisplayedUnordered() { + const { showDisabledServices } = this.stores.settings.all.app; const services = this.allServicesRequest.execute().result || []; - return this.stores.settings.all.app.showDisabledServices ? services : services.filter(service => service.isEnabled); + const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); + return workspaceStore.filterServicesByActiveWorkspace(filteredServices); } @computed get filtered() { diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js index bb7965a4a..a95a8e1e0 100644 --- a/src/stores/UIStore.js +++ b/src/stores/UIStore.js @@ -21,11 +21,12 @@ export default class UIStore extends Store { return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted; } - @computed get theme() { - if (this.stores.settings.all.app.darkMode) { - return theme('dark'); - } + @computed get isDarkThemeActive() { + return this.stores.settings.all.app.darkMode; + } + @computed get theme() { + if (this.isDarkThemeActive) return theme('dark'); return theme('default'); } diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 77d84afe1..534690fbb 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js @@ -142,6 +142,10 @@ export default class UserStore extends Store { return this.getUserInfoRequest.execute().result || {}; } + @computed get isPremium() { + return !!this.data.isPremium; + } + @computed get legacyServices() { return this.getLegacyServicesRequest.execute() || {}; } 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 { return this.execute(...this._currentApiCall.args); } + retry = () => this.reload(); + isExecutingWithArgs(...args) { return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args); } @@ -107,7 +109,7 @@ export default class Request { Request._hooks.forEach(hook => hook(this)); } - reset() { + reset = () => { this.result = null; this.isExecuting = false; this.isError = false; @@ -116,5 +118,5 @@ export default class Request { this._promise = Promise; return this; - } + }; } -- cgit v1.2.3-54-g00ecf