From c32090618e535eb48fb4dc377659ff97dae1a9ee Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Mon, 10 Dec 2018 16:34:53 +0100 Subject: merge default and fetched feature configs --- src/stores/FeaturesStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/stores') diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 0adee6adf..eb2b21af3 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -37,7 +37,7 @@ export default class FeaturesStore extends Store { @computed get features() { if (this.stores.user.isLoggedIn) { - return this.featuresRequest.execute().result || DEFAULT_FEATURES_CONFIG; + return Object.assign({}, DEFAULT_FEATURES_CONFIG, this.featuresRequest.execute().result); } return DEFAULT_FEATURES_CONFIG; -- cgit v1.2.3-70-g09d2 From 4a537e890d95e8666985ce77df4c6327582332be Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Mon, 10 Dec 2018 17:15:37 +0100 Subject: basic setup for workspaces feature --- src/config.js | 1 + src/features/workspaces/api.js | 9 +++++++++ src/features/workspaces/index.js | 34 ++++++++++++++++++++++++++++++++++ src/features/workspaces/store.js | 29 +++++++++++++++++++++++++++++ src/stores/FeaturesStore.js | 2 ++ 5 files changed, 75 insertions(+) create mode 100644 src/features/workspaces/api.js create mode 100644 src/features/workspaces/index.js create mode 100644 src/features/workspaces/store.js (limited to 'src/stores') diff --git a/src/config.js b/src/config.js index 789ddd1a0..d7a485b8a 100644 --- a/src/config.js +++ b/src/config.js @@ -37,6 +37,7 @@ export const DEFAULT_FEATURES_CONFIG = { }, isServiceProxyEnabled: false, isServiceProxyPremiumFeature: true, + isWorkspaceEnabled: true, }; export const DEFAULT_WINDOW_OPTIONS = { diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.js new file mode 100644 index 000000000..1ee2440fe --- /dev/null +++ b/src/features/workspaces/api.js @@ -0,0 +1,9 @@ +// TODO: use real server instead +const workspaces = [ + { id: 'workspace-1', name: 'Private' }, + { id: 'workspace-2', name: 'Office' }, +]; + +export default { + getUserWorkspaces: () => Promise.resolve(workspaces), +}; diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js new file mode 100644 index 000000000..b7e1090e8 --- /dev/null +++ b/src/features/workspaces/index.js @@ -0,0 +1,34 @@ +import { observable, reaction } from 'mobx'; +import { merge } from 'lodash'; +import WorkspacesStore from './store'; +import api from './api'; + +const debug = require('debug')('Franz:feature:workspaces'); + +let store = null; +const defaultState = { workspaces: [] }; + +export const state = observable(defaultState); + +export default function initWorkspaces(stores, actions) { + const { features, user } = stores; + reaction( + () => features.features.isWorkspaceEnabled && user.isLoggedIn, + (isEnabled) => { + if (isEnabled) { + debug('Initializing `workspaces` feature'); + store = new WorkspacesStore(stores, api, actions, state); + store.initialize(); + } else if (store) { + debug('Disabling `workspaces` feature'); + store.teardown(); + store = null; + // Reset state to default + merge(state, defaultState); + } + }, + { + fireImmediately: true, + }, + ); +} diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js new file mode 100644 index 000000000..4b4e729ed --- /dev/null +++ b/src/features/workspaces/store.js @@ -0,0 +1,29 @@ +import { observable, reaction } from 'mobx'; +import Store from '../../stores/lib/Store'; +import CachedRequest from '../../stores/lib/CachedRequest'; + +const debug = require('debug')('Franz:feature:workspaces'); + +export default class WorkspacesStore extends Store { + @observable allWorkspacesRequest = new CachedRequest(this.api, 'getUserWorkspaces'); + + constructor(stores, api, actions, state) { + super(stores, api, actions); + this.state = state; + } + + setup() { + debug('fetching user workspaces'); + this.allWorkspacesRequest.execute(); + + reaction( + () => this.allWorkspacesRequest.result, + workspaces => this.setWorkspaces(workspaces), + ); + } + + setWorkspaces = (workspaces) => { + debug('setting user workspaces', workspaces.slice()); + this.state.workspaces = workspaces; + }; +} diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index eb2b21af3..05a620f0b 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -7,6 +7,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 { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -56,5 +57,6 @@ export default class FeaturesStore extends Store { spellchecker(this.stores, this.actions); serviceProxy(this.stores, this.actions); basicAuth(this.stores, this.actions); + workspaces(this.stores, this.actions); } } -- cgit v1.2.3-70-g09d2 From 739ef2e8a2dec94c3e10c3d26d797fe759fac7aa Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 1 Mar 2019 14:25:44 +0100 Subject: finish workspaces mvp --- src/actions/index.js | 8 ++- src/actions/lib/actions.js | 29 +++++---- src/components/layout/Sidebar.js | 2 +- src/components/services/tabs/Tabbar.js | 4 +- src/features/workspaces/actions.js | 9 ++- .../workspaces/containers/EditWorkspaceScreen.js | 12 ++-- .../workspaces/containers/WorkspacesScreen.js | 10 +-- src/features/workspaces/index.js | 40 +++++++++++- src/features/workspaces/state.js | 6 +- src/features/workspaces/store.js | 37 +++++++---- src/i18n/locales/de.json | 5 +- src/i18n/locales/en-US.json | 5 +- src/lib/Menu.js | 71 +++++++++++++++++++++- src/stores/ServicesStore.js | 12 ++-- 14 files changed, 193 insertions(+), 57 deletions(-) (limited to 'src/stores') diff --git a/src/actions/index.js b/src/actions/index.js index 45e6da515..00f843cd6 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -11,7 +11,7 @@ import payment from './payment'; import news from './news'; import settings from './settings'; import requests from './requests'; -import workspace from '../features/workspaces/actions'; +import workspaces from '../features/workspaces/actions'; const actions = Object.assign({}, { service, @@ -24,7 +24,9 @@ const actions = Object.assign({}, { news, settings, requests, - workspace, }); -export default defineActions(actions, PropTypes.checkPropTypes); +export default Object.assign( + defineActions(actions, PropTypes.checkPropTypes), + { workspaces }, +); diff --git a/src/actions/lib/actions.js b/src/actions/lib/actions.js index 499018d70..6571e9441 100644 --- a/src/actions/lib/actions.js +++ b/src/actions/lib/actions.js @@ -1,18 +1,23 @@ +export const createActionsFromDefinitions = (actionDefinitions, validate) => { + const actions = {}; + Object.keys(actionDefinitions).forEach((actionName) => { + const action = (params) => { + const schema = actionDefinitions[actionName]; + validate(schema, params, actionName); + action.notify(params); + }; + actions[actionName] = action; + action.listeners = []; + action.listen = listener => action.listeners.push(listener); + action.notify = params => action.listeners.forEach(listener => listener(params)); + }); + return actions; +}; + export default (definitions, validate) => { const newActions = {}; Object.keys(definitions).forEach((scopeName) => { - newActions[scopeName] = {}; - Object.keys(definitions[scopeName]).forEach((actionName) => { - const action = (params) => { - const schema = definitions[scopeName][actionName]; - validate(schema, params, actionName); - action.notify(params); - }; - newActions[scopeName][actionName] = action; - action.listeners = []; - action.listen = listener => action.listeners.push(listener); - action.notify = params => action.listeners.forEach(listener => listener(params)); - }); + newActions[scopeName] = createActionsFromDefinitions(definitions[scopeName], validate); }); return newActions; }; diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 609a3b604..fcc5b0001 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js @@ -31,7 +31,7 @@ export default @observer class Sidebar extends Component { openSettings: PropTypes.func.isRequired, toggleMuteApp: PropTypes.func.isRequired, isAppMuted: PropTypes.bool.isRequired, - } + }; static contextTypes = { intl: intlShape, diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js index dd5c2140f..5e8260ad0 100644 --- a/src/components/services/tabs/Tabbar.js +++ b/src/components/services/tabs/Tabbar.js @@ -19,7 +19,7 @@ export default @observer class TabBar extends Component { updateService: PropTypes.func.isRequired, showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, - } + }; onSortEnd = ({ oldIndex, newIndex }) => { const { @@ -45,7 +45,7 @@ export default @observer class TabBar extends Component { redirect: false, }); } - } + }; disableService({ serviceId }) { this.toggleService({ serviceId, isEnabled: false }); diff --git a/src/features/workspaces/actions.js b/src/features/workspaces/actions.js index 84de2b011..25246de09 100644 --- a/src/features/workspaces/actions.js +++ b/src/features/workspaces/actions.js @@ -1,7 +1,8 @@ import PropTypes from 'prop-types'; import Workspace from './models/Workspace'; +import { createActionsFromDefinitions } from '../../actions/lib/actions'; -export default { +export default createActionsFromDefinitions({ edit: { workspace: PropTypes.instanceOf(Workspace).isRequired, }, @@ -14,4 +15,8 @@ export default { update: { workspace: PropTypes.instanceOf(Workspace).isRequired, }, -}; + activate: { + workspace: PropTypes.instanceOf(Workspace).isRequired, + }, + deactivate: {}, +}, PropTypes.checkPropTypes); diff --git a/src/features/workspaces/containers/EditWorkspaceScreen.js b/src/features/workspaces/containers/EditWorkspaceScreen.js index 790b8a0fe..1b13bc2d4 100644 --- a/src/features/workspaces/containers/EditWorkspaceScreen.js +++ b/src/features/workspaces/containers/EditWorkspaceScreen.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ErrorBoundary from '../../../components/util/ErrorBoundary'; import EditWorkspaceForm from '../components/EditWorkspaceForm'; -import { state } from '../state'; +import { workspacesState } from '../state'; import ServicesStore from '../../../stores/ServicesStore'; import Workspace from '../models/Workspace'; @@ -22,23 +22,23 @@ class EditWorkspaceScreen extends Component { }; onDelete = () => { - const { workspaceBeingEdited } = state; + const { workspaceBeingEdited } = workspacesState; const { actions } = this.props; if (!workspaceBeingEdited) return null; - actions.workspace.delete({ workspace: workspaceBeingEdited }); + actions.workspaces.delete({ workspace: workspaceBeingEdited }); }; onSave = (values) => { - const { workspaceBeingEdited } = state; + const { workspaceBeingEdited } = workspacesState; const { actions } = this.props; const workspace = new Workspace( Object.assign({}, workspaceBeingEdited, values), ); - actions.workspace.update({ workspace }); + actions.workspaces.update({ workspace }); }; render() { - const { workspaceBeingEdited } = state; + const { workspaceBeingEdited } = workspacesState; const { stores } = this.props; if (!workspaceBeingEdited) return null; return ( diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js index b89cbcf67..94e714255 100644 --- a/src/features/workspaces/containers/WorkspacesScreen.js +++ b/src/features/workspaces/containers/WorkspacesScreen.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { inject, observer } from 'mobx-react'; import PropTypes from 'prop-types'; -import { state } from '../state'; +import { workspacesState } from '../state'; import WorkspacesDashboard from '../components/WorkspacesDashboard'; import ErrorBoundary from '../../../components/util/ErrorBoundary'; @@ -20,10 +20,10 @@ class WorkspacesScreen extends Component { return ( actions.workspace.create(data)} - onWorkspaceClick={w => actions.workspace.edit({ workspace: w })} + workspaces={workspacesState.workspaces} + isLoading={workspacesState.isLoading} + onCreateWorkspaceSubmit={data => actions.workspaces.create(data)} + onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })} /> ); diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js index 50ac3b414..8091f49fc 100644 --- a/src/features/workspaces/index.js +++ b/src/features/workspaces/index.js @@ -1,14 +1,28 @@ -import { reaction } from 'mobx'; +import { reaction, runInAction } from 'mobx'; import WorkspacesStore from './store'; import api from './api'; -import { state, resetState } from './state'; +import { workspacesState, resetState } from './state'; const debug = require('debug')('Franz:feature:workspaces'); let store = null; +export const filterServicesByActiveWorkspace = (services) => { + const { isFeatureActive, activeWorkspace } = workspacesState; + if (isFeatureActive && activeWorkspace) { + return services.filter(s => activeWorkspace.services.includes(s.id)); + } + return services; +}; + +export const getActiveWorkspaceServices = (services) => { + return filterServicesByActiveWorkspace(services); +}; + export default function initWorkspaces(stores, actions) { const { features, user } = stores; + + // Toggle workspace feature reaction( () => ( features.features.isWorkspaceEnabled && ( @@ -18,10 +32,12 @@ export default function initWorkspaces(stores, actions) { (isEnabled) => { if (isEnabled) { debug('Initializing `workspaces` feature'); - store = new WorkspacesStore(stores, api, actions, state); + store = new WorkspacesStore(stores, api, actions, workspacesState); store.initialize(); + runInAction(() => { workspacesState.isFeatureActive = true; }); } else if (store) { debug('Disabling `workspaces` feature'); + runInAction(() => { workspacesState.isFeatureActive = false; }); store.teardown(); store = null; resetState(); // Reset state to default @@ -31,4 +47,22 @@ export default function initWorkspaces(stores, actions) { fireImmediately: true, }, ); + + // Update active service on workspace switches + reaction(() => ({ + isFeatureActive: workspacesState.isFeatureActive, + activeWorkspace: workspacesState.activeWorkspace, + }), ({ isFeatureActive, activeWorkspace }) => { + if (!isFeatureActive) return; + if (activeWorkspace) { + const services = stores.services.allDisplayed; + const activeService = services.find(s => s.isActive); + const workspaceServices = filterServicesByActiveWorkspace(services); + const isActiveServiceInWorkspace = workspaceServices.includes(activeService); + if (!isActiveServiceInWorkspace) { + console.log(workspaceServices[0].id); + actions.service.setActive({ serviceId: workspaceServices[0].id }); + } + } + }); } diff --git a/src/features/workspaces/state.js b/src/features/workspaces/state.js index f938c1470..963b96f81 100644 --- a/src/features/workspaces/state.js +++ b/src/features/workspaces/state.js @@ -1,13 +1,15 @@ import { observable } from 'mobx'; const defaultState = { + activeWorkspace: null, isLoading: false, + isFeatureActive: false, workspaces: [], workspaceBeingEdited: null, }; -export const state = observable(defaultState); +export const workspacesState = observable(defaultState); export function resetState() { - Object.assign(state, defaultState); + Object.assign(workspacesState, defaultState); } diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index 5cccb2ab7..a2997a0d2 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -1,8 +1,9 @@ -import { observable, reaction } from 'mobx'; +import { observable, reaction, action } from 'mobx'; import Store from '../../stores/lib/Store'; import CachedRequest from '../../stores/lib/CachedRequest'; import Workspace from './models/Workspace'; import { matchRoute } from '../../helpers/routing-helpers'; +import workspaceActions from './actions'; const debug = require('debug')('Franz:feature:workspaces'); @@ -48,28 +49,30 @@ export default class WorkspacesStore extends Store { }, ); - this.actions.workspace.edit.listen(this._edit); - this.actions.workspace.create.listen(this._create); - this.actions.workspace.delete.listen(this._delete); - this.actions.workspace.update.listen(this._update); + workspaceActions.edit.listen(this._edit); + workspaceActions.create.listen(this._create); + workspaceActions.delete.listen(this._delete); + workspaceActions.update.listen(this._update); + workspaceActions.activate.listen(this._setActiveWorkspace); + workspaceActions.deactivate.listen(this._deactivateActiveWorkspace); } - _setWorkspaces = (workspaces) => { + _getWorkspaceById = id => this.state.workspaces.find(w => w.id === id); + + @action _setWorkspaces = (workspaces) => { debug('setting user workspaces', workspaces.slice()); this.state.workspaces = workspaces.map(data => new Workspace(data)); }; - _setIsLoading = (isLoading) => { + @action _setIsLoading = (isLoading) => { this.state.isLoading = isLoading; }; - _getWorkspaceById = id => this.state.workspaces.find(w => w.id === id); - - _edit = ({ workspace }) => { + @action _edit = ({ workspace }) => { this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`); }; - _create = async ({ name }) => { + @action _create = async ({ name }) => { try { const result = await this.api.createWorkspace(name); const workspace = new Workspace(result); @@ -80,7 +83,7 @@ export default class WorkspacesStore extends Store { } }; - _delete = async ({ workspace }) => { + @action _delete = async ({ workspace }) => { try { await this.api.deleteWorkspace(workspace); this.state.workspaces.remove(workspace); @@ -90,7 +93,7 @@ export default class WorkspacesStore extends Store { } }; - _update = async ({ workspace }) => { + @action _update = async ({ workspace }) => { try { await this.api.updateWorkspace(workspace); const localWorkspace = this.state.workspaces.find(ws => ws.id === workspace.id); @@ -100,4 +103,12 @@ export default class WorkspacesStore extends Store { throw error; } }; + + @action _setActiveWorkspace = ({ workspace }) => { + this.state.activeWorkspace = workspace; + }; + + @action _deactivateActiveWorkspace = () => { + this.state.activeWorkspace = null; + }; } diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 4906070a3..0c1fb8aa6 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -74,6 +74,9 @@ "menu.window" : "Fenster", "menu.window.close" : "Schließen", "menu.window.minimize" : "Minimieren", + "menu.workspaces": "Workspaces", + "menu.workspaces.defaultWorkspace": "All services", + "menu.workspaces.addNewWorkspace": "Add New Workspace", "password.email.label" : "E-Mail Adresse", "password.headline" : "Passwort zurücksetzen", "password.link.login" : "An Deinem Konto anmelden", @@ -224,7 +227,7 @@ "settings.workspace.form.name": "Name", "settings.workspace.form.buttonDelete": "Workspace löschen", "settings.workspace.form.buttonSave": "Workspace speichern", - "settings.workspace.form.servicesInWorkspaceHeadline": "Services in diesem Workspace", + "settings.workspace.form.servicesInWorkspaceHeadline": "Services in diesem Workspace", "settings.user.form.accountType.company" : "Firma", "settings.user.form.accountType.individual" : "Einzelperson", "settings.user.form.accountType.label" : "Konto-Typ", diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 988ac46f2..2a51662a2 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -276,7 +276,10 @@ "menu.app.hideOthers": "Hide Others", "menu.app.unhide": "Unhide", "menu.app.quit": "Quit", - "menu.services.addNewService": "Add New Service...", + "menu.services.addNewService": "Add New Service", + "menu.workspaces": "Workspaces", + "menu.workspaces.defaultWorkspace": "All services", + "menu.workspaces.addNewWorkspace": "Add New Workspace", "validation.required": "{field} is required", "validation.email": "{field} is not valid", "validation.url": "{field} is not a valid URL", diff --git a/src/lib/Menu.js b/src/lib/Menu.js index c378619ad..1560dd285 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js @@ -3,6 +3,8 @@ import { observable, autorun, computed } from 'mobx'; import { defineMessages } from 'react-intl'; import { isMac, ctrlKey, cmdKey } from '../environment'; +import { workspacesState } from '../features/workspaces/state'; +import workspaceActions from '../features/workspaces/actions'; const { app, Menu, dialog } = remote; @@ -179,6 +181,18 @@ const menuItems = defineMessages({ id: 'menu.services.addNewService', defaultMessage: '!!!Add New Service...', }, + workspaces: { + id: 'menu.workspaces', + defaultMessage: '!!!Workspaces', + }, + defaultWorkspace: { + id: 'menu.workspaces.defaultWorkspace', + defaultMessage: '!!!Default', + }, + addNewWorkspace: { + id: 'menu.workspaces.addNewWorkspace', + defaultMessage: '!!!Add New Workspace...', + }, }); function getActiveWebview() { @@ -265,6 +279,10 @@ const _templateFactory = intl => [ label: intl.formatMessage(menuItems.services), submenu: [], }, + { + label: intl.formatMessage(menuItems.workspaces), + submenu: [], + }, { label: intl.formatMessage(menuItems.window), role: 'window', @@ -499,7 +517,9 @@ export default class FranzMenu { } _build() { - const serviceTpl = Object.assign([], this.serviceTpl); // need to clone object so we don't modify computed (cached) object + // need to clone object so we don't modify computed (cached) object + const serviceTpl = Object.assign([], this.serviceTpl); + const workspacesMenu = Object.assign([], this.workspacesMenu); if (window.franz === undefined) { return; @@ -632,7 +652,7 @@ export default class FranzMenu { }, ); - tpl[4].submenu.unshift(about, { + tpl[5].submenu.unshift(about, { type: 'separator', }); } else { @@ -678,6 +698,8 @@ export default class FranzMenu { tpl[3].submenu = serviceTpl; } + tpl[4].submenu = workspacesMenu; + this.currentTemplate = tpl; const menu = Menu.buildFromTemplate(tpl); Menu.setApplicationMenu(menu); @@ -701,6 +723,51 @@ export default class FranzMenu { return []; } + @computed get workspacesMenu() { + const { workspaces, activeWorkspace } = workspacesState; + const { intl } = window.franz; + const menu = []; + + // Add new workspace item: + menu.push({ + label: intl.formatMessage(menuItems.addNewWorkspace), + accelerator: `${cmdKey}+Shift+N`, + click: () => { + this.actions.ui.openSettings({ path: 'workspaces' }); + }, + enabled: this.stores.user.isLoggedIn, + }, { + type: 'separator', + }); + + // Default workspace + menu.push({ + label: intl.formatMessage(menuItems.defaultWorkspace), + accelerator: `${cmdKey}+Alt+1`, + type: 'radio', + checked: !activeWorkspace, + click: () => { + workspaceActions.deactivate(); + }, + }); + + // Workspace items + if (this.stores.user.isLoggedIn) { + workspaces.forEach((workspace, i) => menu.push({ + label: workspace.name, + accelerator: i < 9 ? `${cmdKey}+Alt+${i + 2}` : null, + type: 'radio', + checked: activeWorkspace ? workspace.id === activeWorkspace.id : false, + click: () => { + workspaceActions.activate({ workspace }); + }, + })); + } + + console.log(menu); + return menu; + } + _getServiceName(service) { if (service.name) { return service.name; diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index c63bef196..a86db8103 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -2,7 +2,7 @@ import { action, reaction, computed, - observable, + observable, runInAction, } from 'mobx'; import { debounce, remove } from 'lodash'; import ms from 'ms'; @@ -12,6 +12,8 @@ import Request from './lib/Request'; import CachedRequest from './lib/CachedRequest'; import { matchRoute } from '../helpers/routing-helpers'; import { gaEvent } from '../lib/analytics'; +import { workspacesState } from '../features/workspaces/state'; +import { filterServicesByActiveWorkspace, getActiveWorkspaceServices } from '../features/workspaces'; const debug = require('debug')('Franz:ServiceStore'); @@ -98,7 +100,6 @@ export default class ServicesStore extends Store { return observable(services.slice().slice().sort((a, b) => a.order - b.order)); } } - return []; } @@ -107,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 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 getActiveWorkspaceServices(filteredServices); } @computed get filtered() { -- cgit v1.2.3-70-g09d2 From 1947cad07e0d9c32ffb874bdea482e7ff037888b Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 1 Mar 2019 14:27:40 +0100 Subject: fix eslint issues --- src/features/workspaces/index.js | 6 +++--- src/stores/ServicesStore.js | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src/stores') diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js index 8091f49fc..79c9b8ac9 100644 --- a/src/features/workspaces/index.js +++ b/src/features/workspaces/index.js @@ -15,9 +15,9 @@ export const filterServicesByActiveWorkspace = (services) => { return services; }; -export const getActiveWorkspaceServices = (services) => { - return filterServicesByActiveWorkspace(services); -}; +export const getActiveWorkspaceServices = services => ( + filterServicesByActiveWorkspace(services) +); export default function initWorkspaces(stores, actions) { const { features, user } = stores; diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index a86db8103..da4b19c0d 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -2,7 +2,7 @@ import { action, reaction, computed, - observable, runInAction, + observable, } from 'mobx'; import { debounce, remove } from 'lodash'; import ms from 'ms'; @@ -12,7 +12,6 @@ import Request from './lib/Request'; import CachedRequest from './lib/CachedRequest'; import { matchRoute } from '../helpers/routing-helpers'; import { gaEvent } from '../lib/analytics'; -import { workspacesState } from '../features/workspaces/state'; import { filterServicesByActiveWorkspace, getActiveWorkspaceServices } from '../features/workspaces'; const debug = require('debug')('Franz:ServiceStore'); -- cgit v1.2.3-70-g09d2 From cf9d7a30fed4fe50c346e652073464b07277a81e Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 8 Mar 2019 14:48:46 +0100 Subject: detach service when underlying webview unmounts --- src/actions/service.js | 4 + src/components/services/content/ServiceView.js | 136 ++++++++++++++++++++ src/components/services/content/ServiceWebview.js | 145 ++++------------------ src/components/services/content/Services.js | 7 +- src/containers/layout/AppLayoutContainer.js | 3 + src/stores/ServicesStore.js | 6 + 6 files changed, 179 insertions(+), 122 deletions(-) create mode 100644 src/components/services/content/ServiceView.js (limited to 'src/stores') diff --git a/src/actions/service.js b/src/actions/service.js index 5d483b12a..ceaabc31e 100644 --- a/src/actions/service.js +++ b/src/actions/service.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import ServiceModel from '../models/Service'; export default { setActive: { @@ -36,6 +37,9 @@ export default { serviceId: PropTypes.string.isRequired, webview: PropTypes.object.isRequired, }, + detachService: { + service: PropTypes.instanceOf(ServiceModel).isRequired, + }, focusService: { serviceId: PropTypes.string.isRequired, }, diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js new file mode 100644 index 000000000..5afc54f9d --- /dev/null +++ b/src/components/services/content/ServiceView.js @@ -0,0 +1,136 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import { autorun } from 'mobx'; +import { observer } from 'mobx-react'; +import classnames from 'classnames'; + +import ServiceModel from '../../../models/Service'; +import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; +import WebviewLoader from '../../ui/WebviewLoader'; +import WebviewCrashHandler from './WebviewCrashHandler'; +import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; +import ServiceDisabled from './ServiceDisabled'; +import ServiceWebview from './ServiceWebview'; + +export default @observer class ServiceView extends Component { + static propTypes = { + service: PropTypes.instanceOf(ServiceModel).isRequired, + setWebviewReference: PropTypes.func.isRequired, + detachService: PropTypes.func.isRequired, + reload: PropTypes.func.isRequired, + edit: PropTypes.func.isRequired, + enable: PropTypes.func.isRequired, + isActive: PropTypes.bool, + }; + + static defaultProps = { + isActive: false, + }; + + state = { + forceRepaint: false, + targetUrl: '', + statusBarVisible: false, + }; + + autorunDisposer = null; + + componentDidMount() { + this.autorunDisposer = autorun(() => { + if (this.props.service.isActive) { + this.setState({ forceRepaint: true }); + setTimeout(() => { + this.setState({ forceRepaint: false }); + }, 100); + } + }); + } + + componentWillUnmount() { + this.autorunDisposer(); + } + + updateTargetUrl = (event) => { + let visible = true; + if (event.url === '' || event.url === '#') { + visible = false; + } + this.setState({ + targetUrl: event.url, + statusBarVisible: visible, + }); + }; + + render() { + const { + detachService, + service, + setWebviewReference, + reload, + edit, + enable, + } = this.props; + + const webviewClasses = classnames({ + services__webview: true, + 'services__webview-wrapper': true, + 'is-active': service.isActive, + 'services__webview--force-repaint': this.state.forceRepaint, + }); + + let statusBar = null; + if (this.state.statusBarVisible) { + statusBar = ( + + ); + } + + return ( +
+ {service.isActive && service.isEnabled && ( + + {service.hasCrashed && ( + + )} + {service.isEnabled && service.isLoading && service.isFirstLoad && ( + + )} + {service.isError && ( + + )} + + )} + {!service.isEnabled ? ( + + {service.isActive && ( + + )} + + ) : ( + + )} + {statusBar} +
+ ); + } +} diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index bb577e4cc..7252c695f 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js @@ -1,145 +1,50 @@ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { autorun } from 'mobx'; import { observer } from 'mobx-react'; -import Webview from 'react-electron-web-view'; -import classnames from 'classnames'; +import ElectronWebView from 'react-electron-web-view'; import ServiceModel from '../../../models/Service'; -import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; -import WebviewLoader from '../../ui/WebviewLoader'; -import WebviewCrashHandler from './WebviewCrashHandler'; -import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; -import ServiceDisabled from './ServiceDisabled'; -export default @observer class ServiceWebview extends Component { +@observer +class ServiceWebview extends Component { static propTypes = { service: PropTypes.instanceOf(ServiceModel).isRequired, setWebviewReference: PropTypes.func.isRequired, - reload: PropTypes.func.isRequired, - edit: PropTypes.func.isRequired, - enable: PropTypes.func.isRequired, - isActive: PropTypes.bool, + detachService: PropTypes.func.isRequired, }; - static defaultProps = { - isActive: false, - }; - - state = { - forceRepaint: false, - targetUrl: '', - statusBarVisible: false, - }; - - autorunDisposer = null; - webview = null; - componentDidMount() { - this.autorunDisposer = autorun(() => { - if (this.props.service.isActive) { - this.setState({ forceRepaint: true }); - setTimeout(() => { - this.setState({ forceRepaint: false }); - }, 100); - } - }); - } - componentWillUnmount() { - this.autorunDisposer(); - } - - updateTargetUrl = (event) => { - let visible = true; - if (event.url === '' || event.url === '#') { - visible = false; - } - this.setState({ - targetUrl: event.url, - statusBarVisible: visible, - }); + const { service, detachService } = this.props; + detachService({ service }); } render() { const { service, setWebviewReference, - reload, - edit, - enable, } = this.props; - const webviewClasses = classnames({ - services__webview: true, - 'services__webview-wrapper': true, - 'is-active': service.isActive, - 'services__webview--force-repaint': this.state.forceRepaint, - }); - - let statusBar = null; - if (this.state.statusBarVisible) { - statusBar = ( - - ); - } - return ( -
- {service.isActive && service.isEnabled && ( - - {service.hasCrashed && ( - - )} - {service.isEnabled && service.isLoading && service.isFirstLoad && ( - - )} - {service.isError && ( - - )} - - )} - {!service.isEnabled ? ( - - {service.isActive && ( - - )} - - ) : ( - { this.webview = element; }} - autosize - src={service.url} - preload="./webview/recipe.js" - partition={`persist:service-${service.id}`} - onDidAttach={() => setWebviewReference({ - serviceId: service.id, - webview: this.webview.view, - })} - onUpdateTargetUrl={this.updateTargetUrl} - useragent={service.userAgent} - allowpopups - /> - )} - {statusBar} -
+ { this.webview = webview; }} + autosize + src={service.url} + preload="./webview/recipe.js" + partition={`persist:service-${service.id}`} + onDidAttach={() => { + setWebviewReference({ + serviceId: service.id, + webview: this.webview.view, + }); + }} + onUpdateTargetUrl={this.updateTargetUrl} + useragent={service.userAgent} + allowpopups + /> ); } } + +export default ServiceWebview; diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 54f16ba12..8f8c38a11 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js @@ -4,7 +4,7 @@ import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; import { Link } from 'react-router'; import { defineMessages, intlShape } from 'react-intl'; -import Webview from './ServiceWebview'; +import ServiceView from './ServiceView'; import Appear from '../../ui/effects/Appear'; const messages = defineMessages({ @@ -22,6 +22,7 @@ export default @observer class Services extends Component { static propTypes = { services: MobxPropTypes.arrayOrObservableArray, setWebviewReference: PropTypes.func.isRequired, + detachService: PropTypes.func.isRequired, handleIPCMessage: PropTypes.func.isRequired, openWindow: PropTypes.func.isRequired, reload: PropTypes.func.isRequired, @@ -42,6 +43,7 @@ export default @observer class Services extends Component { services, handleIPCMessage, setWebviewReference, + detachService, openWindow, reload, openSettings, @@ -71,11 +73,12 @@ export default @observer class Services extends Component { )} {services.map(service => ( - reload({ serviceId: service.id })} edit={() => openSettings({ path: `services/edit/${service.id}` })} diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 749912c59..5a05ce431 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -42,6 +42,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e setActive, handleIPCMessage, setWebviewReference, + detachService, openWindow, reorder, reload, @@ -105,6 +106,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e services={services.allDisplayedUnordered} handleIPCMessage={handleIPCMessage} setWebviewReference={setWebviewReference} + detachService={detachService} openWindow={openWindow} reload={reload} openSettings={openSettings} @@ -160,6 +162,7 @@ AppLayoutContainer.wrappedComponent.propTypes = { toggleAudio: PropTypes.func.isRequired, handleIPCMessage: PropTypes.func.isRequired, setWebviewReference: PropTypes.func.isRequired, + detachService: PropTypes.func.isRequired, openWindow: PropTypes.func.isRequired, reloadUpdatedServices: PropTypes.func.isRequired, updateService: PropTypes.func.isRequired, diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index c63bef196..69e616f0c 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -44,6 +44,7 @@ export default class ServicesStore extends Store { this.actions.service.deleteService.listen(this._deleteService.bind(this)); this.actions.service.clearCache.listen(this._clearCache.bind(this)); this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); + this.actions.service.detachService.listen(this._detachService.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)); @@ -341,6 +342,11 @@ export default class ServicesStore extends Store { service.isAttached = true; } + @action _detachService({ service }) { + service.webview = null; + service.isAttached = false; + } + @action _focusService({ serviceId }) { const service = this.one(serviceId); -- cgit v1.2.3-70-g09d2 From 489d4cee38daf6177a9a914d3ccb5048c8acd01a Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 21 Mar 2019 12:35:56 +0100 Subject: add workspace drawer toggle menu item and shortcut --- src/i18n/locales/defaultMessages.json | 74 +++++++++++++++++++++++------------ src/i18n/locales/en-US.json | 2 + src/i18n/messages/src/lib/Menu.json | 50 +++++++++++++++++------ src/lib/Menu.js | 23 ++++++++++- src/stores/UserStore.js | 4 ++ 5 files changed, 116 insertions(+), 37 deletions(-) (limited to 'src/stores') diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 4bf9a8c0a..1c6c6c969 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -625,78 +625,78 @@ "defaultMessage": "!!!Your services have been updated.", "end": { "column": 3, - "line": 25 + "line": 27 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.servicesUpdated", "start": { "column": 19, - "line": 22 + "line": 24 } }, { "defaultMessage": "!!!A new update for Franz is available.", "end": { "column": 3, - "line": 29 + "line": 31 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.updateAvailable", "start": { "column": 19, - "line": 26 + "line": 28 } }, { "defaultMessage": "!!!Reload services", "end": { "column": 3, - "line": 33 + "line": 35 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonReloadServices", "start": { "column": 24, - "line": 30 + "line": 32 } }, { "defaultMessage": "!!!Changelog", "end": { "column": 3, - "line": 37 + "line": 39 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonChangelog", "start": { "column": 13, - "line": 34 + "line": 36 } }, { "defaultMessage": "!!!Restart & install update", "end": { "column": 3, - "line": 41 + "line": 43 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonInstallUpdate", "start": { "column": 23, - "line": 38 + "line": 40 } }, { "defaultMessage": "!!!Could not load services and user information", "end": { "column": 3, - "line": 45 + "line": 47 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.requiredRequestsFailed", "start": { "column": 26, - "line": 42 + "line": 44 } } ], @@ -4092,81 +4092,107 @@ } }, { - "defaultMessage": "!!!Activate next service...", + "defaultMessage": "!!!Open workspace drawer", "end": { "column": 3, "line": 191 }, "file": "src/lib/Menu.js", - "id": "menu.services.setNextServiceActive", + "id": "menu.workspaces.openWorkspaceDrawer", "start": { "column": 23, "line": 188 } }, { - "defaultMessage": "!!!Activate previous service...", + "defaultMessage": "!!!Close workspace drawer", "end": { "column": 3, "line": 195 }, "file": "src/lib/Menu.js", + "id": "menu.workspaces.closeWorkspaceDrawer", + "start": { + "column": 24, + "line": 192 + } + }, + { + "defaultMessage": "!!!Activate next service...", + "end": { + "column": 3, + "line": 199 + }, + "file": "src/lib/Menu.js", + "id": "menu.services.setNextServiceActive", + "start": { + "column": 23, + "line": 196 + } + }, + { + "defaultMessage": "!!!Activate previous service...", + "end": { + "column": 3, + "line": 203 + }, + "file": "src/lib/Menu.js", "id": "menu.services.activatePreviousService", "start": { "column": 27, - "line": 192 + "line": 200 } }, { "defaultMessage": "!!!Disable notifications & audio", "end": { "column": 3, - "line": 199 + "line": 207 }, "file": "src/lib/Menu.js", "id": "sidebar.muteApp", "start": { "column": 11, - "line": 196 + "line": 204 } }, { "defaultMessage": "!!!Enable notifications & audio", "end": { "column": 3, - "line": 203 + "line": 211 }, "file": "src/lib/Menu.js", "id": "sidebar.unmuteApp", "start": { "column": 13, - "line": 200 + "line": 208 } }, { "defaultMessage": "!!!Workspaces", "end": { "column": 3, - "line": 207 + "line": 215 }, "file": "src/lib/Menu.js", "id": "menu.workspaces", "start": { "column": 14, - "line": 204 + "line": 212 } }, { "defaultMessage": "!!!Default", "end": { "column": 3, - "line": 211 + "line": 219 }, "file": "src/lib/Menu.js", "id": "menu.workspaces.defaultWorkspace", "start": { "column": 20, - "line": 208 + "line": 216 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 94c6fcf32..9716c3717 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -89,7 +89,9 @@ "menu.window.minimize": "Minimize", "menu.workspaces": "Workspaces", "menu.workspaces.addNewWorkspace": "Add New Workspace...", + "menu.workspaces.closeWorkspaceDrawer": "Close workspace drawer", "menu.workspaces.defaultWorkspace": "All services", + "menu.workspaces.openWorkspaceDrawer": "Open workspace drawer", "password.email.label": "Email address", "password.headline": "Reset password", "password.link.login": "Sign in to your account", diff --git a/src/i18n/messages/src/lib/Menu.json b/src/i18n/messages/src/lib/Menu.json index 53fa038c6..adbbd2638 100644 --- a/src/i18n/messages/src/lib/Menu.json +++ b/src/i18n/messages/src/lib/Menu.json @@ -572,8 +572,8 @@ } }, { - "id": "menu.services.setNextServiceActive", - "defaultMessage": "!!!Activate next service...", + "id": "menu.workspaces.openWorkspaceDrawer", + "defaultMessage": "!!!Open workspace drawer", "file": "src/lib/Menu.js", "start": { "line": 188, @@ -584,16 +584,42 @@ "column": 3 } }, + { + "id": "menu.workspaces.closeWorkspaceDrawer", + "defaultMessage": "!!!Close workspace drawer", + "file": "src/lib/Menu.js", + "start": { + "line": 192, + "column": 24 + }, + "end": { + "line": 195, + "column": 3 + } + }, + { + "id": "menu.services.setNextServiceActive", + "defaultMessage": "!!!Activate next service...", + "file": "src/lib/Menu.js", + "start": { + "line": 196, + "column": 23 + }, + "end": { + "line": 199, + "column": 3 + } + }, { "id": "menu.services.activatePreviousService", "defaultMessage": "!!!Activate previous service...", "file": "src/lib/Menu.js", "start": { - "line": 192, + "line": 200, "column": 27 }, "end": { - "line": 195, + "line": 203, "column": 3 } }, @@ -602,11 +628,11 @@ "defaultMessage": "!!!Disable notifications & audio", "file": "src/lib/Menu.js", "start": { - "line": 196, + "line": 204, "column": 11 }, "end": { - "line": 199, + "line": 207, "column": 3 } }, @@ -615,11 +641,11 @@ "defaultMessage": "!!!Enable notifications & audio", "file": "src/lib/Menu.js", "start": { - "line": 200, + "line": 208, "column": 13 }, "end": { - "line": 203, + "line": 211, "column": 3 } }, @@ -628,11 +654,11 @@ "defaultMessage": "!!!Workspaces", "file": "src/lib/Menu.js", "start": { - "line": 204, + "line": 212, "column": 14 }, "end": { - "line": 207, + "line": 215, "column": 3 } }, @@ -641,11 +667,11 @@ "defaultMessage": "!!!Default", "file": "src/lib/Menu.js", "start": { - "line": 208, + "line": 216, "column": 20 }, "end": { - "line": 211, + "line": 219, "column": 3 } } diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 1d4f0e001..2a88515f4 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js @@ -185,6 +185,14 @@ const menuItems = defineMessages({ id: 'menu.workspaces.addNewWorkspace', defaultMessage: '!!!Add New Workspace...', }, + openWorkspaceDrawer: { + id: 'menu.workspaces.openWorkspaceDrawer', + defaultMessage: '!!!Open workspace drawer', + }, + closeWorkspaceDrawer: { + id: 'menu.workspaces.closeWorkspaceDrawer', + defaultMessage: '!!!Close workspace drawer', + }, activateNextService: { id: 'menu.services.setNextServiceActive', defaultMessage: '!!!Activate next service...', @@ -788,6 +796,19 @@ export default class FranzMenu { workspaceActions.openWorkspaceSettings(); }, enabled: this.stores.user.isLoggedIn, + }); + + // Open workspace drawer: + const drawerLabel = ( + workspacesState.isWorkspaceDrawerOpen ? menuItems.closeWorkspaceDrawer : menuItems.openWorkspaceDrawer + ); + menu.push({ + label: intl.formatMessage(drawerLabel), + accelerator: `${cmdKey}+D`, + click: () => { + workspaceActions.toggleWorkspaceDrawer(); + }, + enabled: this.stores.user.isLoggedIn, }, { type: 'separator', }); @@ -804,7 +825,7 @@ export default class FranzMenu { }); // Workspace items - if (this.stores.user.isLoggedIn) { + if (this.stores.user.isPremium) { workspaces.forEach((workspace, i) => menu.push({ label: workspace.name, accelerator: i < 9 ? `${cmdKey}+Alt+${i + 1}` : null, 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() || {}; } -- cgit v1.2.3-70-g09d2 From 0af622e6e81a5aee64f839eeadd23b4a62b3cf62 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Sat, 23 Mar 2019 14:15:57 +0100 Subject: refactor state management for workspace feature --- src/components/layout/AppLayout.js | 4 +- src/components/services/content/ServiceView.js | 6 +- src/components/ui/AppLoader/index.js | 4 +- src/containers/layout/AppLayoutContainer.js | 4 +- src/containers/settings/SettingsWindow.js | 4 +- src/features/workspaces/api.js | 51 ++++-- .../workspaces/components/WorkspaceDrawer.js | 11 +- .../components/WorkspaceSwitchingIndicator.js | 4 +- .../workspaces/containers/EditWorkspaceScreen.js | 8 +- .../workspaces/containers/WorkspacesScreen.js | 7 +- src/features/workspaces/index.js | 51 +----- src/features/workspaces/state.js | 18 -- src/features/workspaces/store.js | 196 +++++++++++++-------- src/lib/Menu.js | 6 +- src/stores/ServicesStore.js | 6 +- 15 files changed, 199 insertions(+), 181 deletions(-) delete mode 100644 src/features/workspaces/state.js (limited to 'src/stores') diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 4dd5ff686..0c72c1413 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -14,8 +14,8 @@ import ErrorBoundary from '../util/ErrorBoundary'; // import globalMessages from '../../i18n/globalMessages'; import { isWindows } from '../../environment'; -import { workspacesState } from '../../features/workspaces/state'; import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; +import { workspaceStore } from '../../features/workspaces'; function createMarkup(HTMLString) { return { __html: HTMLString }; @@ -53,7 +53,7 @@ const styles = theme => ({ width: `calc(100% + ${theme.workspaceDrawerWidth}px)`, transition: 'transform 0.5s ease', transform() { - return workspacesState.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaceDrawerWidth}px)`; + return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaceDrawerWidth}px)`; }, }, }); diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index ada920cb6..13148b9b3 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js @@ -35,12 +35,13 @@ export default @observer class ServiceView extends Component { autorunDisposer = null; + forceRepaintTimeout = null; + componentDidMount() { this.autorunDisposer = autorun(() => { - if (!this.isMounted) return; if (this.props.service.isActive) { this.setState({ forceRepaint: true }); - setTimeout(() => { + this.forceRepaintTimeout = setTimeout(() => { this.setState({ forceRepaint: false }); }, 100); } @@ -49,6 +50,7 @@ export default @observer class ServiceView extends Component { componentWillUnmount() { this.autorunDisposer(); + clearTimeout(this.forceRepaintTimeout); } updateTargetUrl = (event) => { diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js index 61053f6d1..b0c7fed7b 100644 --- a/src/components/ui/AppLoader/index.js +++ b/src/components/ui/AppLoader/index.js @@ -23,11 +23,11 @@ export default @injectSheet(styles) @withTheme class AppLoader extends Component static propTypes = { classes: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, - } + }; state = { step: 0, - } + }; interval = null; diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 772458eab..4329c3097 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -20,9 +20,9 @@ import Services from '../../components/services/content/Services'; import AppLoader from '../../components/ui/AppLoader'; import { state as delayAppState } from '../../features/delayApp'; -import { workspacesState } from '../../features/workspaces/state'; import { workspaceActions } from '../../features/workspaces/actions'; import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; +import { workspaceStore } from '../../features/workspaces'; export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { static defaultProps = { @@ -108,7 +108,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e updateService={updateService} toggleMuteApp={toggleMuteApp} toggleWorkspaceDrawer={workspaceActions.toggleWorkspaceDrawer} - isWorkspaceDrawerOpen={workspacesState.isWorkspaceDrawerOpen} + isWorkspaceDrawerOpen={workspaceStore.isWorkspaceDrawerOpen} showMessageBadgeWhenMutedSetting={settings.all.app.showMessageBadgeWhenMuted} showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted} /> diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js index 8cbde24c1..663b9e2e4 100644 --- a/src/containers/settings/SettingsWindow.js +++ b/src/containers/settings/SettingsWindow.js @@ -7,7 +7,7 @@ import ServicesStore from '../../stores/ServicesStore'; import Layout from '../../components/settings/SettingsLayout'; import Navigation from '../../components/settings/navigation/SettingsNavigation'; import ErrorBoundary from '../../components/util/ErrorBoundary'; -import { workspacesState } from '../../features/workspaces/state'; +import { workspaceStore } from '../../features/workspaces'; export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { render() { @@ -17,7 +17,7 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex const navigation = ( ); diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.js index 733cb5593..4e076d233 100644 --- a/src/features/workspaces/api.js +++ b/src/features/workspaces/api.js @@ -1,39 +1,60 @@ import { pick } from 'lodash'; import { sendAuthRequest } from '../../api/utils/auth'; import { API, API_VERSION } from '../../environment'; +import Request from '../../stores/lib/Request'; +import CachedRequest from '../../stores/lib/CachedRequest'; +import Workspace from './models/Workspace'; -export default { +const debug = require('debug')('Franz:feature:workspaces:api'); + +export const workspaceApi = { getUserWorkspaces: async () => { const url = `${API}/${API_VERSION}/workspace`; - const request = await sendAuthRequest(url, { method: 'GET' }); - if (!request.ok) throw request; - return request.json(); + debug('getUserWorkspaces GET', url); + const result = await sendAuthRequest(url, { method: 'GET' }); + debug('getUserWorkspaces RESULT', result); + if (!result.ok) throw result; + const workspaces = await result.json(); + return workspaces.map(data => new Workspace(data)); }, createWorkspace: async (name) => { const url = `${API}/${API_VERSION}/workspace`; - const request = await sendAuthRequest(url, { + const options = { method: 'POST', body: JSON.stringify({ name }), - }); - if (!request.ok) throw request; - return request.json(); + }; + debug('createWorkspace POST', url, options); + const result = await sendAuthRequest(url, options); + debug('createWorkspace RESULT', result); + if (!result.ok) throw result; + return new Workspace(await result.json()); }, deleteWorkspace: async (workspace) => { const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; - const request = await sendAuthRequest(url, { method: 'DELETE' }); - if (!request.ok) throw request; - return request.json(); + debug('deleteWorkspace DELETE', url); + const result = await sendAuthRequest(url, { method: 'DELETE' }); + debug('deleteWorkspace RESULT', result); + if (!result.ok) throw result; + return (await result.json()).deleted; }, updateWorkspace: async (workspace) => { const url = `${API}/${API_VERSION}/workspace/${workspace.id}`; - const request = await sendAuthRequest(url, { + const options = { method: 'PUT', body: JSON.stringify(pick(workspace, ['name', 'services'])), - }); - if (!request.ok) throw request; - return request.json(); + }; + debug('updateWorkspace UPDATE', url, options); + const result = await sendAuthRequest(url, options); + debug('updateWorkspace RESULT', result); + if (!result.ok) throw result; + return new Workspace(await result.json()); }, }; + +export const getUserWorkspacesRequest = new CachedRequest(workspaceApi, 'getUserWorkspaces'); +export const createWorkspaceRequest = new Request(workspaceApi, 'createWorkspace'); +export const deleteWorkspaceRequest = new Request(workspaceApi, 'deleteWorkspace'); +export const updateWorkspaceRequest = new Request(workspaceApi, 'updateWorkspace'); diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js index c18eb0e11..6dc779be9 100644 --- a/src/features/workspaces/components/WorkspaceDrawer.js +++ b/src/features/workspaces/components/WorkspaceDrawer.js @@ -6,9 +6,9 @@ import { defineMessages, intlShape } from 'react-intl'; import { H1, Icon } from '@meetfranz/ui'; import ReactTooltip from 'react-tooltip'; -import { workspacesState } from '../state'; import WorkspaceDrawerItem from './WorkspaceDrawerItem'; import { workspaceActions } from '../actions'; +import { workspaceStore } from '../index'; const messages = defineMessages({ headline: { @@ -70,7 +70,12 @@ class WorkspaceDrawer extends Component { getServicesForWorkspace, } = this.props; const { intl } = this.context; - const { activeWorkspace, isSwitchingWorkspace, nextWorkspace } = workspacesState; + const { + activeWorkspace, + isSwitchingWorkspace, + nextWorkspace, + workspaces, + } = workspaceStore; const actualWorkspace = isSwitchingWorkspace ? nextWorkspace : activeWorkspace; return (
@@ -95,7 +100,7 @@ class WorkspaceDrawer extends Component { services={getServicesForWorkspace(null)} isActive={actualWorkspace == null} /> - {workspacesState.workspaces.map(workspace => ( + {workspaces.map(workspace => ( { - const { workspaceBeingEdited } = workspacesState; + const { workspaceBeingEdited } = workspaceStore; const { actions } = this.props; if (!workspaceBeingEdited) return null; actions.workspaces.delete({ workspace: workspaceBeingEdited }); }; onSave = (values) => { - const { workspaceBeingEdited } = workspacesState; + const { workspaceBeingEdited } = workspaceStore; const { actions } = this.props; const workspace = new Workspace( Object.assign({}, workspaceBeingEdited, values), @@ -38,7 +38,7 @@ class EditWorkspaceScreen extends Component { }; render() { - const { workspaceBeingEdited } = workspacesState; + const { workspaceBeingEdited } = workspaceStore; const { stores } = this.props; if (!workspaceBeingEdited) return null; return ( diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js index bd1ddcd43..5fdea217e 100644 --- a/src/features/workspaces/containers/WorkspacesScreen.js +++ b/src/features/workspaces/containers/WorkspacesScreen.js @@ -1,9 +1,10 @@ import React, { Component } from 'react'; import { inject, observer } from 'mobx-react'; import PropTypes from 'prop-types'; -import { workspacesState } from '../state'; import WorkspacesDashboard from '../components/WorkspacesDashboard'; import ErrorBoundary from '../../../components/util/ErrorBoundary'; +import { workspaceStore } from '../index'; +import { getUserWorkspacesRequest } from '../api'; @inject('actions') @observer class WorkspacesScreen extends Component { @@ -20,8 +21,8 @@ class WorkspacesScreen extends Component { return ( actions.workspaces.create(data)} onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })} /> diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js index 1644c0e2f..68f82bdee 100644 --- a/src/features/workspaces/index.js +++ b/src/features/workspaces/index.js @@ -1,26 +1,9 @@ -import { reaction, runInAction } from 'mobx'; +import { reaction } from 'mobx'; import WorkspacesStore from './store'; -import api from './api'; -import { workspacesState, resetState } from './state'; const debug = require('debug')('Franz:feature:workspaces'); -let store = null; - -export const filterServicesByActiveWorkspace = (services) => { - const { - activeWorkspace, - isFeatureActive, - } = workspacesState; - - if (!isFeatureActive) return services; - if (activeWorkspace) return services.filter(s => activeWorkspace.services.includes(s.id)); - return services; -}; - -export const getActiveWorkspaceServices = services => ( - filterServicesByActiveWorkspace(services) -); +export const workspaceStore = new WorkspacesStore(); export default function initWorkspaces(stores, actions) { const { features, user } = stores; @@ -33,38 +16,16 @@ export default function initWorkspaces(stores, actions) { ) ), (isEnabled) => { - if (isEnabled) { + if (isEnabled && !workspaceStore.isFeatureActive) { debug('Initializing `workspaces` feature'); - store = new WorkspacesStore(stores, api, actions, workspacesState); - store.initialize(); - runInAction(() => { workspacesState.isFeatureActive = true; }); - } else if (store) { + workspaceStore.start(stores, actions); + } else if (workspaceStore.isFeatureActive) { debug('Disabling `workspaces` feature'); - runInAction(() => { workspacesState.isFeatureActive = false; }); - store.teardown(); - store = null; - resetState(); // Reset state to default + workspaceStore.stop(); } }, { fireImmediately: true, }, ); - - // Update active service on workspace switches - reaction(() => ({ - isFeatureActive: workspacesState.isFeatureActive, - activeWorkspace: workspacesState.activeWorkspace, - }), ({ isFeatureActive, activeWorkspace }) => { - if (!isFeatureActive) return; - if (activeWorkspace) { - const services = stores.services.allDisplayed; - const activeService = services.find(s => s.isActive); - const workspaceServices = filterServicesByActiveWorkspace(services); - const isActiveServiceInWorkspace = workspaceServices.includes(activeService); - if (!isActiveServiceInWorkspace) { - actions.service.setActive({ serviceId: workspaceServices[0].id }); - } - } - }); } diff --git a/src/features/workspaces/state.js b/src/features/workspaces/state.js deleted file mode 100644 index c916480c0..000000000 --- a/src/features/workspaces/state.js +++ /dev/null @@ -1,18 +0,0 @@ -import { observable } from 'mobx'; - -const defaultState = { - activeWorkspace: null, - nextWorkspace: null, - isLoadingWorkspaces: false, - isFeatureActive: false, - isSwitchingWorkspace: false, - isWorkspaceDrawerOpen: false, - workspaces: [], - workspaceBeingEdited: null, -}; - -export const workspacesState = observable(defaultState); - -export function resetState() { - Object.assign(workspacesState, defaultState); -} diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index f6b9b2ff4..883f36ffb 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -1,54 +1,39 @@ -import { observable, reaction, action } from 'mobx'; -import Store from '../../stores/lib/Store'; -import CachedRequest from '../../stores/lib/CachedRequest'; -import Workspace from './models/Workspace'; +import { + computed, + observable, + action, +} from 'mobx'; +import Reaction from '../../stores/lib/Reaction'; import { matchRoute } from '../../helpers/routing-helpers'; import { workspaceActions } from './actions'; +import { + createWorkspaceRequest, + deleteWorkspaceRequest, + getUserWorkspacesRequest, + updateWorkspaceRequest, +} from './api'; -const debug = require('debug')('Franz:feature:workspaces'); +const debug = require('debug')('Franz:feature:workspaces:store'); -export default class WorkspacesStore extends Store { - @observable allWorkspacesRequest = new CachedRequest(this.api, 'getUserWorkspaces'); +export default class WorkspacesStore { + @observable isFeatureActive = false; - constructor(stores, api, actions, state) { - super(stores, api, actions); - this.state = state; - } + @observable activeWorkspace = null; + + @observable nextWorkspace = null; + + @observable workspaceBeingEdited = null; + + @observable isSwitchingWorkspace = false; - setup() { - debug('fetching workspaces'); - this.allWorkspacesRequest.execute(); - - /** - * Update the state workspaces array when workspaces request has results. - */ - reaction( - () => this.allWorkspacesRequest.result, - workspaces => this._setWorkspaces(workspaces), - ); - /** - * Update the loading state when workspace request is executing. - */ - reaction( - () => this.allWorkspacesRequest.isExecuting, - isExecuting => this._setIsLoadingWorkspaces(isExecuting), - ); - /** - * Update the state with the workspace to be edited when route matches. - */ - reaction( - () => ({ - pathname: this.stores.router.location.pathname, - workspaces: this.state.workspaces, - }), - ({ pathname }) => { - const match = matchRoute('/settings/workspaces/edit/:id', pathname); - if (match) { - this.state.workspaceBeingEdited = this._getWorkspaceById(match.id); - } - }, - ); + @observable isWorkspaceDrawerOpen = false; + @computed get workspaces() { + return getUserWorkspacesRequest.execute().result || []; + } + + constructor() { + // Wire-up action handlers workspaceActions.edit.listen(this._edit); workspaceActions.create.listen(this._create); workspaceActions.delete.listen(this._delete); @@ -57,28 +42,62 @@ export default class WorkspacesStore extends Store { workspaceActions.deactivate.listen(this._deactivateActiveWorkspace); workspaceActions.toggleWorkspaceDrawer.listen(this._toggleWorkspaceDrawer); workspaceActions.openWorkspaceSettings.listen(this._openWorkspaceSettings); + + // Register and start reactions + this._registerReactions([ + this._updateWorkspaceBeingEdited, + this._updateActiveServiceOnWorkspaceSwitch, + ]); } - _getWorkspaceById = id => this.state.workspaces.find(w => w.id === id); + start(stores, actions) { + debug('WorkspacesStore::start'); + this.stores = stores; + this.actions = actions; + this._reactions.forEach(r => r.start()); + this.isFeatureActive = true; + } - @action _setWorkspaces = (workspaces) => { - debug('setting user workspaces', workspaces.slice()); - this.state.workspaces = workspaces.map(data => new Workspace(data)); - }; + stop() { + debug('WorkspacesStore::stop'); + this._reactions.forEach(r => r.stop()); + this.isFeatureActive = false; + } - @action _setIsLoadingWorkspaces = (isLoading) => { - this.state.isLoadingWorkspaces = isLoading; + filterServicesByActiveWorkspace = (services) => { + const { activeWorkspace, isFeatureActive } = this; + + if (!isFeatureActive) return services; + if (activeWorkspace) { + return services.filter(s => ( + activeWorkspace.services.includes(s.id) + )); + } + return services; }; + // ========== PRIVATE ========= // + + _reactions = []; + + _registerReactions(reactions) { + reactions.forEach(r => this._reactions.push(new Reaction(r))); + } + + _getWorkspaceById = id => this.workspaces.find(w => w.id === id); + + // Actions + @action _edit = ({ workspace }) => { this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`); }; @action _create = async ({ name }) => { try { - const result = await this.api.createWorkspace(name); - const workspace = new Workspace(result); - this.state.workspaces.push(workspace); + const workspace = await createWorkspaceRequest.execute(name); + await getUserWorkspacesRequest.patch((result) => { + result.push(workspace); + }); this._edit({ workspace }); } catch (error) { throw error; @@ -87,8 +106,10 @@ export default class WorkspacesStore extends Store { @action _delete = async ({ workspace }) => { try { - await this.api.deleteWorkspace(workspace); - this.state.workspaces.remove(workspace); + await deleteWorkspaceRequest.execute(workspace); + await getUserWorkspacesRequest.patch((result) => { + result.remove(workspace); + }); this.stores.router.push('/settings/workspaces'); } catch (error) { throw error; @@ -97,9 +118,11 @@ export default class WorkspacesStore extends Store { @action _update = async ({ workspace }) => { try { - await this.api.updateWorkspace(workspace); - const localWorkspace = this.state.workspaces.find(ws => ws.id === workspace.id); - Object.assign(localWorkspace, workspace); + await updateWorkspaceRequest.execute(workspace); + await getUserWorkspacesRequest.patch((result) => { + const localWorkspace = result.find(ws => ws.id === workspace.id); + Object.assign(localWorkspace, workspace); + }); this.stores.router.push('/settings/workspaces'); } catch (error) { throw error; @@ -107,33 +130,56 @@ export default class WorkspacesStore extends Store { }; @action _setActiveWorkspace = ({ workspace }) => { - Object.assign(this.state, { - isSwitchingWorkspace: true, - nextWorkspace: workspace, - }); - setTimeout(() => { this.state.activeWorkspace = workspace; }, 100); + // Indicate that we are switching to another workspace + this.isSwitchingWorkspace = true; + this.nextWorkspace = workspace; + // Delay switching to next workspace so that the services loading does not drag down UI + setTimeout(() => { this.activeWorkspace = workspace; }, 100); + // Indicate that we are done switching to the next workspace setTimeout(() => { - Object.assign(this.state, { - isSwitchingWorkspace: false, - nextWorkspace: null, - }); + this.isSwitchingWorkspace = false; + this.nextWorkspace = null; }, 1000); }; @action _deactivateActiveWorkspace = () => { - Object.assign(this.state, { - isSwitchingWorkspace: true, - nextWorkspace: null, - }); - setTimeout(() => { this.state.activeWorkspace = null; }, 100); - setTimeout(() => { this.state.isSwitchingWorkspace = false; }, 1000); + // Indicate that we are switching to default workspace + this.isSwitchingWorkspace = true; + this.nextWorkspace = null; + // Delay switching to next workspace so that the services loading does not drag down UI + setTimeout(() => { this.activeWorkspace = null; }, 100); + // Indicate that we are done switching to the default workspace + setTimeout(() => { this.isSwitchingWorkspace = false; }, 1000); }; @action _toggleWorkspaceDrawer = () => { - this.state.isWorkspaceDrawerOpen = !this.state.isWorkspaceDrawerOpen; + this.isWorkspaceDrawerOpen = !this.isWorkspaceDrawerOpen; }; @action _openWorkspaceSettings = () => { this.actions.ui.openSettings({ path: 'workspaces' }); }; + + // Reactions + + _updateWorkspaceBeingEdited = () => { + const { pathname } = this.stores.router.location; + const match = matchRoute('/settings/workspaces/edit/:id', pathname); + if (match) { + this.workspaceBeingEdited = this._getWorkspaceById(match.id); + } + }; + + _updateActiveServiceOnWorkspaceSwitch = () => { + if (!this.isFeatureActive) return; + if (this.activeWorkspace) { + const services = this.stores.services.allDisplayed; + const activeService = services.find(s => s.isActive); + const workspaceServices = this.filterServicesByActiveWorkspace(services); + const isActiveServiceInWorkspace = workspaceServices.includes(activeService); + if (!isActiveServiceInWorkspace) { + this.actions.service.setActive({ serviceId: workspaceServices[0].id }); + } + } + }; } diff --git a/src/lib/Menu.js b/src/lib/Menu.js index 2a88515f4..3435e04f7 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js @@ -3,7 +3,7 @@ import { observable, autorun } from 'mobx'; import { defineMessages } from 'react-intl'; import { isMac, ctrlKey, cmdKey } from '../environment'; -import { workspacesState } from '../features/workspaces/state'; +import { workspaceStore } from '../features/workspaces/index'; import { workspaceActions } from '../features/workspaces/actions'; const { app, Menu, dialog } = remote; @@ -784,7 +784,7 @@ export default class FranzMenu { } workspacesMenu() { - const { workspaces, activeWorkspace } = workspacesState; + const { workspaces, activeWorkspace, isWorkspaceDrawerOpen } = workspaceStore; const { intl } = window.franz; const menu = []; @@ -800,7 +800,7 @@ export default class FranzMenu { // Open workspace drawer: const drawerLabel = ( - workspacesState.isWorkspaceDrawerOpen ? menuItems.closeWorkspaceDrawer : menuItems.openWorkspaceDrawer + isWorkspaceDrawerOpen ? menuItems.closeWorkspaceDrawer : menuItems.openWorkspaceDrawer ); menu.push({ label: intl.formatMessage(drawerLabel), diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index cc8eed65b..0ec6bf550 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -12,7 +12,7 @@ import Request from './lib/Request'; import CachedRequest from './lib/CachedRequest'; import { matchRoute } from '../helpers/routing-helpers'; import { gaEvent } from '../lib/analytics'; -import { filterServicesByActiveWorkspace, getActiveWorkspaceServices } from '../features/workspaces'; +import { workspaceStore } from '../features/workspaces'; const debug = require('debug')('Franz:ServiceStore'); @@ -109,7 +109,7 @@ export default class ServicesStore extends Store { @computed get allDisplayed() { const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; - return filterServicesByActiveWorkspace(services); + return workspaceStore.filterServicesByActiveWorkspace(services); } // This is just used to avoid unnecessary rerendering of resource-heavy webviews @@ -117,7 +117,7 @@ export default class ServicesStore extends Store { const { showDisabledServices } = this.stores.settings.all.app; const services = this.allServicesRequest.execute().result || []; const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled); - return getActiveWorkspaceServices(filteredServices); + return workspaceStore.filterServicesByActiveWorkspace(filteredServices); } @computed get filtered() { -- cgit v1.2.3-70-g09d2 From d05a8efffadd926165d516d6efd8c8b893648ebe Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Tue, 26 Mar 2019 13:47:54 +0100 Subject: hide workspace feature if it is disabled --- src/components/layout/Sidebar.js | 25 ++++++----- .../settings/navigation/SettingsNavigation.js | 21 +++++---- src/config.js | 2 +- src/features/workspaces/store.js | 5 +++ src/i18n/locales/defaultMessages.json | 52 +++++++++++----------- .../messages/src/components/layout/Sidebar.json | 24 +++++----- .../settings/navigation/SettingsNavigation.json | 28 ++++++------ src/lib/Menu.js | 6 ++- src/stores/FeaturesStore.js | 4 +- 9 files changed, 90 insertions(+), 77 deletions(-) (limited to 'src/stores') diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index de379875e..f7bacfe0f 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js @@ -6,6 +6,7 @@ import { observer } from 'mobx-react'; import Tabbar from '../services/tabs/Tabbar'; import { ctrlKey } from '../../environment'; +import { workspaceStore } from '../../features/workspaces'; const messages = defineMessages({ settings: { @@ -88,17 +89,19 @@ export default @observer class Sidebar extends Component { enableToolTip={() => this.enableToolTip()} disableToolTip={() => this.disableToolTip()} /> - + {workspaceStore.isFeatureActive ? ( + + ) : null}
-
-
- -
- {getUserWorkspacesRequest.isExecuting ? ( - - ) : ( - - {getUserWorkspacesRequest.error ? ( - - {intl.formatMessage(messages.workspacesRequestFailed)} - - ) : ( - - - {workspaces.map(workspace => ( - onWorkspaceClick(w)} - /> - ))} - -
- )} -
- )} + {updateWorkspaceRequest.wasExecuted && updateWorkspaceRequest.result && ( + + + {intl.formatMessage(messages.updatedInfo)} + + + )} +
+
+ {getUserWorkspacesRequest.isExecuting ? ( + + ) : ( + + {getUserWorkspacesRequest.error ? ( + + {intl.formatMessage(messages.workspacesRequestFailed)} + + ) : ( + + + {workspaces.map(workspace => ( + onWorkspaceClick(w)} + /> + ))} + +
+ )} +
+ )}
); diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js index 89bd2a2ef..3f41de0c2 100644 --- a/src/features/workspaces/containers/WorkspacesScreen.js +++ b/src/features/workspaces/containers/WorkspacesScreen.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import WorkspacesDashboard from '../components/WorkspacesDashboard'; import ErrorBoundary from '../../../components/util/ErrorBoundary'; import { workspaceStore } from '../index'; -import { getUserWorkspacesRequest } from '../api'; +import { getUserWorkspacesRequest, updateWorkspaceRequest } from '../api'; @inject('actions') @observer class WorkspacesScreen extends Component { @@ -23,6 +23,7 @@ class WorkspacesScreen extends Component { actions.workspaces.create(data)} onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })} /> diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index 3cec5f360..f7df7b29c 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -102,9 +102,7 @@ export default class WorkspacesStore { @action _create = async ({ name }) => { try { const workspace = await createWorkspaceRequest.execute(name); - await getUserWorkspacesRequest.patch((result) => { - result.push(workspace); - }); + await getUserWorkspacesRequest.result.push(workspace); this._edit({ workspace }); } catch (error) { throw error; @@ -114,9 +112,7 @@ export default class WorkspacesStore { @action _delete = async ({ workspace }) => { try { await deleteWorkspaceRequest.execute(workspace); - await getUserWorkspacesRequest.patch((result) => { - result.remove(workspace); - }); + await getUserWorkspacesRequest.result.remove(workspace); this.stores.router.push('/settings/workspaces'); } catch (error) { throw error; @@ -126,10 +122,9 @@ export default class WorkspacesStore { @action _update = async ({ workspace }) => { try { await updateWorkspaceRequest.execute(workspace); - await getUserWorkspacesRequest.patch((result) => { - const localWorkspace = result.find(ws => ws.id === workspace.id); - Object.assign(localWorkspace, workspace); - }); + // Path local result optimistically + const localWorkspace = this._getWorkspaceById(workspace.id); + Object.assign(localWorkspace, workspace); this.stores.router.push('/settings/workspaces'); } catch (error) { throw error; diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index afbacf28a..891ad38d4 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -3388,52 +3388,65 @@ "defaultMessage": "!!!Your workspaces", "end": { "column": 3, - "line": 17 + "line": 18 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.headline", "start": { "column": 12, - "line": 14 + "line": 15 } }, { "defaultMessage": "!!!You haven't added any workspaces yet.", "end": { "column": 3, - "line": 21 + "line": 22 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.noWorkspacesAdded", "start": { "column": 19, - "line": 18 + "line": 19 } }, { "defaultMessage": "!!!Could not load your workspaces", "end": { "column": 3, - "line": 25 + "line": 26 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.workspacesRequestFailed", "start": { "column": 27, - "line": 22 + "line": 23 } }, { "defaultMessage": "!!!Try again", "end": { "column": 3, - "line": 29 + "line": 30 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.tryReloadWorkspaces", "start": { "column": 23, - "line": 26 + "line": 27 + } + }, + { + "defaultMessage": "!!!Your changes have been saved", + "end": { + "column": 3, + "line": 34 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.updatedInfo", + "start": { + "column": 15, + "line": 31 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 2b4e79621..ad179bc1d 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -252,6 +252,7 @@ "settings.workspaces.headline": "Your workspaces", "settings.workspaces.noWorkspacesAdded": "You haven't added any workspaces yet.", "settings.workspaces.tryReloadWorkspaces": "Try again", + "settings.workspaces.updatedInfo": "!!!Your changes have been saved", "settings.workspaces.workspacesRequestFailed": "Could not load your workspaces", "sidebar.addNewService": "Add new service", "sidebar.closeWorkspaceDrawer": "Close workspace drawer", @@ -307,4 +308,4 @@ "workspaceDrawer.headline": "Workspaces", "workspaceDrawer.item.noServicesAddedYet": "No services added yet", "workspaces.switchingIndicator.switchingTo": "Switching to" -} +} \ No newline at end of file diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json index f875ace8a..d68899d9b 100644 --- a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json +++ b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Your workspaces", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 14, + "line": 15, "column": 12 }, "end": { - "line": 17, + "line": 18, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!You haven't added any workspaces yet.", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 18, + "line": 19, "column": 19 }, "end": { - "line": 21, + "line": 22, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Could not load your workspaces", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 22, + "line": 23, "column": 27 }, "end": { - "line": 25, + "line": 26, "column": 3 } }, @@ -43,11 +43,24 @@ "defaultMessage": "!!!Try again", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 26, + "line": 27, "column": 23 }, "end": { - "line": 29, + "line": 30, + "column": 3 + } + }, + { + "id": "settings.workspaces.updatedInfo", + "defaultMessage": "!!!Your changes have been saved", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 31, + "column": 15 + }, + "end": { + "line": 34, "column": 3 } } diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js index 1fb67cc15..486de8a49 100644 --- a/src/stores/lib/Request.js +++ b/src/stores/lib/Request.js @@ -109,7 +109,7 @@ export default class Request { Request._hooks.forEach(hook => hook(this)); } - reset() { + reset = () => { this.result = null; this.isExecuting = false; this.isError = false; @@ -118,5 +118,5 @@ export default class Request { this._promise = Promise; return this; - } + }; } -- cgit v1.2.3-70-g09d2 From 7941831bf773b49944001c095a1949a1bdec2cf2 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Thu, 28 Mar 2019 16:23:17 +0100 Subject: add workspace premium notice to dashboard --- src/actions/lib/actions.js | 4 ++ src/components/layout/Sidebar.js | 2 +- .../settings/navigation/SettingsNavigation.js | 2 +- .../settings/services/EditServiceForm.js | 10 +++- .../settings/settings/EditSettingsForm.js | 1 + src/components/ui/PremiumFeatureContainer/index.js | 21 ++++++- .../ui/PremiumFeatureContainer/styles.js | 3 +- src/features/delayApp/index.js | 2 +- src/features/utils/FeatureStore.js | 21 +++++++ .../workspaces/components/CreateWorkspaceForm.js | 1 - .../workspaces/components/WorkspacesDashboard.js | 42 ++++++++++--- src/features/workspaces/index.js | 3 +- src/features/workspaces/store.js | 69 +++++++++++++--------- src/i18n/locales/defaultMessages.json | 54 ++++++++++++----- src/i18n/locales/en-US.json | 2 + .../ui/PremiumFeatureContainer/index.json | 4 +- .../workspaces/components/WorkspacesDashboard.json | 50 ++++++++++++---- src/lib/Menu.js | 4 +- src/stores/FeaturesStore.js | 18 ++++-- 19 files changed, 230 insertions(+), 83 deletions(-) create mode 100644 src/features/utils/FeatureStore.js (limited to 'src/stores') diff --git a/src/actions/lib/actions.js b/src/actions/lib/actions.js index 6571e9441..2bc7d2711 100644 --- a/src/actions/lib/actions.js +++ b/src/actions/lib/actions.js @@ -9,6 +9,10 @@ export const createActionsFromDefinitions = (actionDefinitions, validate) => { actions[actionName] = action; action.listeners = []; action.listen = listener => action.listeners.push(listener); + action.off = (listener) => { + const { listeners } = action; + listeners.splice(listeners.indexOf(listener), 1); + }; action.notify = params => action.listeners.forEach(listener => listener(params)); }); return actions; diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js index 4fa5e79de..327f76392 100644 --- a/src/components/layout/Sidebar.js +++ b/src/components/layout/Sidebar.js @@ -90,7 +90,7 @@ export default @observer class Sidebar extends Component { enableToolTip={() => this.enableToolTip()} disableToolTip={() => this.disableToolTip()} /> - {workspaceStore.isFeatureActive ? ( + {workspaceStore.isFeatureEnabled ? ( @@ -73,3 +88,5 @@ PremiumFeatureContainer.wrappedComponent.propTypes = { }).isRequired, }).isRequired, }; + +export default PremiumFeatureContainer; diff --git a/src/components/ui/PremiumFeatureContainer/styles.js b/src/components/ui/PremiumFeatureContainer/styles.js index 81d6666c6..615ed0a79 100644 --- a/src/components/ui/PremiumFeatureContainer/styles.js +++ b/src/components/ui/PremiumFeatureContainer/styles.js @@ -6,6 +6,7 @@ export default theme => ({ padding: 20, 'border-radius': theme.borderRadius, pointerEvents: 'none', + height: 'auto', }, titleContainer: { display: 'flex', @@ -26,7 +27,7 @@ export default theme => ({ content: { opacity: 0.5, 'margin-top': 20, - '& :last-child': { + '& > :last-child': { 'margin-bottom': 0, }, }, diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js index abc8274cf..67f0fc5e6 100644 --- a/src/features/delayApp/index.js +++ b/src/features/delayApp/index.js @@ -55,7 +55,7 @@ export default function init(stores) { setVisibility(true); gaPage('/delayApp'); - gaEvent('delayApp', 'show', 'Delay App Feature'); + gaEvent('DelayApp', 'show', 'Delay App Feature'); timeLastDelay = moment(); shownAfterLaunch = true; diff --git a/src/features/utils/FeatureStore.js b/src/features/utils/FeatureStore.js new file mode 100644 index 000000000..66b66a104 --- /dev/null +++ b/src/features/utils/FeatureStore.js @@ -0,0 +1,21 @@ +import Reaction from '../../stores/lib/Reaction'; + +export class FeatureStore { + _actions = null; + + _reactions = null; + + _listenToActions(actions) { + if (this._actions) this._actions.forEach(a => a[0].off(a[1])); + this._actions = []; + actions.forEach(a => this._actions.push(a)); + this._actions.forEach(a => a[0].listen(a[1])); + } + + _startReactions(reactions) { + if (this._reactions) this._reactions.forEach(r => r.stop()); + this._reactions = []; + reactions.forEach(r => this._reactions.push(new Reaction(r))); + this._reactions.forEach(r => r.start()); + } +} diff --git a/src/features/workspaces/components/CreateWorkspaceForm.js b/src/features/workspaces/components/CreateWorkspaceForm.js index a8f07d0d5..0be2d528f 100644 --- a/src/features/workspaces/components/CreateWorkspaceForm.js +++ b/src/features/workspaces/components/CreateWorkspaceForm.js @@ -30,7 +30,6 @@ const styles = () => ({ }, submitButton: { height: 'inherit', - marginTop: '3px', }, }); diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js index 52c3afdcf..1fad1f71d 100644 --- a/src/features/workspaces/components/WorkspacesDashboard.js +++ b/src/features/workspaces/components/WorkspacesDashboard.js @@ -10,6 +10,8 @@ import WorkspaceItem from './WorkspaceItem'; import CreateWorkspaceForm from './CreateWorkspaceForm'; import Request from '../../../stores/lib/Request'; import Appear from '../../../components/ui/effects/Appear'; +import { workspaceStore } from '../index'; +import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer'; const messages = defineMessages({ headline: { @@ -36,6 +38,14 @@ const messages = defineMessages({ id: 'settings.workspaces.deletedInfo', defaultMessage: '!!!Workspace has been deleted', }, + workspaceFeatureInfo: { + id: 'settings.workspaces.workspaceFeatureInfo', + defaultMessage: '!!!Info about workspace feature', + }, + workspaceFeatureHeadline: { + id: 'settings.workspaces.workspaceFeatureHeadline', + defaultMessage: '!!!Less is More: Introducing Franz Workspaces', + }, }); const styles = () => ({ @@ -46,6 +56,12 @@ const styles = () => ({ appear: { height: 'auto', }, + premiumAnnouncement: { + padding: '20px', + backgroundColor: '#3498db', + marginLeft: '-20px', + height: 'auto', + }, }); @injectSheet(styles) @observer @@ -112,14 +128,24 @@ class WorkspacesDashboard extends Component { )} - {/* ===== Create workspace form ===== */} -
- -
- + + {/* ===== Create workspace form ===== */} +
+ +
+
+ {workspaceStore.isUpgradeToPremiumRequired && ( +
+

{intl.formatMessage(messages.workspaceFeatureHeadline)}

+

{intl.formatMessage(messages.workspaceFeatureInfo)}

+
+ )} {getUserWorkspacesRequest.isExecuting ? ( ) : ( diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js index 89999ab0f..524a83e3c 100644 --- a/src/features/workspaces/index.js +++ b/src/features/workspaces/index.js @@ -4,11 +4,12 @@ import { resetApiRequests } from './api'; const debug = require('debug')('Franz:feature:workspaces'); -export const GA_CATEGORY_WORKSPACES = 'workspaces'; +export const GA_CATEGORY_WORKSPACES = 'Workspaces'; export const workspaceStore = new WorkspacesStore(); export default function initWorkspaces(stores, actions) { + stores.workspaces = workspaceStore; const { features, user } = stores; // Toggle workspace feature diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index f7df7b29c..62bf3efb4 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -3,9 +3,9 @@ import { observable, action, } from 'mobx'; -import Reaction from '../../stores/lib/Reaction'; import { matchRoute } from '../../helpers/routing-helpers'; import { workspaceActions } from './actions'; +import { FeatureStore } from '../utils/FeatureStore'; import { createWorkspaceRequest, deleteWorkspaceRequest, @@ -15,7 +15,11 @@ import { const debug = require('debug')('Franz:feature:workspaces:store'); -export default class WorkspacesStore { +export default class WorkspacesStore extends FeatureStore { + @observable isFeatureEnabled = false; + + @observable isPremiumFeature = true; + @observable isFeatureActive = false; @observable activeWorkspace = null; @@ -33,36 +37,39 @@ export default class WorkspacesStore { return getUserWorkspacesRequest.result || []; } - constructor() { - // Wire-up action handlers - workspaceActions.edit.listen(this._edit); - workspaceActions.create.listen(this._create); - workspaceActions.delete.listen(this._delete); - workspaceActions.update.listen(this._update); - workspaceActions.activate.listen(this._setActiveWorkspace); - workspaceActions.deactivate.listen(this._deactivateActiveWorkspace); - workspaceActions.toggleWorkspaceDrawer.listen(this._toggleWorkspaceDrawer); - workspaceActions.openWorkspaceSettings.listen(this._openWorkspaceSettings); - - // Register and start reactions - this._registerReactions([ - this._updateWorkspaceBeingEdited, - this._updateActiveServiceOnWorkspaceSwitch, - ]); + @computed get isUpgradeToPremiumRequired() { + return this.isFeatureEnabled && !this.isFeatureActive; } start(stores, actions) { debug('WorkspacesStore::start'); this.stores = stores; this.actions = actions; - this._reactions.forEach(r => r.start()); - this.isFeatureActive = true; + + this._listenToActions([ + [workspaceActions.edit, this._edit], + [workspaceActions.create, this._create], + [workspaceActions.delete, this._delete], + [workspaceActions.update, this._update], + [workspaceActions.activate, this._setActiveWorkspace], + [workspaceActions.deactivate, this._deactivateActiveWorkspace], + [workspaceActions.toggleWorkspaceDrawer, this._toggleWorkspaceDrawer], + [workspaceActions.openWorkspaceSettings, this._openWorkspaceSettings], + ]); + + this._startReactions([ + this._setWorkspaceBeingEditedReaction, + this._setActiveServiceOnWorkspaceSwitchReaction, + this._setFeatureEnabledReaction, + this._setIsPremiumFeatureReaction, + ]); + getUserWorkspacesRequest.execute(); + this.isFeatureActive = true; } stop() { debug('WorkspacesStore::stop'); - this._reactions.forEach(r => r.stop()); this.isFeatureActive = false; this.activeWorkspace = null; this.nextWorkspace = null; @@ -85,12 +92,6 @@ export default class WorkspacesStore { // ========== PRIVATE ========= // - _reactions = []; - - _registerReactions(reactions) { - reactions.forEach(r => this._reactions.push(new Reaction(r))); - } - _getWorkspaceById = id => this.workspaces.find(w => w.id === id); // Actions @@ -164,7 +165,17 @@ export default class WorkspacesStore { // Reactions - _updateWorkspaceBeingEdited = () => { + _setFeatureEnabledReaction = () => { + const { isWorkspaceEnabled } = this.stores.features.features; + this.isFeatureEnabled = isWorkspaceEnabled; + }; + + _setIsPremiumFeatureReaction = () => { + const { isWorkspacePremiumFeature } = this.stores.features.features; + this.isPremiumFeature = isWorkspacePremiumFeature; + }; + + _setWorkspaceBeingEditedReaction = () => { const { pathname } = this.stores.router.location; const match = matchRoute('/settings/workspaces/edit/:id', pathname); if (match) { @@ -172,7 +183,7 @@ export default class WorkspacesStore { } }; - _updateActiveServiceOnWorkspaceSwitch = () => { + _setActiveServiceOnWorkspaceSwitchReaction = () => { if (!this.isFeatureActive) return; if (this.activeWorkspace) { const services = this.stores.services.allDisplayed; diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 659b1b361..1747e1976 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -2535,13 +2535,13 @@ "defaultMessage": "!!!Upgrade account", "end": { "column": 3, - "line": 17 + "line": 18 }, "file": "src/components/ui/PremiumFeatureContainer/index.js", "id": "premiumFeature.button.upgradeAccount", "start": { "column": 10, - "line": 14 + "line": 15 } } ], @@ -3388,78 +3388,104 @@ "defaultMessage": "!!!Your workspaces", "end": { "column": 3, - "line": 18 + "line": 20 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.headline", "start": { "column": 12, - "line": 15 + "line": 17 } }, { "defaultMessage": "!!!You haven't added any workspaces yet.", "end": { "column": 3, - "line": 22 + "line": 24 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.noWorkspacesAdded", "start": { "column": 19, - "line": 19 + "line": 21 } }, { "defaultMessage": "!!!Could not load your workspaces", "end": { "column": 3, - "line": 26 + "line": 28 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.workspacesRequestFailed", "start": { "column": 27, - "line": 23 + "line": 25 } }, { "defaultMessage": "!!!Try again", "end": { "column": 3, - "line": 30 + "line": 32 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.tryReloadWorkspaces", "start": { "column": 23, - "line": 27 + "line": 29 } }, { "defaultMessage": "!!!Your changes have been saved", "end": { "column": 3, - "line": 34 + "line": 36 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.updatedInfo", "start": { "column": 15, - "line": 31 + "line": 33 } }, { "defaultMessage": "!!!Workspace has been deleted", "end": { "column": 3, - "line": 38 + "line": 40 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.deletedInfo", "start": { "column": 15, - "line": 35 + "line": 37 + } + }, + { + "defaultMessage": "!!!Info about workspace feature", + "end": { + "column": 3, + "line": 44 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.workspaceFeatureInfo", + "start": { + "column": 24, + "line": 41 + } + }, + { + "defaultMessage": "!!!Less is More: Introducing Franz Workspaces", + "end": { + "column": 3, + "line": 48 + }, + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "id": "settings.workspaces.workspaceFeatureHeadline", + "start": { + "column": 28, + "line": 45 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 5f7254317..987262c35 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -254,6 +254,8 @@ "settings.workspaces.noWorkspacesAdded": "You haven't added any workspaces yet.", "settings.workspaces.tryReloadWorkspaces": "Try again", "settings.workspaces.updatedInfo": "Your changes have been saved", + "settings.workspaces.workspaceFeatureHeadline": "Less is More: Introducing Franz Workspaces", + "settings.workspaces.workspaceFeatureInfo": "Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time. You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.", "settings.workspaces.workspacesRequestFailed": "Could not load your workspaces", "sidebar.addNewService": "Add new service", "sidebar.closeWorkspaceDrawer": "Close workspace drawer", diff --git a/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json b/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json index 582d546fa..320d3ca3e 100644 --- a/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json +++ b/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Upgrade account", "file": "src/components/ui/PremiumFeatureContainer/index.js", "start": { - "line": 14, + "line": 15, "column": 10 }, "end": { - "line": 17, + "line": 18, "column": 3 } } diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json index a957358c8..ef8f1bebc 100644 --- a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json +++ b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Your workspaces", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 15, + "line": 17, "column": 12 }, "end": { - "line": 18, + "line": 20, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!You haven't added any workspaces yet.", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 19, + "line": 21, "column": 19 }, "end": { - "line": 22, + "line": 24, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Could not load your workspaces", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 23, + "line": 25, "column": 27 }, "end": { - "line": 26, + "line": 28, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Try again", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 27, + "line": 29, "column": 23 }, "end": { - "line": 30, + "line": 32, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Your changes have been saved", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 31, + "line": 33, "column": 15 }, "end": { - "line": 34, + "line": 36, "column": 3 } }, @@ -69,11 +69,37 @@ "defaultMessage": "!!!Workspace has been deleted", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 35, + "line": 37, "column": 15 }, "end": { - "line": 38, + "line": 40, + "column": 3 + } + }, + { + "id": "settings.workspaces.workspaceFeatureInfo", + "defaultMessage": "!!!Info about workspace feature", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 41, + "column": 24 + }, + "end": { + "line": 44, + "column": 3 + } + }, + { + "id": "settings.workspaces.workspaceFeatureHeadline", + "defaultMessage": "!!!Less is More: Introducing Franz Workspaces", + "file": "src/features/workspaces/components/WorkspacesDashboard.js", + "start": { + "line": 45, + "column": 28 + }, + "end": { + "line": 48, "column": 3 } } diff --git a/src/lib/Menu.js b/src/lib/Menu.js index d19aa9d6e..a4e41c17c 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js @@ -323,7 +323,7 @@ const _templateFactory = intl => [ { label: intl.formatMessage(menuItems.workspaces), submenu: [], - visible: workspaceStore.isFeatureActive, + visible: workspaceStore.isFeatureEnabled, }, { label: intl.formatMessage(menuItems.window), @@ -732,7 +732,7 @@ export default class FranzMenu { tpl[3].submenu = serviceTpl; } - if (workspaceStore.isFeatureActive) { + if (workspaceStore.isFeatureEnabled) { tpl[4].submenu = this.workspacesMenu(); } diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 2bda82e17..52e38ad96 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -1,4 +1,4 @@ -import { computed, observable, reaction } from 'mobx'; +import { computed, observable, reaction, runInAction } from 'mobx'; import Store from './lib/Store'; import CachedRequest from './lib/CachedRequest'; @@ -17,8 +17,11 @@ 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), ]); @@ -37,13 +40,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 Object.assign({}, DEFAULT_FEATURES_CONFIG, this.featuresRequest.execute().result); + 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) { -- cgit v1.2.3-70-g09d2 From 6b38abc9011648a1f54b1ef3e3fb29d13750750c Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Wed, 3 Apr 2019 16:59:24 +0200 Subject: add workspace premium badge in settings nav --- .../settings/navigation/SettingsNavigation.js | 9 +++++- src/i18n/locales/defaultMessages.json | 32 +++++++++++----------- src/i18n/locales/en-US.json | 2 +- .../settings/navigation/SettingsNavigation.json | 28 +++++++++---------- src/stores/FeaturesStore.js | 7 ++++- src/styles/settings.scss | 10 +++++++ 6 files changed, 55 insertions(+), 33 deletions(-) (limited to 'src/stores') diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js index dc3c1d6f1..945285f5a 100644 --- a/src/components/settings/navigation/SettingsNavigation.js +++ b/src/components/settings/navigation/SettingsNavigation.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, intlShape } from 'react-intl'; import { inject, observer } from 'mobx-react'; +import { Icon } from '@meetfranz/ui'; import Link from '../../ui/Link'; import { workspaceStore } from '../../../features/workspaces'; @@ -77,7 +78,13 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp > {intl.formatMessage(messages.yourWorkspaces)} {' '} - {workspaceCount} + {workspaceStore.isPremiumUpgradeRequired ? ( + + + + ) : ( + {workspaceCount} + )} ) : null} Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.

You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.

", "workspaces.switchingIndicator.switchingTo": "Switching to" -} +} \ No newline at end of file diff --git a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json index 96a42aa80..de78a71cf 100644 --- a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json +++ b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Available services", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 10, + "line": 11, "column": 21 }, "end": { - "line": 13, + "line": 14, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Your services", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 14, + "line": 15, "column": 16 }, "end": { - "line": 17, + "line": 18, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Your workspaces", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 18, + "line": 19, "column": 18 }, "end": { - "line": 21, + "line": 22, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Account", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 22, + "line": 23, "column": 11 }, "end": { - "line": 25, + "line": 26, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Settings", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 26, + "line": 27, "column": 12 }, "end": { - "line": 29, + "line": 30, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Invite Friends", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 30, + "line": 31, "column": 17 }, "end": { - "line": 33, + "line": 34, "column": 3 } }, @@ -82,11 +82,11 @@ "defaultMessage": "!!!Logout", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 34, + "line": 35, "column": 10 }, "end": { - "line": 37, + "line": 38, "column": 3 } } diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 52e38ad96..8fe576813 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -1,4 +1,9 @@ -import { computed, observable, reaction, runInAction } from 'mobx'; +import { + computed, + observable, + reaction, + runInAction, +} from 'mobx'; import Store from './lib/Store'; import CachedRequest from './lib/CachedRequest'; diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 9fde9a7bf..d97d4ac2c 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss @@ -85,6 +85,11 @@ .badge { background: $dark-theme-gray-lighter; color: $dark-theme-gray-smoke; + + &.badge--pro { + background: $theme-brand-primary; + padding: 4px 6px 3px 7px; + } } &:hover { @@ -93,6 +98,11 @@ .badge { background: $dark-theme-gray-lighter; color: $dark-theme-gray-smoke; + + &.badge--pro { + background: $theme-brand-primary; + padding: 4px 6px 3px 7px; + } } } -- cgit v1.2.3-70-g09d2 From 073212bf046b9218f9e3129988b1b63fba5d685d Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Wed, 10 Apr 2019 16:12:38 +0200 Subject: added generic pro badge component for settings nav --- packages/ui/src/badge/ProBadge.tsx | 65 ++++++++++++++++++++++ packages/ui/src/index.ts | 1 + .../settings/navigation/SettingsNavigation.js | 13 +++-- src/features/workspaces/store.js | 12 ++-- src/i18n/locales/defaultMessages.json | 28 +++++----- src/i18n/locales/en-US.json | 2 +- .../settings/navigation/SettingsNavigation.json | 28 +++++----- src/stores/UIStore.js | 9 +-- src/styles/settings.scss | 26 --------- uidev/src/stories/badge.stories.tsx | 12 +++- 10 files changed, 125 insertions(+), 71 deletions(-) create mode 100644 packages/ui/src/badge/ProBadge.tsx (limited to 'src/stores') diff --git a/packages/ui/src/badge/ProBadge.tsx b/packages/ui/src/badge/ProBadge.tsx new file mode 100644 index 000000000..eb00e156d --- /dev/null +++ b/packages/ui/src/badge/ProBadge.tsx @@ -0,0 +1,65 @@ +import { Theme } from '@meetfranz/theme'; +import classnames from 'classnames'; +import React, { Component } from 'react'; +import injectStyle from 'react-jss'; + +import { Icon, Badge } from '../'; +import { IWithStyle } from '../typings/generic'; + +interface IProps extends IWithStyle { + badgeClasses?: string; + iconClasses?: string; + inverted?: boolean; +} + +const styles = (theme: Theme) => ({ + badge: { + height: 'auto', + padding: [4, 6, 2, 7], + borderRadius: theme.borderRadiusSmall, + }, + invertedBadge: { + background: theme.styleTypes.primary.contrast, + color: theme.styleTypes.primary.accent, + }, + icon: { + fill: theme.styleTypes.primary.contrast, + }, + invertedIcon: { + fill: theme.styleTypes.primary.accent, + }, +}); + +class ProBadgeComponent extends Component { + render() { + const { + classes, + badgeClasses, + iconClasses, + inverted, + } = this.props; + + return ( + + + + ); + } +} + +export const ProBadge = injectStyle(styles)(ProBadgeComponent); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 1eeec5144..666495ce9 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -3,3 +3,4 @@ export { Infobox } from './infobox'; export * from './headline'; export { Loader } from './loader'; export { Badge } from './badge'; +export { ProBadge } from './badge/ProBadge'; diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js index 1c51d50d6..993b0a44a 100644 --- a/src/components/settings/navigation/SettingsNavigation.js +++ b/src/components/settings/navigation/SettingsNavigation.js @@ -2,10 +2,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, intlShape } from 'react-intl'; import { inject, observer } from 'mobx-react'; -import { Icon } from '@meetfranz/ui'; +import { ProBadge } from '@meetfranz/ui'; import Link from '../../ui/Link'; import { workspaceStore } from '../../../features/workspaces'; +import UIStore from '../../../stores/UIStore'; const messages = defineMessages({ availableServices: { @@ -40,6 +41,9 @@ const messages = defineMessages({ export default @inject('stores') @observer class SettingsNavigation extends Component { static propTypes = { + stores: PropTypes.shape({ + ui: PropTypes.instanceOf(UIStore).isRequired, + }).isRequired, serviceCount: PropTypes.number.isRequired, workspaceCount: PropTypes.number.isRequired, }; @@ -49,7 +53,8 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp }; render() { - const { serviceCount, workspaceCount } = this.props; + const { serviceCount, workspaceCount, stores } = this.props; + const { isDarkThemeActive } = stores.ui; const { intl } = this.context; return ( @@ -79,9 +84,7 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp {intl.formatMessage(messages.yourWorkspaces)} {' '} {workspaceStore.isPremiumUpgradeRequired ? ( - - - + ) : ( {workspaceCount} )} diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index 2e1764f99..ba48022c2 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -36,6 +36,8 @@ export default class WorkspacesStore extends FeatureStore { @observable isWorkspaceDrawerOpen = false; + @observable isSettingsRouteActive = null; + @computed get workspaces() { if (!this.isFeatureActive) return []; return getUserWorkspacesRequest.result || []; @@ -104,8 +106,6 @@ export default class WorkspacesStore extends FeatureStore { _wasDrawerOpenBeforeSettingsRoute = null; - _isSettingsRouteActive = null; - _getWorkspaceById = id => this.workspaces.find(w => w.id === id); _updateSettings = (changes) => { @@ -239,17 +239,17 @@ export default class WorkspacesStore extends FeatureStore { _openDrawerWithSettingsReaction = () => { const { router } = this.stores; const isWorkspaceSettingsRoute = router.location.pathname.includes(WORKSPACES_ROUTES.ROOT); - const isSwitchingToSettingsRoute = !this._isSettingsRouteActive && isWorkspaceSettingsRoute; - const isLeavingSettingsRoute = !isWorkspaceSettingsRoute && this._isSettingsRouteActive; + const isSwitchingToSettingsRoute = !this.isSettingsRouteActive && isWorkspaceSettingsRoute; + const isLeavingSettingsRoute = !isWorkspaceSettingsRoute && this.isSettingsRouteActive; if (isSwitchingToSettingsRoute) { - this._isSettingsRouteActive = true; + this.isSettingsRouteActive = true; this._wasDrawerOpenBeforeSettingsRoute = this.isWorkspaceDrawerOpen; if (!this._wasDrawerOpenBeforeSettingsRoute) { workspaceActions.toggleWorkspaceDrawer(); } } else if (isLeavingSettingsRoute) { - this._isSettingsRouteActive = false; + this.isSettingsRouteActive = false; if (!this._wasDrawerOpenBeforeSettingsRoute && this.isWorkspaceDrawerOpen) { workspaceActions.toggleWorkspaceDrawer(); } diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index f882e6030..791c4dd53 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -1302,91 +1302,91 @@ "defaultMessage": "!!!Available services", "end": { "column": 3, - "line": 14 + "line": 15 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.availableServices", "start": { "column": 21, - "line": 11 + "line": 12 } }, { "defaultMessage": "!!!Your services", "end": { "column": 3, - "line": 18 + "line": 19 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.yourServices", "start": { "column": 16, - "line": 15 + "line": 16 } }, { "defaultMessage": "!!!Your workspaces", "end": { "column": 3, - "line": 22 + "line": 23 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.yourWorkspaces", "start": { "column": 18, - "line": 19 + "line": 20 } }, { "defaultMessage": "!!!Account", "end": { "column": 3, - "line": 26 + "line": 27 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.account", "start": { "column": 11, - "line": 23 + "line": 24 } }, { "defaultMessage": "!!!Settings", "end": { "column": 3, - "line": 30 + "line": 31 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.settings", "start": { "column": 12, - "line": 27 + "line": 28 } }, { "defaultMessage": "!!!Invite Friends", "end": { "column": 3, - "line": 34 + "line": 35 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.inviteFriends", "start": { "column": 17, - "line": 31 + "line": 32 } }, { "defaultMessage": "!!!Logout", "end": { "column": 3, - "line": 38 + "line": 39 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.logout", "start": { "column": 10, - "line": 35 + "line": 36 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index db1d51f3b..6591af2e2 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -315,4 +315,4 @@ "workspaceDrawer.workspaceFeatureInfo": "

Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.

You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.

", "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings", "workspaces.switchingIndicator.switchingTo": "Switching to" -} +} \ No newline at end of file diff --git a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json index de78a71cf..77b0ed8a4 100644 --- a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json +++ b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Available services", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 11, + "line": 12, "column": 21 }, "end": { - "line": 14, + "line": 15, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Your services", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 15, + "line": 16, "column": 16 }, "end": { - "line": 18, + "line": 19, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Your workspaces", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 19, + "line": 20, "column": 18 }, "end": { - "line": 22, + "line": 23, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Account", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 23, + "line": 24, "column": 11 }, "end": { - "line": 26, + "line": 27, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Settings", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 27, + "line": 28, "column": 12 }, "end": { - "line": 30, + "line": 31, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Invite Friends", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 31, + "line": 32, "column": 17 }, "end": { - "line": 34, + "line": 35, "column": 3 } }, @@ -82,11 +82,11 @@ "defaultMessage": "!!!Logout", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 35, + "line": 36, "column": 10 }, "end": { - "line": 38, + "line": 39, "column": 3 } } 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/styles/settings.scss b/src/styles/settings.scss index 6340f2951..dd6f56d2b 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss @@ -85,11 +85,6 @@ .badge { background: $dark-theme-gray-lighter; color: $dark-theme-gray-smoke; - - &.badge--pro { - background: $theme-brand-primary; - padding: 4px 6px 3px 7px; - } } &:hover { @@ -98,11 +93,6 @@ .badge { background: $dark-theme-gray-lighter; color: $dark-theme-gray-smoke; - - &.badge--pro { - background: $theme-brand-primary; - padding: 4px 6px 3px 7px; - } } } @@ -433,15 +423,6 @@ text-decoration: none; transition: background $theme-transition-time, color $theme-transition-time; - .badge--pro { - background: $theme-brand-primary !important; - padding: 4px 6px 3px 7px; - - .badge-icon-pro { - fill: white; - } - } - &:hover { background: darken($theme-gray-lightest, 5%); @@ -454,13 +435,6 @@ background: $theme-brand-primary; color: #FFF; - .badge--pro { - background: white !important; - .badge-icon-pro { - fill: $theme-brand-primary; - } - } - .badge { background: #FFF; color: $theme-brand-primary; diff --git a/uidev/src/stories/badge.stories.tsx b/uidev/src/stories/badge.stories.tsx index 6de2034bf..d7b4d55b5 100644 --- a/uidev/src/stories/badge.stories.tsx +++ b/uidev/src/stories/badge.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Badge } from '@meetfranz/ui'; +import { Badge, ProBadge } from '@meetfranz/ui'; import { storiesOf } from '../stores/stories'; storiesOf('Badge') @@ -18,4 +18,14 @@ storiesOf('Badge') danger inverted + )) + .add('Pro Badge', () => ( + <> + + + )) + .add('Pro Badge inverted', () => ( + <> + + )); -- cgit v1.2.3-70-g09d2