aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/actions/index.js6
-rw-r--r--src/actions/lib/actions.js4
-rw-r--r--src/app.js4
-rw-r--r--src/components/layout/AppLayout.js26
-rw-r--r--src/components/layout/Sidebar.js49
-rw-r--r--src/components/services/content/ServiceView.js5
-rw-r--r--src/components/services/tabs/Tabbar.js4
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js26
-rw-r--r--src/components/settings/services/EditServiceForm.js10
-rw-r--r--src/components/settings/services/ServicesDashboard.js2
-rw-r--r--src/components/settings/settings/EditSettingsForm.js1
-rw-r--r--src/components/ui/AppLoader/index.js4
-rw-r--r--src/components/ui/FullscreenLoader/index.js4
-rw-r--r--src/components/ui/Infobox.js17
-rw-r--r--src/components/ui/PremiumFeatureContainer/index.js21
-rw-r--r--src/components/ui/PremiumFeatureContainer/styles.js3
-rw-r--r--src/components/ui/WebviewLoader/index.js18
-rw-r--r--src/config.js2
-rw-r--r--src/containers/layout/AppLayoutContainer.js14
-rw-r--r--src/containers/settings/SettingsWindow.js2
-rw-r--r--src/environment.js1
-rw-r--r--src/features/delayApp/Component.js2
-rw-r--r--src/features/delayApp/index.js2
-rw-r--r--src/features/spellchecker/index.js2
-rw-r--r--src/features/utils/FeatureStore.js21
-rw-r--r--src/features/workspaces/actions.js26
-rw-r--r--src/features/workspaces/api.js66
-rw-r--r--src/features/workspaces/components/CreateWorkspaceForm.js99
-rw-r--r--src/features/workspaces/components/EditWorkspaceForm.js189
-rw-r--r--src/features/workspaces/components/ServiceListItem.js48
-rw-r--r--src/features/workspaces/components/WorkspaceDrawer.js167
-rw-r--r--src/features/workspaces/components/WorkspaceDrawerItem.js101
-rw-r--r--src/features/workspaces/components/WorkspaceItem.js42
-rw-r--r--src/features/workspaces/components/WorkspaceSwitchingIndicator.js87
-rw-r--r--src/features/workspaces/components/WorkspacesDashboard.js187
-rw-r--r--src/features/workspaces/containers/EditWorkspaceScreen.js60
-rw-r--r--src/features/workspaces/containers/WorkspacesScreen.js42
-rw-r--r--src/features/workspaces/index.js36
-rw-r--r--src/features/workspaces/models/Workspace.js25
-rw-r--r--src/features/workspaces/store.js198
-rw-r--r--src/features/workspaces/styles/workspaces-table.scss53
-rw-r--r--src/i18n/locales/de.json12
-rw-r--r--src/i18n/locales/defaultMessages.json694
-rw-r--r--src/i18n/locales/en-US.json33
-rw-r--r--src/i18n/messages/src/components/layout/AppLayout.json24
-rw-r--r--src/i18n/messages/src/components/layout/Sidebar.json42
-rw-r--r--src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json37
-rw-r--r--src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json4
-rw-r--r--src/i18n/messages/src/components/ui/WebviewLoader/index.json15
-rw-r--r--src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json28
-rw-r--r--src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json67
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json67
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json15
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json15
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json106
-rw-r--r--src/i18n/messages/src/lib/Menu.json253
-rw-r--r--src/lib/Menu.js94
-rw-r--r--src/lib/analytics.js4
-rw-r--r--src/stores/FeaturesStore.js29
-rw-r--r--src/stores/ServicesStore.js9
-rw-r--r--src/stores/UserStore.js4
-rw-r--r--src/stores/lib/Request.js6
-rw-r--r--src/styles/layout.scss13
-rw-r--r--src/styles/main.scss3
-rw-r--r--src/styles/settings.scss15
65 files changed, 2956 insertions, 309 deletions
diff --git a/src/actions/index.js b/src/actions/index.js
index 59acabb0b..00f843cd6 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -11,6 +11,7 @@ import payment from './payment';
11import news from './news'; 11import news from './news';
12import settings from './settings'; 12import settings from './settings';
13import requests from './requests'; 13import requests from './requests';
14import workspaces from '../features/workspaces/actions';
14 15
15const actions = Object.assign({}, { 16const actions = Object.assign({}, {
16 service, 17 service,
@@ -25,4 +26,7 @@ const actions = Object.assign({}, {
25 requests, 26 requests,
26}); 27});
27 28
28export default defineActions(actions, PropTypes.checkPropTypes); 29export default Object.assign(
30 defineActions(actions, PropTypes.checkPropTypes),
31 { workspaces },
32);
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) => {
9 actions[actionName] = action; 9 actions[actionName] = action;
10 action.listeners = []; 10 action.listeners = [];
11 action.listen = listener => action.listeners.push(listener); 11 action.listen = listener => action.listeners.push(listener);
12 action.off = (listener) => {
13 const { listeners } = action;
14 listeners.splice(listeners.indexOf(listener), 1);
15 };
12 action.notify = params => action.listeners.forEach(listener => listener(params)); 16 action.notify = params => action.listeners.forEach(listener => listener(params));
13 }); 17 });
14 return actions; 18 return actions;
diff --git a/src/app.js b/src/app.js
index 6660feb46..d3b540f62 100644
--- a/src/app.js
+++ b/src/app.js
@@ -39,6 +39,8 @@ import PricingScreen from './containers/auth/PricingScreen';
39import InviteScreen from './containers/auth/InviteScreen'; 39import InviteScreen from './containers/auth/InviteScreen';
40import AuthLayoutContainer from './containers/auth/AuthLayoutContainer'; 40import AuthLayoutContainer from './containers/auth/AuthLayoutContainer';
41import SubscriptionPopupScreen from './containers/subscription/SubscriptionPopupScreen'; 41import SubscriptionPopupScreen from './containers/subscription/SubscriptionPopupScreen';
42import WorkspacesScreen from './features/workspaces/containers/WorkspacesScreen';
43import EditWorkspaceScreen from './features/workspaces/containers/EditWorkspaceScreen';
42 44
43// Add Polyfills 45// Add Polyfills
44smoothScroll.polyfill(); 46smoothScroll.polyfill();
@@ -75,6 +77,8 @@ window.addEventListener('load', () => {
75 <Route path="/settings/recipes/:filter" component={RecipesScreen} /> 77 <Route path="/settings/recipes/:filter" component={RecipesScreen} />
76 <Route path="/settings/services" component={ServicesScreen} /> 78 <Route path="/settings/services" component={ServicesScreen} />
77 <Route path="/settings/services/:action/:id" component={EditServiceScreen} /> 79 <Route path="/settings/services/:action/:id" component={EditServiceScreen} />
80 <Route path="/settings/workspaces" component={WorkspacesScreen} />
81 <Route path="/settings/workspaces/:action/:id" component={EditWorkspaceScreen} />
78 <Route path="/settings/user" component={AccountScreen} /> 82 <Route path="/settings/user" component={AccountScreen} />
79 <Route path="/settings/user/edit" component={EditUserScreen} /> 83 <Route path="/settings/user/edit" component={EditUserScreen} />
80 <Route path="/settings/app" component={EditSettingsScreen} /> 84 <Route path="/settings/app" component={EditSettingsScreen} />
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 593149e72..0c72c1413 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import { TitleBar } from 'electron-react-titlebar'; 5import { TitleBar } from 'electron-react-titlebar';
6import injectSheet from 'react-jss';
6 7
7import InfoBar from '../ui/InfoBar'; 8import InfoBar from '../ui/InfoBar';
8import { Component as DelayApp } from '../../features/delayApp'; 9import { Component as DelayApp } from '../../features/delayApp';
@@ -13,6 +14,8 @@ import ErrorBoundary from '../util/ErrorBoundary';
13// import globalMessages from '../../i18n/globalMessages'; 14// import globalMessages from '../../i18n/globalMessages';
14 15
15import { isWindows } from '../../environment'; 16import { isWindows } from '../../environment';
17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator';
18import { workspaceStore } from '../../features/workspaces';
16 19
17function createMarkup(HTMLString) { 20function createMarkup(HTMLString) {
18 return { __html: HTMLString }; 21 return { __html: HTMLString };
@@ -45,10 +48,23 @@ const messages = defineMessages({
45 }, 48 },
46}); 49});
47 50
48export default @observer class AppLayout extends Component { 51const styles = theme => ({
52 appContent: {
53 width: `calc(100% + ${theme.workspaceDrawerWidth}px)`,
54 transition: 'transform 0.5s ease',
55 transform() {
56 return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaceDrawerWidth}px)`;
57 },
58 },
59});
60
61@injectSheet(styles) @observer
62class AppLayout extends Component {
49 static propTypes = { 63 static propTypes = {
64 classes: PropTypes.object.isRequired,
50 isFullScreen: PropTypes.bool.isRequired, 65 isFullScreen: PropTypes.bool.isRequired,
51 sidebar: PropTypes.element.isRequired, 66 sidebar: PropTypes.element.isRequired,
67 workspacesDrawer: PropTypes.element.isRequired,
52 services: PropTypes.element.isRequired, 68 services: PropTypes.element.isRequired,
53 children: PropTypes.element, 69 children: PropTypes.element,
54 news: MobxPropTypes.arrayOrObservableArray.isRequired, 70 news: MobxPropTypes.arrayOrObservableArray.isRequired,
@@ -76,7 +92,9 @@ export default @observer class AppLayout extends Component {
76 92
77 render() { 93 render() {
78 const { 94 const {
95 classes,
79 isFullScreen, 96 isFullScreen,
97 workspacesDrawer,
80 sidebar, 98 sidebar,
81 services, 99 services,
82 children, 100 children,
@@ -102,9 +120,11 @@ export default @observer class AppLayout extends Component {
102 <div className={(darkMode ? 'theme__dark' : '')}> 120 <div className={(darkMode ? 'theme__dark' : '')}>
103 <div className="app"> 121 <div className="app">
104 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} 122 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />}
105 <div className="app__content"> 123 <div className={`app__content ${classes.appContent}`}>
124 {workspacesDrawer}
106 {sidebar} 125 {sidebar}
107 <div className="app__service"> 126 <div className="app__service">
127 <WorkspaceSwitchingIndicator />
108 {news.length > 0 && news.map(item => ( 128 {news.length > 0 && news.map(item => (
109 <InfoBar 129 <InfoBar
110 key={item.id} 130 key={item.id}
@@ -176,3 +196,5 @@ export default @observer class AppLayout extends Component {
176 ); 196 );
177 } 197 }
178} 198}
199
200export default AppLayout;
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js
index 609a3b604..327f76392 100644
--- a/src/components/layout/Sidebar.js
+++ b/src/components/layout/Sidebar.js
@@ -6,6 +6,8 @@ import { observer } from 'mobx-react';
6 6
7import Tabbar from '../services/tabs/Tabbar'; 7import Tabbar from '../services/tabs/Tabbar';
8import { ctrlKey } from '../../environment'; 8import { ctrlKey } from '../../environment';
9import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../../features/workspaces';
10import { gaEvent } from '../../lib/analytics';
9 11
10const messages = defineMessages({ 12const messages = defineMessages({
11 settings: { 13 settings: {
@@ -24,6 +26,14 @@ const messages = defineMessages({
24 id: 'sidebar.unmuteApp', 26 id: 'sidebar.unmuteApp',
25 defaultMessage: '!!!Enable notifications & audio', 27 defaultMessage: '!!!Enable notifications & audio',
26 }, 28 },
29 openWorkspaceDrawer: {
30 id: 'sidebar.openWorkspaceDrawer',
31 defaultMessage: '!!!Open workspace drawer',
32 },
33 closeWorkspaceDrawer: {
34 id: 'sidebar.closeWorkspaceDrawer',
35 defaultMessage: '!!!Close workspace drawer',
36 },
27}); 37});
28 38
29export default @observer class Sidebar extends Component { 39export default @observer class Sidebar extends Component {
@@ -31,7 +41,9 @@ export default @observer class Sidebar extends Component {
31 openSettings: PropTypes.func.isRequired, 41 openSettings: PropTypes.func.isRequired,
32 toggleMuteApp: PropTypes.func.isRequired, 42 toggleMuteApp: PropTypes.func.isRequired,
33 isAppMuted: PropTypes.bool.isRequired, 43 isAppMuted: PropTypes.bool.isRequired,
34 } 44 isWorkspaceDrawerOpen: PropTypes.bool.isRequired,
45 toggleWorkspaceDrawer: PropTypes.func.isRequired,
46 };
35 47
36 static contextTypes = { 48 static contextTypes = {
37 intl: intlShape, 49 intl: intlShape,
@@ -53,9 +65,23 @@ export default @observer class Sidebar extends Component {
53 this.setState({ tooltipEnabled: false }); 65 this.setState({ tooltipEnabled: false });
54 } 66 }
55 67
68 updateToolTip() {
69 this.disableToolTip();
70 setTimeout(this.enableToolTip.bind(this));
71 }
72
56 render() { 73 render() {
57 const { openSettings, toggleMuteApp, isAppMuted } = this.props; 74 const {
75 openSettings,
76 toggleMuteApp,
77 isAppMuted,
78 isWorkspaceDrawerOpen,
79 toggleWorkspaceDrawer,
80 } = this.props;
58 const { intl } = this.context; 81 const { intl } = this.context;
82 const workspaceToggleMessage = (
83 isWorkspaceDrawerOpen ? messages.closeWorkspaceDrawer : messages.openWorkspaceDrawer
84 );
59 85
60 return ( 86 return (
61 <div className="sidebar"> 87 <div className="sidebar">
@@ -64,9 +90,26 @@ export default @observer class Sidebar extends Component {
64 enableToolTip={() => this.enableToolTip()} 90 enableToolTip={() => this.enableToolTip()}
65 disableToolTip={() => this.disableToolTip()} 91 disableToolTip={() => this.disableToolTip()}
66 /> 92 />
93 {workspaceStore.isFeatureEnabled ? (
94 <button
95 type="button"
96 onClick={() => {
97 toggleWorkspaceDrawer();
98 this.updateToolTip();
99 gaEvent(GA_CATEGORY_WORKSPACES, 'toggleDrawer', 'sidebar');
100 }}
101 className={`sidebar__button sidebar__button--workspaces ${isWorkspaceDrawerOpen ? 'is-active' : ''}`}
102 data-tip={`${intl.formatMessage(workspaceToggleMessage)} (${ctrlKey}+Shift+D)`}
103 >
104 <i className="mdi mdi-view-grid" />
105 </button>
106 ) : null}
67 <button 107 <button
68 type="button" 108 type="button"
69 onClick={toggleMuteApp} 109 onClick={() => {
110 toggleMuteApp();
111 this.updateToolTip();
112 }}
70 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} 113 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`}
71 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`} 114 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${ctrlKey}+Shift+M)`}
72 > 115 >
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 5afc54f9d..13148b9b3 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -35,11 +35,13 @@ export default @observer class ServiceView extends Component {
35 35
36 autorunDisposer = null; 36 autorunDisposer = null;
37 37
38 forceRepaintTimeout = null;
39
38 componentDidMount() { 40 componentDidMount() {
39 this.autorunDisposer = autorun(() => { 41 this.autorunDisposer = autorun(() => {
40 if (this.props.service.isActive) { 42 if (this.props.service.isActive) {
41 this.setState({ forceRepaint: true }); 43 this.setState({ forceRepaint: true });
42 setTimeout(() => { 44 this.forceRepaintTimeout = setTimeout(() => {
43 this.setState({ forceRepaint: false }); 45 this.setState({ forceRepaint: false });
44 }, 100); 46 }, 100);
45 } 47 }
@@ -48,6 +50,7 @@ export default @observer class ServiceView extends Component {
48 50
49 componentWillUnmount() { 51 componentWillUnmount() {
50 this.autorunDisposer(); 52 this.autorunDisposer();
53 clearTimeout(this.forceRepaintTimeout);
51 } 54 }
52 55
53 updateTargetUrl = (event) => { 56 updateTargetUrl = (event) => {
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 {
19 updateService: PropTypes.func.isRequired, 19 updateService: PropTypes.func.isRequired,
20 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, 20 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired,
21 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, 21 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired,
22 } 22 };
23 23
24 onSortEnd = ({ oldIndex, newIndex }) => { 24 onSortEnd = ({ oldIndex, newIndex }) => {
25 const { 25 const {
@@ -45,7 +45,7 @@ export default @observer class TabBar extends Component {
45 redirect: false, 45 redirect: false,
46 }); 46 });
47 } 47 }
48 } 48 };
49 49
50 disableService({ serviceId }) { 50 disableService({ serviceId }) {
51 this.toggleService({ serviceId, isEnabled: false }); 51 this.toggleService({ serviceId, isEnabled: false });
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index 953f702f8..945285f5a 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -2,8 +2,10 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, intlShape } from 'react-intl';
4import { inject, observer } from 'mobx-react'; 4import { inject, observer } from 'mobx-react';
5import { Icon } from '@meetfranz/ui';
5 6
6import Link from '../../ui/Link'; 7import Link from '../../ui/Link';
8import { workspaceStore } from '../../../features/workspaces';
7 9
8const messages = defineMessages({ 10const messages = defineMessages({
9 availableServices: { 11 availableServices: {
@@ -14,6 +16,10 @@ const messages = defineMessages({
14 id: 'settings.navigation.yourServices', 16 id: 'settings.navigation.yourServices',
15 defaultMessage: '!!!Your services', 17 defaultMessage: '!!!Your services',
16 }, 18 },
19 yourWorkspaces: {
20 id: 'settings.navigation.yourWorkspaces',
21 defaultMessage: '!!!Your workspaces',
22 },
17 account: { 23 account: {
18 id: 'settings.navigation.account', 24 id: 'settings.navigation.account',
19 defaultMessage: '!!!Account', 25 defaultMessage: '!!!Account',
@@ -35,6 +41,7 @@ const messages = defineMessages({
35export default @inject('stores') @observer class SettingsNavigation extends Component { 41export default @inject('stores') @observer class SettingsNavigation extends Component {
36 static propTypes = { 42 static propTypes = {
37 serviceCount: PropTypes.number.isRequired, 43 serviceCount: PropTypes.number.isRequired,
44 workspaceCount: PropTypes.number.isRequired,
38 }; 45 };
39 46
40 static contextTypes = { 47 static contextTypes = {
@@ -42,7 +49,7 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
42 }; 49 };
43 50
44 render() { 51 render() {
45 const { serviceCount } = this.props; 52 const { serviceCount, workspaceCount } = this.props;
46 const { intl } = this.context; 53 const { intl } = this.context;
47 54
48 return ( 55 return (
@@ -63,6 +70,23 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
63 {' '} 70 {' '}
64 <span className="badge">{serviceCount}</span> 71 <span className="badge">{serviceCount}</span>
65 </Link> 72 </Link>
73 {workspaceStore.isFeatureEnabled ? (
74 <Link
75 to="/settings/workspaces"
76 className="settings-navigation__link"
77 activeClassName="is-active"
78 >
79 {intl.formatMessage(messages.yourWorkspaces)}
80 {' '}
81 {workspaceStore.isPremiumUpgradeRequired ? (
82 <span className="badge badge--pro">
83 <Icon icon="mdiStar" />
84 </span>
85 ) : (
86 <span className="badge">{workspaceCount}</span>
87 )}
88 </Link>
89 ) : null}
66 <Link 90 <Link
67 to="/settings/user" 91 to="/settings/user"
68 className="settings-navigation__link" 92 className="settings-navigation__link"
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 21616b5de..4ba2eb844 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -341,14 +341,20 @@ export default @observer class EditServiceForm extends Component {
341 </div> 341 </div>
342 </div> 342 </div>
343 343
344 <PremiumFeatureContainer condition={isSpellcheckerPremiumFeature}> 344 <PremiumFeatureContainer
345 condition={isSpellcheckerPremiumFeature}
346 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
347 >
345 <div className="settings__settings-group"> 348 <div className="settings__settings-group">
346 <Select field={form.$('spellcheckerLanguage')} /> 349 <Select field={form.$('spellcheckerLanguage')} />
347 </div> 350 </div>
348 </PremiumFeatureContainer> 351 </PremiumFeatureContainer>
349 352
350 {isProxyFeatureEnabled && ( 353 {isProxyFeatureEnabled && (
351 <PremiumFeatureContainer condition={isProxyPremiumFeature}> 354 <PremiumFeatureContainer
355 condition={isProxyPremiumFeature}
356 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'proxy' }}
357 >
352 <div className="settings__settings-group"> 358 <div className="settings__settings-group">
353 <h3> 359 <h3>
354 {intl.formatMessage(messages.headlineProxy)} 360 {intl.formatMessage(messages.headlineProxy)}
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index a12df7372..53bae12df 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -65,7 +65,7 @@ export default @observer class ServicesDashboard extends Component {
65 65
66 static defaultProps = { 66 static defaultProps = {
67 searchNeedle: '', 67 searchNeedle: '',
68 } 68 };
69 69
70 static contextTypes = { 70 static contextTypes = {
71 intl: intlShape, 71 intl: intlShape,
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index a92e559f3..8429d0ecb 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -170,6 +170,7 @@ export default @observer class EditSettingsForm extends Component {
170 <Select field={form.$('locale')} showLabel={false} /> 170 <Select field={form.$('locale')} showLabel={false} />
171 <PremiumFeatureContainer 171 <PremiumFeatureContainer
172 condition={isSpellcheckerPremiumFeature} 172 condition={isSpellcheckerPremiumFeature}
173 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
173 > 174 >
174 <Fragment> 175 <Fragment>
175 <Toggle 176 <Toggle
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
23 static propTypes = { 23 static propTypes = {
24 classes: PropTypes.object.isRequired, 24 classes: PropTypes.object.isRequired,
25 theme: PropTypes.object.isRequired, 25 theme: PropTypes.object.isRequired,
26 } 26 };
27 27
28 state = { 28 state = {
29 step: 0, 29 step: 0,
30 } 30 };
31 31
32 interval = null; 32 interval = null;
33 33
diff --git a/src/components/ui/FullscreenLoader/index.js b/src/components/ui/FullscreenLoader/index.js
index 6ecf4d395..06dab1eb6 100644
--- a/src/components/ui/FullscreenLoader/index.js
+++ b/src/components/ui/FullscreenLoader/index.js
@@ -16,13 +16,13 @@ export default @observer @withTheme @injectSheet(styles) class FullscreenLoader
16 theme: PropTypes.object.isRequired, 16 theme: PropTypes.object.isRequired,
17 spinnerColor: PropTypes.string, 17 spinnerColor: PropTypes.string,
18 children: PropTypes.node, 18 children: PropTypes.node,
19 } 19 };
20 20
21 static defaultProps = { 21 static defaultProps = {
22 className: null, 22 className: null,
23 spinnerColor: null, 23 spinnerColor: null,
24 children: null, 24 children: null,
25 } 25 };
26 26
27 render() { 27 render() {
28 const { 28 const {
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js
index a33c6474a..0917ee9f0 100644
--- a/src/components/ui/Infobox.js
+++ b/src/components/ui/Infobox.js
@@ -13,6 +13,8 @@ export default @observer class Infobox extends Component {
13 ctaLabel: PropTypes.string, 13 ctaLabel: PropTypes.string,
14 ctaLoading: PropTypes.bool, 14 ctaLoading: PropTypes.bool,
15 dismissable: PropTypes.bool, 15 dismissable: PropTypes.bool,
16 onDismiss: PropTypes.func,
17 onSeen: PropTypes.func,
16 }; 18 };
17 19
18 static defaultProps = { 20 static defaultProps = {
@@ -22,12 +24,19 @@ export default @observer class Infobox extends Component {
22 ctaOnClick: () => null, 24 ctaOnClick: () => null,
23 ctaLabel: '', 25 ctaLabel: '',
24 ctaLoading: false, 26 ctaLoading: false,
27 onDismiss: () => null,
28 onSeen: () => null,
25 }; 29 };
26 30
27 state = { 31 state = {
28 dismissed: false, 32 dismissed: false,
29 }; 33 };
30 34
35 componentDidMount() {
36 const { onSeen } = this.props;
37 if (onSeen) onSeen();
38 }
39
31 render() { 40 render() {
32 const { 41 const {
33 children, 42 children,
@@ -37,6 +46,7 @@ export default @observer class Infobox extends Component {
37 ctaLoading, 46 ctaLoading,
38 ctaOnClick, 47 ctaOnClick,
39 dismissable, 48 dismissable,
49 onDismiss,
40 } = this.props; 50 } = this.props;
41 51
42 if (this.state.dismissed) { 52 if (this.state.dismissed) {
@@ -76,9 +86,10 @@ export default @observer class Infobox extends Component {
76 {dismissable && ( 86 {dismissable && (
77 <button 87 <button
78 type="button" 88 type="button"
79 onClick={() => this.setState({ 89 onClick={() => {
80 dismissed: true, 90 this.setState({ dismissed: true });
81 })} 91 if (onDismiss) onDismiss();
92 }}
82 className="infobox__delete mdi mdi-close" 93 className="infobox__delete mdi mdi-close"
83 /> 94 />
84 )} 95 )}
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js
index 67cd6af0b..3c1e0fac3 100644
--- a/src/components/ui/PremiumFeatureContainer/index.js
+++ b/src/components/ui/PremiumFeatureContainer/index.js
@@ -9,6 +9,7 @@ import { oneOrManyChildElements } from '../../../prop-types';
9import UserStore from '../../../stores/UserStore'; 9import UserStore from '../../../stores/UserStore';
10 10
11import styles from './styles'; 11import styles from './styles';
12import { gaEvent } from '../../../lib/analytics';
12 13
13const messages = defineMessages({ 14const messages = defineMessages({
14 action: { 15 action: {
@@ -17,14 +18,21 @@ const messages = defineMessages({
17 }, 18 },
18}); 19});
19 20
20export default @inject('stores', 'actions') @injectSheet(styles) @observer class PremiumFeatureContainer extends Component { 21@inject('stores', 'actions') @injectSheet(styles) @observer
22class PremiumFeatureContainer extends Component {
21 static propTypes = { 23 static propTypes = {
22 classes: PropTypes.object.isRequired, 24 classes: PropTypes.object.isRequired,
23 condition: PropTypes.bool, 25 condition: PropTypes.bool,
26 gaEventInfo: PropTypes.shape({
27 category: PropTypes.string.isRequired,
28 event: PropTypes.string.isRequired,
29 label: PropTypes.string,
30 }),
24 }; 31 };
25 32
26 static defaultProps = { 33 static defaultProps = {
27 condition: true, 34 condition: true,
35 gaEventInfo: null,
28 }; 36 };
29 37
30 static contextTypes = { 38 static contextTypes = {
@@ -38,6 +46,7 @@ export default @inject('stores', 'actions') @injectSheet(styles) @observer class
38 actions, 46 actions,
39 condition, 47 condition,
40 stores, 48 stores,
49 gaEventInfo,
41 } = this.props; 50 } = this.props;
42 51
43 const { intl } = this.context; 52 const { intl } = this.context;
@@ -49,7 +58,13 @@ export default @inject('stores', 'actions') @injectSheet(styles) @observer class
49 <button 58 <button
50 className={classes.actionButton} 59 className={classes.actionButton}
51 type="button" 60 type="button"
52 onClick={() => actions.ui.openSettings({ path: 'user' })} 61 onClick={() => {
62 actions.ui.openSettings({ path: 'user' });
63 if (gaEventInfo) {
64 const { category, event, label } = gaEventInfo;
65 gaEvent(category, event, label);
66 }
67 }}
53 > 68 >
54 {intl.formatMessage(messages.action)} 69 {intl.formatMessage(messages.action)}
55 </button> 70 </button>
@@ -73,3 +88,5 @@ PremiumFeatureContainer.wrappedComponent.propTypes = {
73 }).isRequired, 88 }).isRequired,
74 }).isRequired, 89 }).isRequired,
75}; 90};
91
92export 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 => ({
6 padding: 20, 6 padding: 20,
7 'border-radius': theme.borderRadius, 7 'border-radius': theme.borderRadius,
8 pointerEvents: 'none', 8 pointerEvents: 'none',
9 height: 'auto',
9 }, 10 },
10 titleContainer: { 11 titleContainer: {
11 display: 'flex', 12 display: 'flex',
@@ -26,7 +27,7 @@ export default theme => ({
26 content: { 27 content: {
27 opacity: 0.5, 28 opacity: 0.5,
28 'margin-top': 20, 29 'margin-top': 20,
29 '& :last-child': { 30 '& > :last-child': {
30 'margin-bottom': 0, 31 'margin-bottom': 0,
31 }, 32 },
32 }, 33 },
diff --git a/src/components/ui/WebviewLoader/index.js b/src/components/ui/WebviewLoader/index.js
index 3a3dbbe49..58b6b6f1b 100644
--- a/src/components/ui/WebviewLoader/index.js
+++ b/src/components/ui/WebviewLoader/index.js
@@ -2,23 +2,35 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
5import { defineMessages, intlShape } from 'react-intl';
5 6
6import FullscreenLoader from '../FullscreenLoader'; 7import FullscreenLoader from '../FullscreenLoader';
7
8import styles from './styles'; 8import styles from './styles';
9 9
10const messages = defineMessages({
11 loading: {
12 id: 'service.webviewLoader.loading',
13 defaultMessage: '!!!Loading',
14 },
15});
16
10export default @observer @injectSheet(styles) class WebviewLoader extends Component { 17export default @observer @injectSheet(styles) class WebviewLoader extends Component {
11 static propTypes = { 18 static propTypes = {
12 name: PropTypes.string.isRequired, 19 name: PropTypes.string.isRequired,
13 classes: PropTypes.object.isRequired, 20 classes: PropTypes.object.isRequired,
14 } 21 };
22
23 static contextTypes = {
24 intl: intlShape,
25 };
15 26
16 render() { 27 render() {
17 const { classes, name } = this.props; 28 const { classes, name } = this.props;
29 const { intl } = this.context;
18 return ( 30 return (
19 <FullscreenLoader 31 <FullscreenLoader
20 className={classes.component} 32 className={classes.component}
21 title={`Loading ${name}`} 33 title={`${intl.formatMessage(messages.loading)} ${name}`}
22 /> 34 />
23 ); 35 );
24 } 36 }
diff --git a/src/config.js b/src/config.js
index 479572edb..242675762 100644
--- a/src/config.js
+++ b/src/config.js
@@ -41,6 +41,8 @@ export const DEFAULT_FEATURES_CONFIG = {
41 }, 41 },
42 isServiceProxyEnabled: false, 42 isServiceProxyEnabled: false,
43 isServiceProxyPremiumFeature: true, 43 isServiceProxyPremiumFeature: true,
44 isWorkspacePremiumFeature: true,
45 isWorkspaceEnabled: false,
44}; 46};
45 47
46export const DEFAULT_WINDOW_OPTIONS = { 48export const DEFAULT_WINDOW_OPTIONS = {
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index 5a05ce431..4329c3097 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -20,6 +20,9 @@ import Services from '../../components/services/content/Services';
20import AppLoader from '../../components/ui/AppLoader'; 20import AppLoader from '../../components/ui/AppLoader';
21 21
22import { state as delayAppState } from '../../features/delayApp'; 22import { state as delayAppState } from '../../features/delayApp';
23import { workspaceActions } from '../../features/workspaces/actions';
24import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer';
25import { workspaceStore } from '../../features/workspaces';
23 26
24export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { 27export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component {
25 static defaultProps = { 28 static defaultProps = {
@@ -82,6 +85,14 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
82 ); 85 );
83 } 86 }
84 87
88 const workspacesDrawer = (
89 <WorkspaceDrawer
90 getServicesForWorkspace={workspace => (
91 workspace ? workspace.services.map(id => services.one(id).name) : services.all.map(s => s.name)
92 )}
93 />
94 );
95
85 const sidebar = ( 96 const sidebar = (
86 <Sidebar 97 <Sidebar
87 services={services.allDisplayed} 98 services={services.allDisplayed}
@@ -96,6 +107,8 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
96 deleteService={deleteService} 107 deleteService={deleteService}
97 updateService={updateService} 108 updateService={updateService}
98 toggleMuteApp={toggleMuteApp} 109 toggleMuteApp={toggleMuteApp}
110 toggleWorkspaceDrawer={workspaceActions.toggleWorkspaceDrawer}
111 isWorkspaceDrawerOpen={workspaceStore.isWorkspaceDrawerOpen}
99 showMessageBadgeWhenMutedSetting={settings.all.app.showMessageBadgeWhenMuted} 112 showMessageBadgeWhenMutedSetting={settings.all.app.showMessageBadgeWhenMuted}
100 showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted} 113 showMessageBadgesEvenWhenMuted={ui.showMessageBadgesEvenWhenMuted}
101 /> 114 />
@@ -122,6 +135,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
122 showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar} 135 showServicesUpdatedInfoBar={ui.showServicesUpdatedInfoBar}
123 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED} 136 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED}
124 sidebar={sidebar} 137 sidebar={sidebar}
138 workspacesDrawer={workspacesDrawer}
125 services={servicesContainer} 139 services={servicesContainer}
126 news={news.latest} 140 news={news.latest}
127 removeNewsItem={hide} 141 removeNewsItem={hide}
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js
index 6d9e0ee77..663b9e2e4 100644
--- a/src/containers/settings/SettingsWindow.js
+++ b/src/containers/settings/SettingsWindow.js
@@ -7,6 +7,7 @@ import ServicesStore from '../../stores/ServicesStore';
7import Layout from '../../components/settings/SettingsLayout'; 7import Layout from '../../components/settings/SettingsLayout';
8import Navigation from '../../components/settings/navigation/SettingsNavigation'; 8import Navigation from '../../components/settings/navigation/SettingsNavigation';
9import ErrorBoundary from '../../components/util/ErrorBoundary'; 9import ErrorBoundary from '../../components/util/ErrorBoundary';
10import { workspaceStore } from '../../features/workspaces';
10 11
11export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { 12export default @inject('stores', 'actions') @observer class SettingsContainer extends Component {
12 render() { 13 render() {
@@ -16,6 +17,7 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex
16 const navigation = ( 17 const navigation = (
17 <Navigation 18 <Navigation
18 serviceCount={stores.services.all.length} 19 serviceCount={stores.services.all.length}
20 workspaceCount={workspaceStore.workspaces.length}
19 /> 21 />
20 ); 22 );
21 23
diff --git a/src/environment.js b/src/environment.js
index 73b1c7ab2..d67fd6adb 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -28,3 +28,4 @@ if (!isDevMode || (isDevMode && useLiveAPI)) {
28} 28}
29 29
30export const API = api; 30export const API = api;
31export const API_VERSION = 'v1';
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js
index ff84510e8..ff0f1f2f8 100644
--- a/src/features/delayApp/Component.js
+++ b/src/features/delayApp/Component.js
@@ -38,7 +38,7 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
38 38
39 state = { 39 state = {
40 countdown: config.delayDuration, 40 countdown: config.delayDuration,
41 } 41 };
42 42
43 countdownInterval = null; 43 countdownInterval = null;
44 44
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) {
55 55
56 setVisibility(true); 56 setVisibility(true);
57 gaPage('/delayApp'); 57 gaPage('/delayApp');
58 gaEvent('delayApp', 'show', 'Delay App Feature'); 58 gaEvent('DelayApp', 'show', 'Delay App Feature');
59 59
60 timeLastDelay = moment(); 60 timeLastDelay = moment();
61 shownAfterLaunch = true; 61 shownAfterLaunch = true;
diff --git a/src/features/spellchecker/index.js b/src/features/spellchecker/index.js
index 94883ad17..79a2172b4 100644
--- a/src/features/spellchecker/index.js
+++ b/src/features/spellchecker/index.js
@@ -14,8 +14,6 @@ export default function init(stores) {
14 autorun(() => { 14 autorun(() => {
15 const { isSpellcheckerPremiumFeature } = stores.features.features; 15 const { isSpellcheckerPremiumFeature } = stores.features.features;
16 16
17 console.log('isSpellcheckerPremiumFeature', isSpellcheckerPremiumFeature);
18
19 config.isPremium = isSpellcheckerPremiumFeature !== undefined ? isSpellcheckerPremiumFeature : DEFAULT_FEATURES_CONFIG.isSpellcheckerPremiumFeature; 17 config.isPremium = isSpellcheckerPremiumFeature !== undefined ? isSpellcheckerPremiumFeature : DEFAULT_FEATURES_CONFIG.isSpellcheckerPremiumFeature;
20 18
21 if (!stores.user.data.isPremium && config.isPremium && stores.settings.app.enableSpellchecking) { 19 if (!stores.user.data.isPremium && config.isPremium && stores.settings.app.enableSpellchecking) {
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 @@
1import Reaction from '../../stores/lib/Reaction';
2
3export class FeatureStore {
4 _actions = null;
5
6 _reactions = null;
7
8 _listenToActions(actions) {
9 if (this._actions) this._actions.forEach(a => a[0].off(a[1]));
10 this._actions = [];
11 actions.forEach(a => this._actions.push(a));
12 this._actions.forEach(a => a[0].listen(a[1]));
13 }
14
15 _startReactions(reactions) {
16 if (this._reactions) this._reactions.forEach(r => r.stop());
17 this._reactions = [];
18 reactions.forEach(r => this._reactions.push(new Reaction(r)));
19 this._reactions.forEach(r => r.start());
20 }
21}
diff --git a/src/features/workspaces/actions.js b/src/features/workspaces/actions.js
new file mode 100644
index 000000000..a85f8f57f
--- /dev/null
+++ b/src/features/workspaces/actions.js
@@ -0,0 +1,26 @@
1import PropTypes from 'prop-types';
2import Workspace from './models/Workspace';
3import { createActionsFromDefinitions } from '../../actions/lib/actions';
4
5export const workspaceActions = createActionsFromDefinitions({
6 edit: {
7 workspace: PropTypes.instanceOf(Workspace).isRequired,
8 },
9 create: {
10 name: PropTypes.string.isRequired,
11 },
12 delete: {
13 workspace: PropTypes.instanceOf(Workspace).isRequired,
14 },
15 update: {
16 workspace: PropTypes.instanceOf(Workspace).isRequired,
17 },
18 activate: {
19 workspace: PropTypes.instanceOf(Workspace).isRequired,
20 },
21 deactivate: {},
22 toggleWorkspaceDrawer: {},
23 openWorkspaceSettings: {},
24}, PropTypes.checkPropTypes);
25
26export default workspaceActions;
diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.js
new file mode 100644
index 000000000..0ec20c9ea
--- /dev/null
+++ b/src/features/workspaces/api.js
@@ -0,0 +1,66 @@
1import { pick } from 'lodash';
2import { sendAuthRequest } from '../../api/utils/auth';
3import { API, API_VERSION } from '../../environment';
4import Request from '../../stores/lib/Request';
5import Workspace from './models/Workspace';
6
7const debug = require('debug')('Franz:feature:workspaces:api');
8
9export const workspaceApi = {
10 getUserWorkspaces: async () => {
11 const url = `${API}/${API_VERSION}/workspace`;
12 debug('getUserWorkspaces GET', url);
13 const result = await sendAuthRequest(url, { method: 'GET' });
14 debug('getUserWorkspaces RESULT', result);
15 if (!result.ok) throw result;
16 const workspaces = await result.json();
17 return workspaces.map(data => new Workspace(data));
18 },
19
20 createWorkspace: async (name) => {
21 const url = `${API}/${API_VERSION}/workspace`;
22 const options = {
23 method: 'POST',
24 body: JSON.stringify({ name }),
25 };
26 debug('createWorkspace POST', url, options);
27 const result = await sendAuthRequest(url, options);
28 debug('createWorkspace RESULT', result);
29 if (!result.ok) throw result;
30 return new Workspace(await result.json());
31 },
32
33 deleteWorkspace: async (workspace) => {
34 const url = `${API}/${API_VERSION}/workspace/${workspace.id}`;
35 debug('deleteWorkspace DELETE', url);
36 const result = await sendAuthRequest(url, { method: 'DELETE' });
37 debug('deleteWorkspace RESULT', result);
38 if (!result.ok) throw result;
39 return true;
40 },
41
42 updateWorkspace: async (workspace) => {
43 const url = `${API}/${API_VERSION}/workspace/${workspace.id}`;
44 const options = {
45 method: 'PUT',
46 body: JSON.stringify(pick(workspace, ['name', 'services'])),
47 };
48 debug('updateWorkspace UPDATE', url, options);
49 const result = await sendAuthRequest(url, options);
50 debug('updateWorkspace RESULT', result);
51 if (!result.ok) throw result;
52 return new Workspace(await result.json());
53 },
54};
55
56export const getUserWorkspacesRequest = new Request(workspaceApi, 'getUserWorkspaces');
57export const createWorkspaceRequest = new Request(workspaceApi, 'createWorkspace');
58export const deleteWorkspaceRequest = new Request(workspaceApi, 'deleteWorkspace');
59export const updateWorkspaceRequest = new Request(workspaceApi, 'updateWorkspace');
60
61export const resetApiRequests = () => {
62 getUserWorkspacesRequest.reset();
63 createWorkspaceRequest.reset();
64 deleteWorkspaceRequest.reset();
65 updateWorkspaceRequest.reset();
66};
diff --git a/src/features/workspaces/components/CreateWorkspaceForm.js b/src/features/workspaces/components/CreateWorkspaceForm.js
new file mode 100644
index 000000000..0be2d528f
--- /dev/null
+++ b/src/features/workspaces/components/CreateWorkspaceForm.js
@@ -0,0 +1,99 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Input, Button } from '@meetfranz/forms';
6import injectSheet from 'react-jss';
7import Form from '../../../lib/Form';
8import { required } from '../../../helpers/validation-helpers';
9import { gaEvent } from '../../../lib/analytics';
10import { GA_CATEGORY_WORKSPACES } from '../index';
11
12const messages = defineMessages({
13 submitButton: {
14 id: 'settings.workspace.add.form.submitButton',
15 defaultMessage: '!!!Create workspace',
16 },
17 name: {
18 id: 'settings.workspace.add.form.name',
19 defaultMessage: '!!!Name',
20 },
21});
22
23const styles = () => ({
24 form: {
25 display: 'flex',
26 },
27 input: {
28 flexGrow: 1,
29 marginRight: '10px',
30 },
31 submitButton: {
32 height: 'inherit',
33 },
34});
35
36@injectSheet(styles) @observer
37class CreateWorkspaceForm extends Component {
38 static contextTypes = {
39 intl: intlShape,
40 };
41
42 static propTypes = {
43 classes: PropTypes.object.isRequired,
44 isSubmitting: PropTypes.bool.isRequired,
45 onSubmit: PropTypes.func.isRequired,
46 };
47
48 form = (() => {
49 const { intl } = this.context;
50 return new Form({
51 fields: {
52 name: {
53 label: intl.formatMessage(messages.name),
54 placeholder: intl.formatMessage(messages.name),
55 value: '',
56 validators: [required],
57 },
58 },
59 });
60 })();
61
62 submitForm() {
63 const { form } = this;
64 form.submit({
65 onSuccess: async (f) => {
66 const { onSubmit } = this.props;
67 const values = f.values();
68 onSubmit(values);
69 gaEvent(GA_CATEGORY_WORKSPACES, 'create', values.name);
70 },
71 });
72 }
73
74 render() {
75 const { intl } = this.context;
76 const { classes, isSubmitting } = this.props;
77 const { form } = this;
78 return (
79 <div className={classes.form}>
80 <Input
81 className={classes.input}
82 {...form.$('name').bind()}
83 showLabel={false}
84 onEnterKey={this.submitForm.bind(this, form)}
85 />
86 <Button
87 className={classes.submitButton}
88 type="submit"
89 label={intl.formatMessage(messages.submitButton)}
90 onClick={this.submitForm.bind(this, form)}
91 busy={isSubmitting}
92 buttonType={isSubmitting ? 'secondary' : 'primary'}
93 />
94 </div>
95 );
96 }
97}
98
99export default CreateWorkspaceForm;
diff --git a/src/features/workspaces/components/EditWorkspaceForm.js b/src/features/workspaces/components/EditWorkspaceForm.js
new file mode 100644
index 000000000..e4bf44248
--- /dev/null
+++ b/src/features/workspaces/components/EditWorkspaceForm.js
@@ -0,0 +1,189 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router';
6import { Input, Button } from '@meetfranz/forms';
7import injectSheet from 'react-jss';
8
9import Workspace from '../models/Workspace';
10import Service from '../../../models/Service';
11import Form from '../../../lib/Form';
12import { required } from '../../../helpers/validation-helpers';
13import ServiceListItem from './ServiceListItem';
14import Request from '../../../stores/lib/Request';
15import { gaEvent } from '../../../lib/analytics';
16import { GA_CATEGORY_WORKSPACES } from '../index';
17
18const messages = defineMessages({
19 buttonDelete: {
20 id: 'settings.workspace.form.buttonDelete',
21 defaultMessage: '!!!Delete workspace',
22 },
23 buttonSave: {
24 id: 'settings.workspace.form.buttonSave',
25 defaultMessage: '!!!Save workspace',
26 },
27 name: {
28 id: 'settings.workspace.form.name',
29 defaultMessage: '!!!Name',
30 },
31 yourWorkspaces: {
32 id: 'settings.workspace.form.yourWorkspaces',
33 defaultMessage: '!!!Your workspaces',
34 },
35 servicesInWorkspaceHeadline: {
36 id: 'settings.workspace.form.servicesInWorkspaceHeadline',
37 defaultMessage: '!!!Services in this Workspace',
38 },
39});
40
41const styles = () => ({
42 nameInput: {
43 height: 'auto',
44 },
45 serviceList: {
46 height: 'auto',
47 },
48});
49
50@injectSheet(styles) @observer
51class EditWorkspaceForm extends Component {
52 static contextTypes = {
53 intl: intlShape,
54 };
55
56 static propTypes = {
57 classes: PropTypes.object.isRequired,
58 onDelete: PropTypes.func.isRequired,
59 onSave: PropTypes.func.isRequired,
60 services: PropTypes.arrayOf(PropTypes.instanceOf(Service)).isRequired,
61 workspace: PropTypes.instanceOf(Workspace).isRequired,
62 updateWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
63 deleteWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
64 };
65
66 form = this.prepareWorkspaceForm(this.props.workspace);
67
68 componentWillReceiveProps(nextProps) {
69 const { workspace } = this.props;
70 if (workspace.id !== nextProps.workspace.id) {
71 this.form = this.prepareWorkspaceForm(nextProps.workspace);
72 }
73 }
74
75 prepareWorkspaceForm(workspace) {
76 const { intl } = this.context;
77 return new Form({
78 fields: {
79 name: {
80 label: intl.formatMessage(messages.name),
81 placeholder: intl.formatMessage(messages.name),
82 value: workspace.name,
83 validators: [required],
84 },
85 services: {
86 value: workspace.services.slice(),
87 },
88 },
89 });
90 }
91
92 save(form) {
93 form.submit({
94 onSuccess: async (f) => {
95 const { onSave } = this.props;
96 const values = f.values();
97 onSave(values);
98 gaEvent(GA_CATEGORY_WORKSPACES, 'save');
99 },
100 onError: async () => {},
101 });
102 }
103
104 delete() {
105 const { onDelete } = this.props;
106 onDelete();
107 gaEvent(GA_CATEGORY_WORKSPACES, 'delete');
108 }
109
110 toggleService(service) {
111 const servicesField = this.form.$('services');
112 const serviceIds = servicesField.value;
113 if (serviceIds.includes(service.id)) {
114 serviceIds.splice(serviceIds.indexOf(service.id), 1);
115 } else {
116 serviceIds.push(service.id);
117 }
118 servicesField.set(serviceIds);
119 }
120
121 render() {
122 const { intl } = this.context;
123 const {
124 classes,
125 workspace,
126 services,
127 deleteWorkspaceRequest,
128 updateWorkspaceRequest,
129 } = this.props;
130 const { form } = this;
131 const workspaceServices = form.$('services').value;
132 const isDeleting = deleteWorkspaceRequest.isExecuting;
133 const isSaving = updateWorkspaceRequest.isExecuting;
134 return (
135 <div className="settings__main">
136 <div className="settings__header">
137 <span className="settings__header-item">
138 <Link to="/settings/workspaces">
139 {intl.formatMessage(messages.yourWorkspaces)}
140 </Link>
141 </span>
142 <span className="separator" />
143 <span className="settings__header-item">
144 {workspace.name}
145 </span>
146 </div>
147 <div className="settings__body">
148 <div className={classes.nameInput}>
149 <Input {...form.$('name').bind()} />
150 </div>
151 <h2>{intl.formatMessage(messages.servicesInWorkspaceHeadline)}</h2>
152 <div className={classes.serviceList}>
153 {services.map(s => (
154 <ServiceListItem
155 key={s.id}
156 service={s}
157 isInWorkspace={workspaceServices.includes(s.id)}
158 onToggle={() => this.toggleService(s)}
159 />
160 ))}
161 </div>
162 </div>
163 <div className="settings__controls">
164 {/* ===== Delete Button ===== */}
165 <Button
166 label={intl.formatMessage(messages.buttonDelete)}
167 loaded={false}
168 busy={isDeleting}
169 buttonType={isDeleting ? 'secondary' : 'danger'}
170 className="settings__delete-button"
171 disabled={isDeleting}
172 onClick={this.delete.bind(this)}
173 />
174 {/* ===== Save Button ===== */}
175 <Button
176 type="submit"
177 label={intl.formatMessage(messages.buttonSave)}
178 busy={isSaving}
179 buttonType={isSaving ? 'secondary' : 'primary'}
180 onClick={this.save.bind(this, form)}
181 disabled={isSaving}
182 />
183 </div>
184 </div>
185 );
186 }
187}
188
189export default EditWorkspaceForm;
diff --git a/src/features/workspaces/components/ServiceListItem.js b/src/features/workspaces/components/ServiceListItem.js
new file mode 100644
index 000000000..146cc5a36
--- /dev/null
+++ b/src/features/workspaces/components/ServiceListItem.js
@@ -0,0 +1,48 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { Toggle } from '@meetfranz/forms';
6
7import Service from '../../../models/Service';
8
9const styles = () => ({
10 service: {
11 height: 'auto',
12 display: 'flex',
13 },
14 name: {
15 marginTop: '4px',
16 },
17});
18
19@injectSheet(styles) @observer
20class ServiceListItem extends Component {
21 static propTypes = {
22 classes: PropTypes.object.isRequired,
23 isInWorkspace: PropTypes.bool.isRequired,
24 onToggle: PropTypes.func.isRequired,
25 service: PropTypes.instanceOf(Service).isRequired,
26 };
27
28 render() {
29 const {
30 classes,
31 isInWorkspace,
32 onToggle,
33 service,
34 } = this.props;
35
36 return (
37 <div className={classes.service}>
38 <Toggle
39 checked={isInWorkspace}
40 onChange={onToggle}
41 label={service.name}
42 />
43 </div>
44 );
45 }
46}
47
48export default ServiceListItem;
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js
new file mode 100644
index 000000000..6eacafa68
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceDrawer.js
@@ -0,0 +1,167 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { defineMessages, FormattedHTMLMessage, intlShape } from 'react-intl';
6import { H1, Icon } from '@meetfranz/ui';
7import { Button } from '@meetfranz/forms/lib';
8import ReactTooltip from 'react-tooltip';
9
10import WorkspaceDrawerItem from './WorkspaceDrawerItem';
11import { workspaceActions } from '../actions';
12import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../index';
13import { gaEvent } from '../../../lib/analytics';
14
15const messages = defineMessages({
16 headline: {
17 id: 'workspaceDrawer.headline',
18 defaultMessage: '!!!Workspaces',
19 },
20 allServices: {
21 id: 'workspaceDrawer.allServices',
22 defaultMessage: '!!!All services',
23 },
24 addWorkspaceTooltip: {
25 id: 'workspaceDrawer.addWorkspaceTooltip',
26 defaultMessage: '!!!Add workspace',
27 },
28 workspaceFeatureInfo: {
29 id: 'workspaceDrawer.workspaceFeatureInfo',
30 defaultMessage: '!!!Info about workspace feature',
31 },
32 premiumCtaButtonLabel: {
33 id: 'workspaceDrawer.premiumCtaButtonLabel',
34 defaultMessage: '!!!Create your first workspace',
35 },
36});
37
38const styles = theme => ({
39 drawer: {
40 backgroundColor: theme.workspaceDrawerBackground,
41 width: `${theme.workspaceDrawerWidth}px`,
42 },
43 headline: {
44 fontSize: '24px',
45 marginTop: '38px',
46 marginBottom: '25px',
47 marginLeft: `${theme.workspaceDrawerPadding}px`,
48 },
49 addWorkspaceButton: {
50 float: 'right',
51 marginRight: `${theme.workspaceDrawerPadding}px`,
52 marginTop: '2px',
53 },
54 addWorkspaceButtonIcon: {
55 fill: theme.workspaceDrawerAddButtonColor,
56 '&:hover': {
57 fill: theme.workspaceDrawerAddButtonHoverColor,
58 },
59 },
60 workspaces: {
61 height: 'auto',
62 },
63 premiumAnnouncement: {
64 padding: '20px',
65 paddingTop: '0',
66 height: 'auto',
67 },
68 premiumCtaButton: {
69 marginTop: '20px',
70 width: '100%',
71 color: 'white !important',
72 },
73});
74
75@injectSheet(styles) @observer
76class WorkspaceDrawer extends Component {
77 static propTypes = {
78 classes: PropTypes.object.isRequired,
79 getServicesForWorkspace: PropTypes.func.isRequired,
80 };
81
82 static contextTypes = {
83 intl: intlShape,
84 };
85
86 componentDidMount() {
87 ReactTooltip.rebuild();
88 }
89
90 render() {
91 const {
92 classes,
93 getServicesForWorkspace,
94 } = this.props;
95 const { intl } = this.context;
96 const {
97 activeWorkspace,
98 isSwitchingWorkspace,
99 nextWorkspace,
100 workspaces,
101 } = workspaceStore;
102 const actualWorkspace = isSwitchingWorkspace ? nextWorkspace : activeWorkspace;
103 return (
104 <div className={classes.drawer}>
105 <H1 className={classes.headline}>
106 {intl.formatMessage(messages.headline)}
107 <span
108 className={classes.addWorkspaceButton}
109 onClick={() => {
110 workspaceActions.openWorkspaceSettings();
111 gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerHeadline');
112 }}
113 data-tip={`${intl.formatMessage(messages.addWorkspaceTooltip)}`}
114 >
115 <Icon
116 icon="mdiPlusBox"
117 size={1.5}
118 className={classes.addWorkspaceButtonIcon}
119 />
120 </span>
121 </H1>
122 {workspaceStore.isPremiumUpgradeRequired ? (
123 <div className={classes.premiumAnnouncement}>
124 <FormattedHTMLMessage {...messages.workspaceFeatureInfo} />
125 <Button
126 className={classes.premiumCtaButton}
127 buttonType="primary"
128 label={intl.formatMessage(messages.premiumCtaButtonLabel)}
129 icon="mdiPlusBox"
130 onClick={() => {
131 workspaceActions.openWorkspaceSettings();
132 gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerPremiumCta');
133 }}
134 />
135 </div>
136 ) : (
137 <div className={classes.workspaces}>
138 <WorkspaceDrawerItem
139 name={intl.formatMessage(messages.allServices)}
140 onClick={() => {
141 workspaceActions.deactivate();
142 gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'drawer');
143 }}
144 services={getServicesForWorkspace(null)}
145 isActive={actualWorkspace == null}
146 />
147 {workspaces.map(workspace => (
148 <WorkspaceDrawerItem
149 key={workspace.id}
150 name={workspace.name}
151 isActive={actualWorkspace === workspace}
152 onClick={() => {
153 workspaceActions.activate({ workspace });
154 gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'drawer');
155 }}
156 services={getServicesForWorkspace(workspace)}
157 />
158 ))}
159 </div>
160 )}
161 <ReactTooltip place="right" type="dark" effect="solid" />
162 </div>
163 );
164 }
165}
166
167export default WorkspaceDrawer;
diff --git a/src/features/workspaces/components/WorkspaceDrawerItem.js b/src/features/workspaces/components/WorkspaceDrawerItem.js
new file mode 100644
index 000000000..1e28ebea6
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceDrawerItem.js
@@ -0,0 +1,101 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import classnames from 'classnames';
6import { defineMessages, intlShape } from 'react-intl';
7
8const messages = defineMessages({
9 noServicesAddedYet: {
10 id: 'workspaceDrawer.item.noServicesAddedYet',
11 defaultMessage: '!!!No services added yet',
12 },
13});
14
15const styles = theme => ({
16 item: {
17 height: '67px',
18 padding: `15px ${theme.workspaceDrawerPadding}px`,
19 borderBottom: `1px solid ${theme.workspaceDrawerItemBorder}`,
20 '&:first-child': {
21 borderTop: `1px solid ${theme.workspaceDrawerItemBorder}`,
22 },
23 },
24 isActiveItem: {
25 backgroundColor: theme.workspaceDrawerItemActiveBackground,
26 },
27 name: {
28 marginTop: '4px',
29 color: theme.workspaceDrawerItemNameColor,
30 },
31 activeName: {
32 color: theme.workspaceDrawerItemNameActiveColor,
33 },
34 services: {
35 display: 'block',
36 fontSize: '11px',
37 marginTop: '5px',
38 color: theme.workspaceDrawerServicesColor,
39 whiteSpace: 'nowrap',
40 textOverflow: 'ellipsis',
41 overflow: 'hidden',
42 lineHeight: '15px',
43 },
44 activeServices: {
45 color: theme.workspaceDrawerServicesActiveColor,
46 },
47});
48
49@injectSheet(styles) @observer
50class WorkspaceDrawerItem extends Component {
51 static propTypes = {
52 classes: PropTypes.object.isRequired,
53 isActive: PropTypes.bool.isRequired,
54 name: PropTypes.string.isRequired,
55 onClick: PropTypes.func.isRequired,
56 services: PropTypes.arrayOf(PropTypes.string).isRequired,
57 };
58
59 static contextTypes = {
60 intl: intlShape,
61 };
62
63 render() {
64 const {
65 classes,
66 isActive,
67 name,
68 onClick,
69 services,
70 } = this.props;
71 const { intl } = this.context;
72 return (
73 <div
74 className={classnames([
75 classes.item,
76 isActive ? classes.isActiveItem : null,
77 ])}
78 onClick={onClick}
79 >
80 <span
81 className={classnames([
82 classes.name,
83 isActive ? classes.activeName : null,
84 ])}
85 >
86 {name}
87 </span>
88 <span
89 className={classnames([
90 classes.services,
91 isActive ? classes.activeServices : null,
92 ])}
93 >
94 {services.length ? services.join(', ') : intl.formatMessage(messages.noServicesAddedYet)}
95 </span>
96 </div>
97 );
98 }
99}
100
101export default WorkspaceDrawerItem;
diff --git a/src/features/workspaces/components/WorkspaceItem.js b/src/features/workspaces/components/WorkspaceItem.js
new file mode 100644
index 000000000..b2c2a4830
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceItem.js
@@ -0,0 +1,42 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { intlShape } from 'react-intl';
4import { observer } from 'mobx-react';
5import classnames from 'classnames';
6import Workspace from '../models/Workspace';
7
8// const messages = defineMessages({});
9
10@observer
11class WorkspaceItem extends Component {
12 static propTypes = {
13 workspace: PropTypes.instanceOf(Workspace).isRequired,
14 onItemClick: PropTypes.func.isRequired,
15 };
16
17 static contextTypes = {
18 intl: intlShape,
19 };
20
21 render() {
22 const { workspace, onItemClick } = this.props;
23 // const { intl } = this.context;
24
25 return (
26 <tr
27 className={classnames({
28 'workspace-table__row': true,
29 })}
30 >
31 <td
32 className="workspace-table__column-name"
33 onClick={() => onItemClick(workspace)}
34 >
35 {workspace.name}
36 </td>
37 </tr>
38 );
39 }
40}
41
42export default WorkspaceItem;
diff --git a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
new file mode 100644
index 000000000..8aba5bbd9
--- /dev/null
+++ b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
@@ -0,0 +1,87 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import classnames from 'classnames';
6import { defineMessages, intlShape } from 'react-intl';
7
8import LoaderComponent from '../../../components/ui/Loader';
9import { workspaceStore } from '../index';
10
11const messages = defineMessages({
12 switchingTo: {
13 id: 'workspaces.switchingIndicator.switchingTo',
14 defaultMessage: '!!!Switching to',
15 },
16});
17
18const styles = theme => ({
19 wrapper: {
20 display: 'flex',
21 alignItems: 'flex-start',
22 position: 'absolute',
23 transition: 'width 0.5s ease',
24 width: '100%',
25 marginTop: '20px',
26 },
27 wrapperWhenDrawerIsOpen: {
28 width: `calc(100% - ${theme.workspaceDrawerWidth}px)`,
29 },
30 component: {
31 background: 'rgba(20, 20, 20, 0.4)',
32 padding: '10px 20px',
33 display: 'flex',
34 width: 'auto',
35 height: 'auto',
36 margin: [0, 'auto'],
37 borderRadius: 6,
38 alignItems: 'center',
39 zIndex: 200,
40 },
41 spinner: {
42 width: '40px',
43 marginRight: '5px',
44 },
45 message: {
46 fontSize: 16,
47 whiteSpace: 'nowrap',
48 },
49});
50
51@injectSheet(styles) @observer
52class WorkspaceSwitchingIndicator extends Component {
53 static propTypes = {
54 classes: PropTypes.object.isRequired,
55 };
56
57 static contextTypes = {
58 intl: intlShape,
59 };
60
61 render() {
62 const { classes } = this.props;
63 const { intl } = this.context;
64 const { isSwitchingWorkspace, isWorkspaceDrawerOpen, nextWorkspace } = workspaceStore;
65 if (!isSwitchingWorkspace) return null;
66 const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services';
67 return (
68 <div
69 className={classnames([
70 classes.wrapper,
71 isWorkspaceDrawerOpen ? classes.wrapperWhenDrawerIsOpen : null,
72 ])}
73 >
74 <div className={classes.component}>
75 <div className={classes.spinner}>
76 <LoaderComponent />
77 </div>
78 <p className={classes.message}>
79 {`${intl.formatMessage(messages.switchingTo)} ${nextWorkspaceName}`}
80 </p>
81 </div>
82 </div>
83 );
84 }
85}
86
87export default WorkspaceSwitchingIndicator;
diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js
new file mode 100644
index 000000000..b141dc960
--- /dev/null
+++ b/src/features/workspaces/components/WorkspacesDashboard.js
@@ -0,0 +1,187 @@
1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6import { Infobox } from '@meetfranz/ui';
7
8import Loader from '../../../components/ui/Loader';
9import WorkspaceItem from './WorkspaceItem';
10import CreateWorkspaceForm from './CreateWorkspaceForm';
11import Request from '../../../stores/lib/Request';
12import Appear from '../../../components/ui/effects/Appear';
13import { workspaceStore } from '../index';
14import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer';
15
16const messages = defineMessages({
17 headline: {
18 id: 'settings.workspaces.headline',
19 defaultMessage: '!!!Your workspaces',
20 },
21 noServicesAdded: {
22 id: 'settings.workspaces.noWorkspacesAdded',
23 defaultMessage: '!!!You haven\'t added any workspaces yet.',
24 },
25 workspacesRequestFailed: {
26 id: 'settings.workspaces.workspacesRequestFailed',
27 defaultMessage: '!!!Could not load your workspaces',
28 },
29 tryReloadWorkspaces: {
30 id: 'settings.workspaces.tryReloadWorkspaces',
31 defaultMessage: '!!!Try again',
32 },
33 updatedInfo: {
34 id: 'settings.workspaces.updatedInfo',
35 defaultMessage: '!!!Your changes have been saved',
36 },
37 deletedInfo: {
38 id: 'settings.workspaces.deletedInfo',
39 defaultMessage: '!!!Workspace has been deleted',
40 },
41 workspaceFeatureInfo: {
42 id: 'settings.workspaces.workspaceFeatureInfo',
43 defaultMessage: '!!!Info about workspace feature',
44 },
45 workspaceFeatureHeadline: {
46 id: 'settings.workspaces.workspaceFeatureHeadline',
47 defaultMessage: '!!!Less is More: Introducing Franz Workspaces',
48 },
49});
50
51const styles = () => ({
52 createForm: {
53 height: 'auto',
54 },
55 appear: {
56 height: 'auto',
57 },
58 premiumAnnouncement: {
59 padding: '20px',
60 backgroundColor: '#3498db',
61 marginLeft: '-20px',
62 marginBottom: '20px',
63 height: 'auto',
64 },
65});
66
67@injectSheet(styles) @observer
68class WorkspacesDashboard extends Component {
69 static propTypes = {
70 classes: PropTypes.object.isRequired,
71 getUserWorkspacesRequest: PropTypes.instanceOf(Request).isRequired,
72 createWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
73 deleteWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
74 updateWorkspaceRequest: PropTypes.instanceOf(Request).isRequired,
75 onCreateWorkspaceSubmit: PropTypes.func.isRequired,
76 onWorkspaceClick: PropTypes.func.isRequired,
77 workspaces: MobxPropTypes.arrayOrObservableArray.isRequired,
78 };
79
80 static contextTypes = {
81 intl: intlShape,
82 };
83
84 render() {
85 const {
86 classes,
87 getUserWorkspacesRequest,
88 createWorkspaceRequest,
89 deleteWorkspaceRequest,
90 updateWorkspaceRequest,
91 onCreateWorkspaceSubmit,
92 onWorkspaceClick,
93 workspaces,
94 } = this.props;
95 const { intl } = this.context;
96 return (
97 <div className="settings__main">
98 <div className="settings__header">
99 <h1>{intl.formatMessage(messages.headline)}</h1>
100 </div>
101 <div className="settings__body">
102
103 {/* ===== Workspace updated info ===== */}
104 {updateWorkspaceRequest.wasExecuted && updateWorkspaceRequest.result && (
105 <Appear className={classes.appear}>
106 <Infobox
107 type="success"
108 icon="mdiCheckboxMarkedCircleOutline"
109 dismissable
110 onUnmount={updateWorkspaceRequest.reset}
111 >
112 {intl.formatMessage(messages.updatedInfo)}
113 </Infobox>
114 </Appear>
115 )}
116
117 {/* ===== Workspace deleted info ===== */}
118 {deleteWorkspaceRequest.wasExecuted && deleteWorkspaceRequest.result && (
119 <Appear className={classes.appear}>
120 <Infobox
121 type="success"
122 icon="mdiCheckboxMarkedCircleOutline"
123 dismissable
124 onUnmount={deleteWorkspaceRequest.reset}
125 >
126 {intl.formatMessage(messages.deletedInfo)}
127 </Infobox>
128 </Appear>
129 )}
130
131 {workspaceStore.isPremiumUpgradeRequired && (
132 <div className={classes.premiumAnnouncement}>
133 <h2>{intl.formatMessage(messages.workspaceFeatureHeadline)}</h2>
134 <p>{intl.formatMessage(messages.workspaceFeatureInfo)}</p>
135 </div>
136 )}
137
138 <PremiumFeatureContainer
139 condition={workspaceStore.isPremiumFeature}
140 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'workspaces' }}
141 >
142 {/* ===== Create workspace form ===== */}
143 <div className={classes.createForm}>
144 <CreateWorkspaceForm
145 isSubmitting={createWorkspaceRequest.isExecuting}
146 onSubmit={onCreateWorkspaceSubmit}
147 />
148 </div>
149 </PremiumFeatureContainer>
150 {getUserWorkspacesRequest.isExecuting ? (
151 <Loader />
152 ) : (
153 <Fragment>
154 {/* ===== Workspace could not be loaded error ===== */}
155 {getUserWorkspacesRequest.error ? (
156 <Infobox
157 icon="alert"
158 type="danger"
159 ctaLabel={intl.formatMessage(messages.tryReloadWorkspaces)}
160 ctaLoading={getUserWorkspacesRequest.isExecuting}
161 ctaOnClick={getUserWorkspacesRequest.retry}
162 >
163 {intl.formatMessage(messages.workspacesRequestFailed)}
164 </Infobox>
165 ) : (
166 <table className="workspace-table">
167 {/* ===== Workspaces list ===== */}
168 <tbody>
169 {workspaces.map(workspace => (
170 <WorkspaceItem
171 key={workspace.id}
172 workspace={workspace}
173 onItemClick={w => onWorkspaceClick(w)}
174 />
175 ))}
176 </tbody>
177 </table>
178 )}
179 </Fragment>
180 )}
181 </div>
182 </div>
183 );
184 }
185}
186
187export default WorkspacesDashboard;
diff --git a/src/features/workspaces/containers/EditWorkspaceScreen.js b/src/features/workspaces/containers/EditWorkspaceScreen.js
new file mode 100644
index 000000000..248b40131
--- /dev/null
+++ b/src/features/workspaces/containers/EditWorkspaceScreen.js
@@ -0,0 +1,60 @@
1import React, { Component } from 'react';
2import { inject, observer } from 'mobx-react';
3import PropTypes from 'prop-types';
4
5import ErrorBoundary from '../../../components/util/ErrorBoundary';
6import EditWorkspaceForm from '../components/EditWorkspaceForm';
7import ServicesStore from '../../../stores/ServicesStore';
8import Workspace from '../models/Workspace';
9import { workspaceStore } from '../index';
10import { deleteWorkspaceRequest, updateWorkspaceRequest } from '../api';
11
12@inject('stores', 'actions') @observer
13class EditWorkspaceScreen extends Component {
14 static propTypes = {
15 actions: PropTypes.shape({
16 workspace: PropTypes.shape({
17 delete: PropTypes.func.isRequired,
18 }),
19 }).isRequired,
20 stores: PropTypes.shape({
21 services: PropTypes.instanceOf(ServicesStore).isRequired,
22 }).isRequired,
23 };
24
25 onDelete = () => {
26 const { workspaceBeingEdited } = workspaceStore;
27 const { actions } = this.props;
28 if (!workspaceBeingEdited) return null;
29 actions.workspaces.delete({ workspace: workspaceBeingEdited });
30 };
31
32 onSave = (values) => {
33 const { workspaceBeingEdited } = workspaceStore;
34 const { actions } = this.props;
35 const workspace = new Workspace(
36 Object.assign({}, workspaceBeingEdited, values),
37 );
38 actions.workspaces.update({ workspace });
39 };
40
41 render() {
42 const { workspaceBeingEdited } = workspaceStore;
43 const { stores } = this.props;
44 if (!workspaceBeingEdited) return null;
45 return (
46 <ErrorBoundary>
47 <EditWorkspaceForm
48 workspace={workspaceBeingEdited}
49 services={stores.services.all}
50 onDelete={this.onDelete}
51 onSave={this.onSave}
52 updateWorkspaceRequest={updateWorkspaceRequest}
53 deleteWorkspaceRequest={deleteWorkspaceRequest}
54 />
55 </ErrorBoundary>
56 );
57 }
58}
59
60export default EditWorkspaceScreen;
diff --git a/src/features/workspaces/containers/WorkspacesScreen.js b/src/features/workspaces/containers/WorkspacesScreen.js
new file mode 100644
index 000000000..2ab565fa1
--- /dev/null
+++ b/src/features/workspaces/containers/WorkspacesScreen.js
@@ -0,0 +1,42 @@
1import React, { Component } from 'react';
2import { inject, observer } from 'mobx-react';
3import PropTypes from 'prop-types';
4import WorkspacesDashboard from '../components/WorkspacesDashboard';
5import ErrorBoundary from '../../../components/util/ErrorBoundary';
6import { workspaceStore } from '../index';
7import {
8 createWorkspaceRequest,
9 deleteWorkspaceRequest,
10 getUserWorkspacesRequest,
11 updateWorkspaceRequest,
12} from '../api';
13
14@inject('actions') @observer
15class WorkspacesScreen extends Component {
16 static propTypes = {
17 actions: PropTypes.shape({
18 workspace: PropTypes.shape({
19 edit: PropTypes.func.isRequired,
20 }),
21 }).isRequired,
22 };
23
24 render() {
25 const { actions } = this.props;
26 return (
27 <ErrorBoundary>
28 <WorkspacesDashboard
29 workspaces={workspaceStore.workspaces}
30 getUserWorkspacesRequest={getUserWorkspacesRequest}
31 createWorkspaceRequest={createWorkspaceRequest}
32 deleteWorkspaceRequest={deleteWorkspaceRequest}
33 updateWorkspaceRequest={updateWorkspaceRequest}
34 onCreateWorkspaceSubmit={data => actions.workspaces.create(data)}
35 onWorkspaceClick={w => actions.workspaces.edit({ workspace: w })}
36 />
37 </ErrorBoundary>
38 );
39 }
40}
41
42export default WorkspacesScreen;
diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.js
new file mode 100644
index 000000000..524a83e3c
--- /dev/null
+++ b/src/features/workspaces/index.js
@@ -0,0 +1,36 @@
1import { reaction } from 'mobx';
2import WorkspacesStore from './store';
3import { resetApiRequests } from './api';
4
5const debug = require('debug')('Franz:feature:workspaces');
6
7export const GA_CATEGORY_WORKSPACES = 'Workspaces';
8
9export const workspaceStore = new WorkspacesStore();
10
11export default function initWorkspaces(stores, actions) {
12 stores.workspaces = workspaceStore;
13 const { features, user } = stores;
14
15 // Toggle workspace feature
16 reaction(
17 () => (
18 features.features.isWorkspaceEnabled && (
19 !features.features.isWorkspacePremiumFeature || user.data.isPremium
20 )
21 ),
22 (isEnabled) => {
23 if (isEnabled && !workspaceStore.isFeatureActive) {
24 debug('Initializing `workspaces` feature');
25 workspaceStore.start(stores, actions);
26 } else if (workspaceStore.isFeatureActive) {
27 debug('Disabling `workspaces` feature');
28 workspaceStore.stop();
29 resetApiRequests();
30 }
31 },
32 {
33 fireImmediately: true,
34 },
35 );
36}
diff --git a/src/features/workspaces/models/Workspace.js b/src/features/workspaces/models/Workspace.js
new file mode 100644
index 000000000..6c73d7095
--- /dev/null
+++ b/src/features/workspaces/models/Workspace.js
@@ -0,0 +1,25 @@
1import { observable } from 'mobx';
2
3export default class Workspace {
4 id = null;
5
6 @observable name = null;
7
8 @observable order = null;
9
10 @observable services = [];
11
12 @observable userId = null;
13
14 constructor(data) {
15 if (!data.id) {
16 throw Error('Workspace requires Id');
17 }
18
19 this.id = data.id;
20 this.name = data.name;
21 this.order = data.order;
22 this.services.replace(data.services);
23 this.userId = data.userId;
24 }
25}
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js
new file mode 100644
index 000000000..712945bdc
--- /dev/null
+++ b/src/features/workspaces/store.js
@@ -0,0 +1,198 @@
1import {
2 computed,
3 observable,
4 action,
5} from 'mobx';
6import { matchRoute } from '../../helpers/routing-helpers';
7import { workspaceActions } from './actions';
8import { FeatureStore } from '../utils/FeatureStore';
9import {
10 createWorkspaceRequest,
11 deleteWorkspaceRequest,
12 getUserWorkspacesRequest,
13 updateWorkspaceRequest,
14} from './api';
15
16const debug = require('debug')('Franz:feature:workspaces:store');
17
18export default class WorkspacesStore extends FeatureStore {
19 @observable isFeatureEnabled = false;
20
21 @observable isPremiumFeature = true;
22
23 @observable isFeatureActive = false;
24
25 @observable activeWorkspace = null;
26
27 @observable nextWorkspace = null;
28
29 @observable workspaceBeingEdited = null;
30
31 @observable isSwitchingWorkspace = false;
32
33 @observable isWorkspaceDrawerOpen = false;
34
35 @computed get workspaces() {
36 if (!this.isFeatureActive) return [];
37 return getUserWorkspacesRequest.result || [];
38 }
39
40 @computed get isPremiumUpgradeRequired() {
41 return this.isFeatureEnabled && !this.isFeatureActive;
42 }
43
44 start(stores, actions) {
45 debug('WorkspacesStore::start');
46 this.stores = stores;
47 this.actions = actions;
48
49 this._listenToActions([
50 [workspaceActions.edit, this._edit],
51 [workspaceActions.create, this._create],
52 [workspaceActions.delete, this._delete],
53 [workspaceActions.update, this._update],
54 [workspaceActions.activate, this._setActiveWorkspace],
55 [workspaceActions.deactivate, this._deactivateActiveWorkspace],
56 [workspaceActions.toggleWorkspaceDrawer, this._toggleWorkspaceDrawer],
57 [workspaceActions.openWorkspaceSettings, this._openWorkspaceSettings],
58 ]);
59
60 this._startReactions([
61 this._setWorkspaceBeingEditedReaction,
62 this._setActiveServiceOnWorkspaceSwitchReaction,
63 this._setFeatureEnabledReaction,
64 this._setIsPremiumFeatureReaction,
65 ]);
66
67 getUserWorkspacesRequest.execute();
68 this.isFeatureActive = true;
69 }
70
71 stop() {
72 debug('WorkspacesStore::stop');
73 this.isFeatureActive = false;
74 this.activeWorkspace = null;
75 this.nextWorkspace = null;
76 this.workspaceBeingEdited = null;
77 this.isSwitchingWorkspace = false;
78 this.isWorkspaceDrawerOpen = false;
79 }
80
81 filterServicesByActiveWorkspace = (services) => {
82 const { activeWorkspace, isFeatureActive } = this;
83
84 if (!isFeatureActive) return services;
85 if (activeWorkspace) {
86 return services.filter(s => (
87 activeWorkspace.services.includes(s.id)
88 ));
89 }
90 return services;
91 };
92
93 // ========== PRIVATE ========= //
94
95 _getWorkspaceById = id => this.workspaces.find(w => w.id === id);
96
97 // Actions
98
99 @action _edit = ({ workspace }) => {
100 this.stores.router.push(`/settings/workspaces/edit/${workspace.id}`);
101 };
102
103 @action _create = async ({ name }) => {
104 try {
105 const workspace = await createWorkspaceRequest.execute(name);
106 await getUserWorkspacesRequest.result.push(workspace);
107 this._edit({ workspace });
108 } catch (error) {
109 throw error;
110 }
111 };
112
113 @action _delete = async ({ workspace }) => {
114 try {
115 await deleteWorkspaceRequest.execute(workspace);
116 await getUserWorkspacesRequest.result.remove(workspace);
117 this.stores.router.push('/settings/workspaces');
118 } catch (error) {
119 throw error;
120 }
121 };
122
123 @action _update = async ({ workspace }) => {
124 try {
125 await updateWorkspaceRequest.execute(workspace);
126 // Path local result optimistically
127 const localWorkspace = this._getWorkspaceById(workspace.id);
128 Object.assign(localWorkspace, workspace);
129 this.stores.router.push('/settings/workspaces');
130 } catch (error) {
131 throw error;
132 }
133 };
134
135 @action _setActiveWorkspace = ({ workspace }) => {
136 // Indicate that we are switching to another workspace
137 this.isSwitchingWorkspace = true;
138 this.nextWorkspace = workspace;
139 // Delay switching to next workspace so that the services loading does not drag down UI
140 setTimeout(() => { this.activeWorkspace = workspace; }, 100);
141 // Indicate that we are done switching to the next workspace
142 setTimeout(() => {
143 this.isSwitchingWorkspace = false;
144 this.nextWorkspace = null;
145 }, 1000);
146 };
147
148 @action _deactivateActiveWorkspace = () => {
149 // Indicate that we are switching to default workspace
150 this.isSwitchingWorkspace = true;
151 this.nextWorkspace = null;
152 // Delay switching to next workspace so that the services loading does not drag down UI
153 setTimeout(() => { this.activeWorkspace = null; }, 100);
154 // Indicate that we are done switching to the default workspace
155 setTimeout(() => { this.isSwitchingWorkspace = false; }, 1000);
156 };
157
158 @action _toggleWorkspaceDrawer = () => {
159 this.isWorkspaceDrawerOpen = !this.isWorkspaceDrawerOpen;
160 };
161
162 @action _openWorkspaceSettings = () => {
163 this.actions.ui.openSettings({ path: 'workspaces' });
164 };
165
166 // Reactions
167
168 _setFeatureEnabledReaction = () => {
169 const { isWorkspaceEnabled } = this.stores.features.features;
170 this.isFeatureEnabled = isWorkspaceEnabled;
171 };
172
173 _setIsPremiumFeatureReaction = () => {
174 const { isWorkspacePremiumFeature } = this.stores.features.features;
175 this.isPremiumFeature = isWorkspacePremiumFeature;
176 };
177
178 _setWorkspaceBeingEditedReaction = () => {
179 const { pathname } = this.stores.router.location;
180 const match = matchRoute('/settings/workspaces/edit/:id', pathname);
181 if (match) {
182 this.workspaceBeingEdited = this._getWorkspaceById(match.id);
183 }
184 };
185
186 _setActiveServiceOnWorkspaceSwitchReaction = () => {
187 if (!this.isFeatureActive) return;
188 if (this.activeWorkspace) {
189 const services = this.stores.services.allDisplayed;
190 const activeService = services.find(s => s.isActive);
191 const workspaceServices = this.filterServicesByActiveWorkspace(services);
192 const isActiveServiceInWorkspace = workspaceServices.includes(activeService);
193 if (!isActiveServiceInWorkspace) {
194 this.actions.service.setActive({ serviceId: workspaceServices[0].id });
195 }
196 }
197 };
198}
diff --git a/src/features/workspaces/styles/workspaces-table.scss b/src/features/workspaces/styles/workspaces-table.scss
new file mode 100644
index 000000000..6d0e7b4f5
--- /dev/null
+++ b/src/features/workspaces/styles/workspaces-table.scss
@@ -0,0 +1,53 @@
1@import '../../../styles/config';
2
3.theme__dark .workspace-table {
4 .workspace-table__column-info .mdi { color: $dark-theme-gray-lightest; }
5
6 .workspace-table__row {
7 border-bottom: 1px solid $dark-theme-gray-darker;
8
9 &:hover { background: $dark-theme-gray-darker; }
10 &.workspace-table__row--disabled { color: $dark-theme-gray; }
11 }
12}
13
14.workspace-table {
15 width: 100%;
16
17 .workspace-table__toggle {
18 width: 60px;
19
20 .franz-form__field {
21 margin-bottom: 0;
22 }
23 }
24
25 .workspace-table__column-action {
26 width: 40px
27 }
28
29 .workspace-table__column-info {
30 width: 40px;
31
32 .mdi {
33 color: $theme-gray-light;
34 display: block;
35 font-size: 18px;
36 }
37 }
38
39 .workspace-table__row {
40 border-bottom: 1px solid $theme-gray-lightest;
41
42 &:hover {
43 cursor: initial;
44 background: $theme-gray-lightest;
45 }
46
47 &.workspace-table__row--disabled {
48 color: $theme-gray-light;
49 }
50 }
51
52 td { padding: 10px; }
53}
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json
index 2560a5add..06a03db65 100644
--- a/src/i18n/locales/de.json
+++ b/src/i18n/locales/de.json
@@ -87,6 +87,9 @@
87 "menu.window" : "Fenster", 87 "menu.window" : "Fenster",
88 "menu.window.close" : "Schließen", 88 "menu.window.close" : "Schließen",
89 "menu.window.minimize" : "Minimieren", 89 "menu.window.minimize" : "Minimieren",
90 "menu.workspaces": "Workspaces",
91 "menu.workspaces.defaultWorkspace": "All services",
92 "menu.workspaces.addNewWorkspace": "Add New Workspace",
90 "password.email.label" : "E-Mail Adresse", 93 "password.email.label" : "E-Mail Adresse",
91 "password.headline" : "Passwort zurücksetzen", 94 "password.headline" : "Passwort zurücksetzen",
92 "password.link.login" : "An Deinem Konto anmelden", 95 "password.link.login" : "An Deinem Konto anmelden",
@@ -169,6 +172,7 @@
169 "settings.navigation.logout" : "Abmelden", 172 "settings.navigation.logout" : "Abmelden",
170 "settings.navigation.settings" : "Einstellungen", 173 "settings.navigation.settings" : "Einstellungen",
171 "settings.navigation.yourServices" : "Deine Dienste", 174 "settings.navigation.yourServices" : "Deine Dienste",
175 "settings.navigation.yourWorkspaces": "Deine Workspaces",
172 "settings.recipes.all" : "Alle Dienste", 176 "settings.recipes.all" : "Alle Dienste",
173 "settings.recipes.dev" : "Entwicklung", 177 "settings.recipes.dev" : "Entwicklung",
174 "settings.recipes.headline" : "Verfügbare Dienste", 178 "settings.recipes.headline" : "Verfügbare Dienste",
@@ -226,6 +230,14 @@
226 "settings.services.tooltip.isMuted" : "Alle Töne sind deaktiviert", 230 "settings.services.tooltip.isMuted" : "Alle Töne sind deaktiviert",
227 "settings.services.tooltip.notificationsDisabled" : "Benachrichtigungen deaktiviert", 231 "settings.services.tooltip.notificationsDisabled" : "Benachrichtigungen deaktiviert",
228 "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert", 232 "settings.services.updatedInfo" : "Deine Änderungen wurden gespeichert",
233 "settings.workspaces.headline": "Deine Workspaces",
234 "settings.workspace.add.form.submitButton": "Workspace erstellen",
235 "settings.workspace.add.form.name": "Name",
236 "settings.workspace.form.yourWorkspaces": "Deine Workspaces",
237 "settings.workspace.form.name": "Name",
238 "settings.workspace.form.buttonDelete": "Workspace löschen",
239 "settings.workspace.form.buttonSave": "Workspace speichern",
240 "settings.workspace.form.servicesInWorkspaceHeadline": "Services in diesem Workspace",
229 "settings.user.form.accountType.company" : "Firma", 241 "settings.user.form.accountType.company" : "Firma",
230 "settings.user.form.accountType.individual" : "Einzelperson", 242 "settings.user.form.accountType.individual" : "Einzelperson",
231 "settings.user.form.accountType.label" : "Konto-Typ", 243 "settings.user.form.accountType.label" : "Konto-Typ",
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index 0641c510c..5e3988781 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -625,78 +625,78 @@
625 "defaultMessage": "!!!Your services have been updated.", 625 "defaultMessage": "!!!Your services have been updated.",
626 "end": { 626 "end": {
627 "column": 3, 627 "column": 3,
628 "line": 25 628 "line": 28
629 }, 629 },
630 "file": "src/components/layout/AppLayout.js", 630 "file": "src/components/layout/AppLayout.js",
631 "id": "infobar.servicesUpdated", 631 "id": "infobar.servicesUpdated",
632 "start": { 632 "start": {
633 "column": 19, 633 "column": 19,
634 "line": 22 634 "line": 25
635 } 635 }
636 }, 636 },
637 { 637 {
638 "defaultMessage": "!!!A new update for Franz is available.", 638 "defaultMessage": "!!!A new update for Franz is available.",
639 "end": { 639 "end": {
640 "column": 3, 640 "column": 3,
641 "line": 29 641 "line": 32
642 }, 642 },
643 "file": "src/components/layout/AppLayout.js", 643 "file": "src/components/layout/AppLayout.js",
644 "id": "infobar.updateAvailable", 644 "id": "infobar.updateAvailable",
645 "start": { 645 "start": {
646 "column": 19, 646 "column": 19,
647 "line": 26 647 "line": 29
648 } 648 }
649 }, 649 },
650 { 650 {
651 "defaultMessage": "!!!Reload services", 651 "defaultMessage": "!!!Reload services",
652 "end": { 652 "end": {
653 "column": 3, 653 "column": 3,
654 "line": 33 654 "line": 36
655 }, 655 },
656 "file": "src/components/layout/AppLayout.js", 656 "file": "src/components/layout/AppLayout.js",
657 "id": "infobar.buttonReloadServices", 657 "id": "infobar.buttonReloadServices",
658 "start": { 658 "start": {
659 "column": 24, 659 "column": 24,
660 "line": 30 660 "line": 33
661 } 661 }
662 }, 662 },
663 { 663 {
664 "defaultMessage": "!!!Changelog", 664 "defaultMessage": "!!!Changelog",
665 "end": { 665 "end": {
666 "column": 3, 666 "column": 3,
667 "line": 37 667 "line": 40
668 }, 668 },
669 "file": "src/components/layout/AppLayout.js", 669 "file": "src/components/layout/AppLayout.js",
670 "id": "infobar.buttonChangelog", 670 "id": "infobar.buttonChangelog",
671 "start": { 671 "start": {
672 "column": 13, 672 "column": 13,
673 "line": 34 673 "line": 37
674 } 674 }
675 }, 675 },
676 { 676 {
677 "defaultMessage": "!!!Restart & install update", 677 "defaultMessage": "!!!Restart & install update",
678 "end": { 678 "end": {
679 "column": 3, 679 "column": 3,
680 "line": 41 680 "line": 44
681 }, 681 },
682 "file": "src/components/layout/AppLayout.js", 682 "file": "src/components/layout/AppLayout.js",
683 "id": "infobar.buttonInstallUpdate", 683 "id": "infobar.buttonInstallUpdate",
684 "start": { 684 "start": {
685 "column": 23, 685 "column": 23,
686 "line": 38 686 "line": 41
687 } 687 }
688 }, 688 },
689 { 689 {
690 "defaultMessage": "!!!Could not load services and user information", 690 "defaultMessage": "!!!Could not load services and user information",
691 "end": { 691 "end": {
692 "column": 3, 692 "column": 3,
693 "line": 45 693 "line": 48
694 }, 694 },
695 "file": "src/components/layout/AppLayout.js", 695 "file": "src/components/layout/AppLayout.js",
696 "id": "infobar.requiredRequestsFailed", 696 "id": "infobar.requiredRequestsFailed",
697 "start": { 697 "start": {
698 "column": 26, 698 "column": 26,
699 "line": 42 699 "line": 45
700 } 700 }
701 } 701 }
702 ], 702 ],
@@ -708,52 +708,78 @@
708 "defaultMessage": "!!!Settings", 708 "defaultMessage": "!!!Settings",
709 "end": { 709 "end": {
710 "column": 3, 710 "column": 3,
711 "line": 14 711 "line": 16
712 }, 712 },
713 "file": "src/components/layout/Sidebar.js", 713 "file": "src/components/layout/Sidebar.js",
714 "id": "sidebar.settings", 714 "id": "sidebar.settings",
715 "start": { 715 "start": {
716 "column": 12, 716 "column": 12,
717 "line": 11 717 "line": 13
718 } 718 }
719 }, 719 },
720 { 720 {
721 "defaultMessage": "!!!Add new service", 721 "defaultMessage": "!!!Add new service",
722 "end": { 722 "end": {
723 "column": 3, 723 "column": 3,
724 "line": 18 724 "line": 20
725 }, 725 },
726 "file": "src/components/layout/Sidebar.js", 726 "file": "src/components/layout/Sidebar.js",
727 "id": "sidebar.addNewService", 727 "id": "sidebar.addNewService",
728 "start": { 728 "start": {
729 "column": 17, 729 "column": 17,
730 "line": 15 730 "line": 17
731 } 731 }
732 }, 732 },
733 { 733 {
734 "defaultMessage": "!!!Disable notifications & audio", 734 "defaultMessage": "!!!Disable notifications & audio",
735 "end": { 735 "end": {
736 "column": 3, 736 "column": 3,
737 "line": 22 737 "line": 24
738 }, 738 },
739 "file": "src/components/layout/Sidebar.js", 739 "file": "src/components/layout/Sidebar.js",
740 "id": "sidebar.muteApp", 740 "id": "sidebar.muteApp",
741 "start": { 741 "start": {
742 "column": 8, 742 "column": 8,
743 "line": 19 743 "line": 21
744 } 744 }
745 }, 745 },
746 { 746 {
747 "defaultMessage": "!!!Enable notifications & audio", 747 "defaultMessage": "!!!Enable notifications & audio",
748 "end": { 748 "end": {
749 "column": 3, 749 "column": 3,
750 "line": 26 750 "line": 28
751 }, 751 },
752 "file": "src/components/layout/Sidebar.js", 752 "file": "src/components/layout/Sidebar.js",
753 "id": "sidebar.unmuteApp", 753 "id": "sidebar.unmuteApp",
754 "start": { 754 "start": {
755 "column": 10, 755 "column": 10,
756 "line": 23 756 "line": 25
757 }
758 },
759 {
760 "defaultMessage": "!!!Open workspace drawer",
761 "end": {
762 "column": 3,
763 "line": 32
764 },
765 "file": "src/components/layout/Sidebar.js",
766 "id": "sidebar.openWorkspaceDrawer",
767 "start": {
768 "column": 23,
769 "line": 29
770 }
771 },
772 {
773 "defaultMessage": "!!!Close workspace drawer",
774 "end": {
775 "column": 3,
776 "line": 36
777 },
778 "file": "src/components/layout/Sidebar.js",
779 "id": "sidebar.closeWorkspaceDrawer",
780 "start": {
781 "column": 24,
782 "line": 33
757 } 783 }
758 } 784 }
759 ], 785 ],
@@ -1276,78 +1302,91 @@
1276 "defaultMessage": "!!!Available services", 1302 "defaultMessage": "!!!Available services",
1277 "end": { 1303 "end": {
1278 "column": 3, 1304 "column": 3,
1279 "line": 12 1305 "line": 14
1280 }, 1306 },
1281 "file": "src/components/settings/navigation/SettingsNavigation.js", 1307 "file": "src/components/settings/navigation/SettingsNavigation.js",
1282 "id": "settings.navigation.availableServices", 1308 "id": "settings.navigation.availableServices",
1283 "start": { 1309 "start": {
1284 "column": 21, 1310 "column": 21,
1285 "line": 9 1311 "line": 11
1286 } 1312 }
1287 }, 1313 },
1288 { 1314 {
1289 "defaultMessage": "!!!Your services", 1315 "defaultMessage": "!!!Your services",
1290 "end": { 1316 "end": {
1291 "column": 3, 1317 "column": 3,
1292 "line": 16 1318 "line": 18
1293 }, 1319 },
1294 "file": "src/components/settings/navigation/SettingsNavigation.js", 1320 "file": "src/components/settings/navigation/SettingsNavigation.js",
1295 "id": "settings.navigation.yourServices", 1321 "id": "settings.navigation.yourServices",
1296 "start": { 1322 "start": {
1297 "column": 16, 1323 "column": 16,
1298 "line": 13 1324 "line": 15
1325 }
1326 },
1327 {
1328 "defaultMessage": "!!!Your workspaces",
1329 "end": {
1330 "column": 3,
1331 "line": 22
1332 },
1333 "file": "src/components/settings/navigation/SettingsNavigation.js",
1334 "id": "settings.navigation.yourWorkspaces",
1335 "start": {
1336 "column": 18,
1337 "line": 19
1299 } 1338 }
1300 }, 1339 },
1301 { 1340 {
1302 "defaultMessage": "!!!Account", 1341 "defaultMessage": "!!!Account",
1303 "end": { 1342 "end": {
1304 "column": 3, 1343 "column": 3,
1305 "line": 20 1344 "line": 26
1306 }, 1345 },
1307 "file": "src/components/settings/navigation/SettingsNavigation.js", 1346 "file": "src/components/settings/navigation/SettingsNavigation.js",
1308 "id": "settings.navigation.account", 1347 "id": "settings.navigation.account",
1309 "start": { 1348 "start": {
1310 "column": 11, 1349 "column": 11,
1311 "line": 17 1350 "line": 23
1312 } 1351 }
1313 }, 1352 },
1314 { 1353 {
1315 "defaultMessage": "!!!Settings", 1354 "defaultMessage": "!!!Settings",
1316 "end": { 1355 "end": {
1317 "column": 3, 1356 "column": 3,
1318 "line": 24 1357 "line": 30
1319 }, 1358 },
1320 "file": "src/components/settings/navigation/SettingsNavigation.js", 1359 "file": "src/components/settings/navigation/SettingsNavigation.js",
1321 "id": "settings.navigation.settings", 1360 "id": "settings.navigation.settings",
1322 "start": { 1361 "start": {
1323 "column": 12, 1362 "column": 12,
1324 "line": 21 1363 "line": 27
1325 } 1364 }
1326 }, 1365 },
1327 { 1366 {
1328 "defaultMessage": "!!!Invite Friends", 1367 "defaultMessage": "!!!Invite Friends",
1329 "end": { 1368 "end": {
1330 "column": 3, 1369 "column": 3,
1331 "line": 28 1370 "line": 34
1332 }, 1371 },
1333 "file": "src/components/settings/navigation/SettingsNavigation.js", 1372 "file": "src/components/settings/navigation/SettingsNavigation.js",
1334 "id": "settings.navigation.inviteFriends", 1373 "id": "settings.navigation.inviteFriends",
1335 "start": { 1374 "start": {
1336 "column": 17, 1375 "column": 17,
1337 "line": 25 1376 "line": 31
1338 } 1377 }
1339 }, 1378 },
1340 { 1379 {
1341 "defaultMessage": "!!!Logout", 1380 "defaultMessage": "!!!Logout",
1342 "end": { 1381 "end": {
1343 "column": 3, 1382 "column": 3,
1344 "line": 32 1383 "line": 38
1345 }, 1384 },
1346 "file": "src/components/settings/navigation/SettingsNavigation.js", 1385 "file": "src/components/settings/navigation/SettingsNavigation.js",
1347 "id": "settings.navigation.logout", 1386 "id": "settings.navigation.logout",
1348 "start": { 1387 "start": {
1349 "column": 10, 1388 "column": 10,
1350 "line": 29 1389 "line": 35
1351 } 1390 }
1352 } 1391 }
1353 ], 1392 ],
@@ -2496,13 +2535,13 @@
2496 "defaultMessage": "!!!Upgrade account", 2535 "defaultMessage": "!!!Upgrade account",
2497 "end": { 2536 "end": {
2498 "column": 3, 2537 "column": 3,
2499 "line": 17 2538 "line": 18
2500 }, 2539 },
2501 "file": "src/components/ui/PremiumFeatureContainer/index.js", 2540 "file": "src/components/ui/PremiumFeatureContainer/index.js",
2502 "id": "premiumFeature.button.upgradeAccount", 2541 "id": "premiumFeature.button.upgradeAccount",
2503 "start": { 2542 "start": {
2504 "column": 10, 2543 "column": 10,
2505 "line": 14 2544 "line": 15
2506 } 2545 }
2507 } 2546 }
2508 ], 2547 ],
@@ -2511,6 +2550,24 @@
2511 { 2550 {
2512 "descriptors": [ 2551 "descriptors": [
2513 { 2552 {
2553 "defaultMessage": "!!!Loading",
2554 "end": {
2555 "column": 3,
2556 "line": 14
2557 },
2558 "file": "src/components/ui/WebviewLoader/index.js",
2559 "id": "service.webviewLoader.loading",
2560 "start": {
2561 "column": 11,
2562 "line": 11
2563 }
2564 }
2565 ],
2566 "path": "src/components/ui/WebviewLoader/index.json"
2567 },
2568 {
2569 "descriptors": [
2570 {
2514 "defaultMessage": "!!!Something went wrong.", 2571 "defaultMessage": "!!!Something went wrong.",
2515 "end": { 2572 "end": {
2516 "column": 3, 2573 "column": 3,
@@ -3165,6 +3222,322 @@
3165 { 3222 {
3166 "descriptors": [ 3223 "descriptors": [
3167 { 3224 {
3225 "defaultMessage": "!!!Create workspace",
3226 "end": {
3227 "column": 3,
3228 "line": 16
3229 },
3230 "file": "src/features/workspaces/components/CreateWorkspaceForm.js",
3231 "id": "settings.workspace.add.form.submitButton",
3232 "start": {
3233 "column": 16,
3234 "line": 13
3235 }
3236 },
3237 {
3238 "defaultMessage": "!!!Name",
3239 "end": {
3240 "column": 3,
3241 "line": 20
3242 },
3243 "file": "src/features/workspaces/components/CreateWorkspaceForm.js",
3244 "id": "settings.workspace.add.form.name",
3245 "start": {
3246 "column": 8,
3247 "line": 17
3248 }
3249 }
3250 ],
3251 "path": "src/features/workspaces/components/CreateWorkspaceForm.json"
3252 },
3253 {
3254 "descriptors": [
3255 {
3256 "defaultMessage": "!!!Delete workspace",
3257 "end": {
3258 "column": 3,
3259 "line": 22
3260 },
3261 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3262 "id": "settings.workspace.form.buttonDelete",
3263 "start": {
3264 "column": 16,
3265 "line": 19
3266 }
3267 },
3268 {
3269 "defaultMessage": "!!!Save workspace",
3270 "end": {
3271 "column": 3,
3272 "line": 26
3273 },
3274 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3275 "id": "settings.workspace.form.buttonSave",
3276 "start": {
3277 "column": 14,
3278 "line": 23
3279 }
3280 },
3281 {
3282 "defaultMessage": "!!!Name",
3283 "end": {
3284 "column": 3,
3285 "line": 30
3286 },
3287 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3288 "id": "settings.workspace.form.name",
3289 "start": {
3290 "column": 8,
3291 "line": 27
3292 }
3293 },
3294 {
3295 "defaultMessage": "!!!Your workspaces",
3296 "end": {
3297 "column": 3,
3298 "line": 34
3299 },
3300 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3301 "id": "settings.workspace.form.yourWorkspaces",
3302 "start": {
3303 "column": 18,
3304 "line": 31
3305 }
3306 },
3307 {
3308 "defaultMessage": "!!!Services in this Workspace",
3309 "end": {
3310 "column": 3,
3311 "line": 38
3312 },
3313 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
3314 "id": "settings.workspace.form.servicesInWorkspaceHeadline",
3315 "start": {
3316 "column": 31,
3317 "line": 35
3318 }
3319 }
3320 ],
3321 "path": "src/features/workspaces/components/EditWorkspaceForm.json"
3322 },
3323 {
3324 "descriptors": [
3325 {
3326 "defaultMessage": "!!!Workspaces",
3327 "end": {
3328 "column": 3,
3329 "line": 19
3330 },
3331 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3332 "id": "workspaceDrawer.headline",
3333 "start": {
3334 "column": 12,
3335 "line": 16
3336 }
3337 },
3338 {
3339 "defaultMessage": "!!!All services",
3340 "end": {
3341 "column": 3,
3342 "line": 23
3343 },
3344 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3345 "id": "workspaceDrawer.allServices",
3346 "start": {
3347 "column": 15,
3348 "line": 20
3349 }
3350 },
3351 {
3352 "defaultMessage": "!!!Add workspace",
3353 "end": {
3354 "column": 3,
3355 "line": 27
3356 },
3357 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3358 "id": "workspaceDrawer.addWorkspaceTooltip",
3359 "start": {
3360 "column": 23,
3361 "line": 24
3362 }
3363 },
3364 {
3365 "defaultMessage": "!!!Info about workspace feature",
3366 "end": {
3367 "column": 3,
3368 "line": 31
3369 },
3370 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3371 "id": "workspaceDrawer.workspaceFeatureInfo",
3372 "start": {
3373 "column": 24,
3374 "line": 28
3375 }
3376 },
3377 {
3378 "defaultMessage": "!!!Create your first workspace",
3379 "end": {
3380 "column": 3,
3381 "line": 35
3382 },
3383 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3384 "id": "workspaceDrawer.premiumCtaButtonLabel",
3385 "start": {
3386 "column": 25,
3387 "line": 32
3388 }
3389 }
3390 ],
3391 "path": "src/features/workspaces/components/WorkspaceDrawer.json"
3392 },
3393 {
3394 "descriptors": [
3395 {
3396 "defaultMessage": "!!!No services added yet",
3397 "end": {
3398 "column": 3,
3399 "line": 12
3400 },
3401 "file": "src/features/workspaces/components/WorkspaceDrawerItem.js",
3402 "id": "workspaceDrawer.item.noServicesAddedYet",
3403 "start": {
3404 "column": 22,
3405 "line": 9
3406 }
3407 }
3408 ],
3409 "path": "src/features/workspaces/components/WorkspaceDrawerItem.json"
3410 },
3411 {
3412 "descriptors": [
3413 {
3414 "defaultMessage": "!!!Your workspaces",
3415 "end": {
3416 "column": 3,
3417 "line": 20
3418 },
3419 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3420 "id": "settings.workspaces.headline",
3421 "start": {
3422 "column": 12,
3423 "line": 17
3424 }
3425 },
3426 {
3427 "defaultMessage": "!!!You haven't added any workspaces yet.",
3428 "end": {
3429 "column": 3,
3430 "line": 24
3431 },
3432 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3433 "id": "settings.workspaces.noWorkspacesAdded",
3434 "start": {
3435 "column": 19,
3436 "line": 21
3437 }
3438 },
3439 {
3440 "defaultMessage": "!!!Could not load your workspaces",
3441 "end": {
3442 "column": 3,
3443 "line": 28
3444 },
3445 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3446 "id": "settings.workspaces.workspacesRequestFailed",
3447 "start": {
3448 "column": 27,
3449 "line": 25
3450 }
3451 },
3452 {
3453 "defaultMessage": "!!!Try again",
3454 "end": {
3455 "column": 3,
3456 "line": 32
3457 },
3458 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3459 "id": "settings.workspaces.tryReloadWorkspaces",
3460 "start": {
3461 "column": 23,
3462 "line": 29
3463 }
3464 },
3465 {
3466 "defaultMessage": "!!!Your changes have been saved",
3467 "end": {
3468 "column": 3,
3469 "line": 36
3470 },
3471 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3472 "id": "settings.workspaces.updatedInfo",
3473 "start": {
3474 "column": 15,
3475 "line": 33
3476 }
3477 },
3478 {
3479 "defaultMessage": "!!!Workspace has been deleted",
3480 "end": {
3481 "column": 3,
3482 "line": 40
3483 },
3484 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3485 "id": "settings.workspaces.deletedInfo",
3486 "start": {
3487 "column": 15,
3488 "line": 37
3489 }
3490 },
3491 {
3492 "defaultMessage": "!!!Info about workspace feature",
3493 "end": {
3494 "column": 3,
3495 "line": 44
3496 },
3497 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3498 "id": "settings.workspaces.workspaceFeatureInfo",
3499 "start": {
3500 "column": 24,
3501 "line": 41
3502 }
3503 },
3504 {
3505 "defaultMessage": "!!!Less is More: Introducing Franz Workspaces",
3506 "end": {
3507 "column": 3,
3508 "line": 48
3509 },
3510 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3511 "id": "settings.workspaces.workspaceFeatureHeadline",
3512 "start": {
3513 "column": 28,
3514 "line": 45
3515 }
3516 }
3517 ],
3518 "path": "src/features/workspaces/components/WorkspacesDashboard.json"
3519 },
3520 {
3521 "descriptors": [
3522 {
3523 "defaultMessage": "!!!Switching to",
3524 "end": {
3525 "column": 3,
3526 "line": 15
3527 },
3528 "file": "src/features/workspaces/components/WorkspaceSwitchingIndicator.js",
3529 "id": "workspaces.switchingIndicator.switchingTo",
3530 "start": {
3531 "column": 15,
3532 "line": 12
3533 }
3534 }
3535 ],
3536 "path": "src/features/workspaces/components/WorkspaceSwitchingIndicator.json"
3537 },
3538 {
3539 "descriptors": [
3540 {
3168 "defaultMessage": "!!!Field is required", 3541 "defaultMessage": "!!!Field is required",
3169 "end": { 3542 "end": {
3170 "column": 3, 3543 "column": 3,
@@ -3321,611 +3694,676 @@
3321 "defaultMessage": "!!!Edit", 3694 "defaultMessage": "!!!Edit",
3322 "end": { 3695 "end": {
3323 "column": 3, 3696 "column": 3,
3324 "line": 13 3697 "line": 16
3325 }, 3698 },
3326 "file": "src/lib/Menu.js", 3699 "file": "src/lib/Menu.js",
3327 "id": "menu.edit", 3700 "id": "menu.edit",
3328 "start": { 3701 "start": {
3329 "column": 8, 3702 "column": 8,
3330 "line": 10 3703 "line": 13
3331 } 3704 }
3332 }, 3705 },
3333 { 3706 {
3334 "defaultMessage": "!!!Undo", 3707 "defaultMessage": "!!!Undo",
3335 "end": { 3708 "end": {
3336 "column": 3, 3709 "column": 3,
3337 "line": 17 3710 "line": 20
3338 }, 3711 },
3339 "file": "src/lib/Menu.js", 3712 "file": "src/lib/Menu.js",
3340 "id": "menu.edit.undo", 3713 "id": "menu.edit.undo",
3341 "start": { 3714 "start": {
3342 "column": 8, 3715 "column": 8,
3343 "line": 14 3716 "line": 17
3344 } 3717 }
3345 }, 3718 },
3346 { 3719 {
3347 "defaultMessage": "!!!Redo", 3720 "defaultMessage": "!!!Redo",
3348 "end": { 3721 "end": {
3349 "column": 3, 3722 "column": 3,
3350 "line": 21 3723 "line": 24
3351 }, 3724 },
3352 "file": "src/lib/Menu.js", 3725 "file": "src/lib/Menu.js",
3353 "id": "menu.edit.redo", 3726 "id": "menu.edit.redo",
3354 "start": { 3727 "start": {
3355 "column": 8, 3728 "column": 8,
3356 "line": 18 3729 "line": 21
3357 } 3730 }
3358 }, 3731 },
3359 { 3732 {
3360 "defaultMessage": "!!!Cut", 3733 "defaultMessage": "!!!Cut",
3361 "end": { 3734 "end": {
3362 "column": 3, 3735 "column": 3,
3363 "line": 25 3736 "line": 28
3364 }, 3737 },
3365 "file": "src/lib/Menu.js", 3738 "file": "src/lib/Menu.js",
3366 "id": "menu.edit.cut", 3739 "id": "menu.edit.cut",
3367 "start": { 3740 "start": {
3368 "column": 7, 3741 "column": 7,
3369 "line": 22 3742 "line": 25
3370 } 3743 }
3371 }, 3744 },
3372 { 3745 {
3373 "defaultMessage": "!!!Copy", 3746 "defaultMessage": "!!!Copy",
3374 "end": { 3747 "end": {
3375 "column": 3, 3748 "column": 3,
3376 "line": 29 3749 "line": 32
3377 }, 3750 },
3378 "file": "src/lib/Menu.js", 3751 "file": "src/lib/Menu.js",
3379 "id": "menu.edit.copy", 3752 "id": "menu.edit.copy",
3380 "start": { 3753 "start": {
3381 "column": 8, 3754 "column": 8,
3382 "line": 26 3755 "line": 29
3383 } 3756 }
3384 }, 3757 },
3385 { 3758 {
3386 "defaultMessage": "!!!Paste", 3759 "defaultMessage": "!!!Paste",
3387 "end": { 3760 "end": {
3388 "column": 3, 3761 "column": 3,
3389 "line": 33 3762 "line": 36
3390 }, 3763 },
3391 "file": "src/lib/Menu.js", 3764 "file": "src/lib/Menu.js",
3392 "id": "menu.edit.paste", 3765 "id": "menu.edit.paste",
3393 "start": { 3766 "start": {
3394 "column": 9, 3767 "column": 9,
3395 "line": 30 3768 "line": 33
3396 } 3769 }
3397 }, 3770 },
3398 { 3771 {
3399 "defaultMessage": "!!!Paste And Match Style", 3772 "defaultMessage": "!!!Paste And Match Style",
3400 "end": { 3773 "end": {
3401 "column": 3, 3774 "column": 3,
3402 "line": 37 3775 "line": 40
3403 }, 3776 },
3404 "file": "src/lib/Menu.js", 3777 "file": "src/lib/Menu.js",
3405 "id": "menu.edit.pasteAndMatchStyle", 3778 "id": "menu.edit.pasteAndMatchStyle",
3406 "start": { 3779 "start": {
3407 "column": 22, 3780 "column": 22,
3408 "line": 34 3781 "line": 37
3409 } 3782 }
3410 }, 3783 },
3411 { 3784 {
3412 "defaultMessage": "!!!Delete", 3785 "defaultMessage": "!!!Delete",
3413 "end": { 3786 "end": {
3414 "column": 3, 3787 "column": 3,
3415 "line": 41 3788 "line": 44
3416 }, 3789 },
3417 "file": "src/lib/Menu.js", 3790 "file": "src/lib/Menu.js",
3418 "id": "menu.edit.delete", 3791 "id": "menu.edit.delete",
3419 "start": { 3792 "start": {
3420 "column": 10, 3793 "column": 10,
3421 "line": 38 3794 "line": 41
3422 } 3795 }
3423 }, 3796 },
3424 { 3797 {
3425 "defaultMessage": "!!!Select All", 3798 "defaultMessage": "!!!Select All",
3426 "end": { 3799 "end": {
3427 "column": 3, 3800 "column": 3,
3428 "line": 45 3801 "line": 48
3429 }, 3802 },
3430 "file": "src/lib/Menu.js", 3803 "file": "src/lib/Menu.js",
3431 "id": "menu.edit.selectAll", 3804 "id": "menu.edit.selectAll",
3432 "start": { 3805 "start": {
3433 "column": 13, 3806 "column": 13,
3434 "line": 42 3807 "line": 45
3435 } 3808 }
3436 }, 3809 },
3437 { 3810 {
3438 "defaultMessage": "!!!Speech", 3811 "defaultMessage": "!!!Speech",
3439 "end": { 3812 "end": {
3440 "column": 3, 3813 "column": 3,
3441 "line": 49 3814 "line": 52
3442 }, 3815 },
3443 "file": "src/lib/Menu.js", 3816 "file": "src/lib/Menu.js",
3444 "id": "menu.edit.speech", 3817 "id": "menu.edit.speech",
3445 "start": { 3818 "start": {
3446 "column": 10, 3819 "column": 10,
3447 "line": 46 3820 "line": 49
3448 } 3821 }
3449 }, 3822 },
3450 { 3823 {
3451 "defaultMessage": "!!!Start Speaking", 3824 "defaultMessage": "!!!Start Speaking",
3452 "end": { 3825 "end": {
3453 "column": 3, 3826 "column": 3,
3454 "line": 53 3827 "line": 56
3455 }, 3828 },
3456 "file": "src/lib/Menu.js", 3829 "file": "src/lib/Menu.js",
3457 "id": "menu.edit.startSpeaking", 3830 "id": "menu.edit.startSpeaking",
3458 "start": { 3831 "start": {
3459 "column": 17, 3832 "column": 17,
3460 "line": 50 3833 "line": 53
3461 } 3834 }
3462 }, 3835 },
3463 { 3836 {
3464 "defaultMessage": "!!!Stop Speaking", 3837 "defaultMessage": "!!!Stop Speaking",
3465 "end": { 3838 "end": {
3466 "column": 3, 3839 "column": 3,
3467 "line": 57 3840 "line": 60
3468 }, 3841 },
3469 "file": "src/lib/Menu.js", 3842 "file": "src/lib/Menu.js",
3470 "id": "menu.edit.stopSpeaking", 3843 "id": "menu.edit.stopSpeaking",
3471 "start": { 3844 "start": {
3472 "column": 16, 3845 "column": 16,
3473 "line": 54 3846 "line": 57
3474 } 3847 }
3475 }, 3848 },
3476 { 3849 {
3477 "defaultMessage": "!!!Start Dictation", 3850 "defaultMessage": "!!!Start Dictation",
3478 "end": { 3851 "end": {
3479 "column": 3, 3852 "column": 3,
3480 "line": 61 3853 "line": 64
3481 }, 3854 },
3482 "file": "src/lib/Menu.js", 3855 "file": "src/lib/Menu.js",
3483 "id": "menu.edit.startDictation", 3856 "id": "menu.edit.startDictation",
3484 "start": { 3857 "start": {
3485 "column": 18, 3858 "column": 18,
3486 "line": 58 3859 "line": 61
3487 } 3860 }
3488 }, 3861 },
3489 { 3862 {
3490 "defaultMessage": "!!!Emoji & Symbols", 3863 "defaultMessage": "!!!Emoji & Symbols",
3491 "end": { 3864 "end": {
3492 "column": 3, 3865 "column": 3,
3493 "line": 65 3866 "line": 68
3494 }, 3867 },
3495 "file": "src/lib/Menu.js", 3868 "file": "src/lib/Menu.js",
3496 "id": "menu.edit.emojiSymbols", 3869 "id": "menu.edit.emojiSymbols",
3497 "start": { 3870 "start": {
3498 "column": 16, 3871 "column": 16,
3499 "line": 62 3872 "line": 65
3500 } 3873 }
3501 }, 3874 },
3502 { 3875 {
3503 "defaultMessage": "!!!Actual Size", 3876 "defaultMessage": "!!!Actual Size",
3504 "end": { 3877 "end": {
3505 "column": 3, 3878 "column": 3,
3506 "line": 69 3879 "line": 72
3507 }, 3880 },
3508 "file": "src/lib/Menu.js", 3881 "file": "src/lib/Menu.js",
3509 "id": "menu.view.resetZoom", 3882 "id": "menu.view.resetZoom",
3510 "start": { 3883 "start": {
3511 "column": 13, 3884 "column": 13,
3512 "line": 66 3885 "line": 69
3513 } 3886 }
3514 }, 3887 },
3515 { 3888 {
3516 "defaultMessage": "!!!Zoom In", 3889 "defaultMessage": "!!!Zoom In",
3517 "end": { 3890 "end": {
3518 "column": 3, 3891 "column": 3,
3519 "line": 73 3892 "line": 76
3520 }, 3893 },
3521 "file": "src/lib/Menu.js", 3894 "file": "src/lib/Menu.js",
3522 "id": "menu.view.zoomIn", 3895 "id": "menu.view.zoomIn",
3523 "start": { 3896 "start": {
3524 "column": 10, 3897 "column": 10,
3525 "line": 70 3898 "line": 73
3526 } 3899 }
3527 }, 3900 },
3528 { 3901 {
3529 "defaultMessage": "!!!Zoom Out", 3902 "defaultMessage": "!!!Zoom Out",
3530 "end": { 3903 "end": {
3531 "column": 3, 3904 "column": 3,
3532 "line": 77 3905 "line": 80
3533 }, 3906 },
3534 "file": "src/lib/Menu.js", 3907 "file": "src/lib/Menu.js",
3535 "id": "menu.view.zoomOut", 3908 "id": "menu.view.zoomOut",
3536 "start": { 3909 "start": {
3537 "column": 11, 3910 "column": 11,
3538 "line": 74 3911 "line": 77
3539 } 3912 }
3540 }, 3913 },
3541 { 3914 {
3542 "defaultMessage": "!!!Enter Full Screen", 3915 "defaultMessage": "!!!Enter Full Screen",
3543 "end": { 3916 "end": {
3544 "column": 3, 3917 "column": 3,
3545 "line": 81 3918 "line": 84
3546 }, 3919 },
3547 "file": "src/lib/Menu.js", 3920 "file": "src/lib/Menu.js",
3548 "id": "menu.view.enterFullScreen", 3921 "id": "menu.view.enterFullScreen",
3549 "start": { 3922 "start": {
3550 "column": 19, 3923 "column": 19,
3551 "line": 78 3924 "line": 81
3552 } 3925 }
3553 }, 3926 },
3554 { 3927 {
3555 "defaultMessage": "!!!Exit Full Screen", 3928 "defaultMessage": "!!!Exit Full Screen",
3556 "end": { 3929 "end": {
3557 "column": 3, 3930 "column": 3,
3558 "line": 85 3931 "line": 88
3559 }, 3932 },
3560 "file": "src/lib/Menu.js", 3933 "file": "src/lib/Menu.js",
3561 "id": "menu.view.exitFullScreen", 3934 "id": "menu.view.exitFullScreen",
3562 "start": { 3935 "start": {
3563 "column": 18, 3936 "column": 18,
3564 "line": 82 3937 "line": 85
3565 } 3938 }
3566 }, 3939 },
3567 { 3940 {
3568 "defaultMessage": "!!!Toggle Full Screen", 3941 "defaultMessage": "!!!Toggle Full Screen",
3569 "end": { 3942 "end": {
3570 "column": 3, 3943 "column": 3,
3571 "line": 89 3944 "line": 92
3572 }, 3945 },
3573 "file": "src/lib/Menu.js", 3946 "file": "src/lib/Menu.js",
3574 "id": "menu.view.toggleFullScreen", 3947 "id": "menu.view.toggleFullScreen",
3575 "start": { 3948 "start": {
3576 "column": 20, 3949 "column": 20,
3577 "line": 86 3950 "line": 89
3578 } 3951 }
3579 }, 3952 },
3580 { 3953 {
3581 "defaultMessage": "!!!Toggle Developer Tools", 3954 "defaultMessage": "!!!Toggle Developer Tools",
3582 "end": { 3955 "end": {
3583 "column": 3, 3956 "column": 3,
3584 "line": 93 3957 "line": 96
3585 }, 3958 },
3586 "file": "src/lib/Menu.js", 3959 "file": "src/lib/Menu.js",
3587 "id": "menu.view.toggleDevTools", 3960 "id": "menu.view.toggleDevTools",
3588 "start": { 3961 "start": {
3589 "column": 18, 3962 "column": 18,
3590 "line": 90 3963 "line": 93
3591 } 3964 }
3592 }, 3965 },
3593 { 3966 {
3594 "defaultMessage": "!!!Toggle Service Developer Tools", 3967 "defaultMessage": "!!!Toggle Service Developer Tools",
3595 "end": { 3968 "end": {
3596 "column": 3, 3969 "column": 3,
3597 "line": 97 3970 "line": 100
3598 }, 3971 },
3599 "file": "src/lib/Menu.js", 3972 "file": "src/lib/Menu.js",
3600 "id": "menu.view.toggleServiceDevTools", 3973 "id": "menu.view.toggleServiceDevTools",
3601 "start": { 3974 "start": {
3602 "column": 25, 3975 "column": 25,
3603 "line": 94 3976 "line": 97
3604 } 3977 }
3605 }, 3978 },
3606 { 3979 {
3607 "defaultMessage": "!!!Reload Service", 3980 "defaultMessage": "!!!Reload Service",
3608 "end": { 3981 "end": {
3609 "column": 3, 3982 "column": 3,
3610 "line": 101 3983 "line": 104
3611 }, 3984 },
3612 "file": "src/lib/Menu.js", 3985 "file": "src/lib/Menu.js",
3613 "id": "menu.view.reloadService", 3986 "id": "menu.view.reloadService",
3614 "start": { 3987 "start": {
3615 "column": 17, 3988 "column": 17,
3616 "line": 98 3989 "line": 101
3617 } 3990 }
3618 }, 3991 },
3619 { 3992 {
3620 "defaultMessage": "!!!Reload Franz", 3993 "defaultMessage": "!!!Reload Franz",
3621 "end": { 3994 "end": {
3622 "column": 3, 3995 "column": 3,
3623 "line": 105 3996 "line": 108
3624 }, 3997 },
3625 "file": "src/lib/Menu.js", 3998 "file": "src/lib/Menu.js",
3626 "id": "menu.view.reloadFranz", 3999 "id": "menu.view.reloadFranz",
3627 "start": { 4000 "start": {
3628 "column": 15, 4001 "column": 15,
3629 "line": 102 4002 "line": 105
3630 } 4003 }
3631 }, 4004 },
3632 { 4005 {
3633 "defaultMessage": "!!!Minimize", 4006 "defaultMessage": "!!!Minimize",
3634 "end": { 4007 "end": {
3635 "column": 3, 4008 "column": 3,
3636 "line": 109 4009 "line": 112
3637 }, 4010 },
3638 "file": "src/lib/Menu.js", 4011 "file": "src/lib/Menu.js",
3639 "id": "menu.window.minimize", 4012 "id": "menu.window.minimize",
3640 "start": { 4013 "start": {
3641 "column": 12, 4014 "column": 12,
3642 "line": 106 4015 "line": 109
3643 } 4016 }
3644 }, 4017 },
3645 { 4018 {
3646 "defaultMessage": "!!!Close", 4019 "defaultMessage": "!!!Close",
3647 "end": { 4020 "end": {
3648 "column": 3, 4021 "column": 3,
3649 "line": 113 4022 "line": 116
3650 }, 4023 },
3651 "file": "src/lib/Menu.js", 4024 "file": "src/lib/Menu.js",
3652 "id": "menu.window.close", 4025 "id": "menu.window.close",
3653 "start": { 4026 "start": {
3654 "column": 9, 4027 "column": 9,
3655 "line": 110 4028 "line": 113
3656 } 4029 }
3657 }, 4030 },
3658 { 4031 {
3659 "defaultMessage": "!!!Learn More", 4032 "defaultMessage": "!!!Learn More",
3660 "end": { 4033 "end": {
3661 "column": 3, 4034 "column": 3,
3662 "line": 117 4035 "line": 120
3663 }, 4036 },
3664 "file": "src/lib/Menu.js", 4037 "file": "src/lib/Menu.js",
3665 "id": "menu.help.learnMore", 4038 "id": "menu.help.learnMore",
3666 "start": { 4039 "start": {
3667 "column": 13, 4040 "column": 13,
3668 "line": 114 4041 "line": 117
3669 } 4042 }
3670 }, 4043 },
3671 { 4044 {
3672 "defaultMessage": "!!!Changelog", 4045 "defaultMessage": "!!!Changelog",
3673 "end": { 4046 "end": {
3674 "column": 3, 4047 "column": 3,
3675 "line": 121 4048 "line": 124
3676 }, 4049 },
3677 "file": "src/lib/Menu.js", 4050 "file": "src/lib/Menu.js",
3678 "id": "menu.help.changelog", 4051 "id": "menu.help.changelog",
3679 "start": { 4052 "start": {
3680 "column": 13, 4053 "column": 13,
3681 "line": 118 4054 "line": 121
3682 } 4055 }
3683 }, 4056 },
3684 { 4057 {
3685 "defaultMessage": "!!!Support", 4058 "defaultMessage": "!!!Support",
3686 "end": { 4059 "end": {
3687 "column": 3, 4060 "column": 3,
3688 "line": 125 4061 "line": 128
3689 }, 4062 },
3690 "file": "src/lib/Menu.js", 4063 "file": "src/lib/Menu.js",
3691 "id": "menu.help.support", 4064 "id": "menu.help.support",
3692 "start": { 4065 "start": {
3693 "column": 11, 4066 "column": 11,
3694 "line": 122 4067 "line": 125
3695 } 4068 }
3696 }, 4069 },
3697 { 4070 {
3698 "defaultMessage": "!!!Terms of Service", 4071 "defaultMessage": "!!!Terms of Service",
3699 "end": { 4072 "end": {
3700 "column": 3, 4073 "column": 3,
3701 "line": 129 4074 "line": 132
3702 }, 4075 },
3703 "file": "src/lib/Menu.js", 4076 "file": "src/lib/Menu.js",
3704 "id": "menu.help.tos", 4077 "id": "menu.help.tos",
3705 "start": { 4078 "start": {
3706 "column": 7, 4079 "column": 7,
3707 "line": 126 4080 "line": 129
3708 } 4081 }
3709 }, 4082 },
3710 { 4083 {
3711 "defaultMessage": "!!!Privacy Statement", 4084 "defaultMessage": "!!!Privacy Statement",
3712 "end": { 4085 "end": {
3713 "column": 3, 4086 "column": 3,
3714 "line": 133 4087 "line": 136
3715 }, 4088 },
3716 "file": "src/lib/Menu.js", 4089 "file": "src/lib/Menu.js",
3717 "id": "menu.help.privacy", 4090 "id": "menu.help.privacy",
3718 "start": { 4091 "start": {
3719 "column": 11, 4092 "column": 11,
3720 "line": 130 4093 "line": 133
3721 } 4094 }
3722 }, 4095 },
3723 { 4096 {
3724 "defaultMessage": "!!!File", 4097 "defaultMessage": "!!!File",
3725 "end": { 4098 "end": {
3726 "column": 3, 4099 "column": 3,
3727 "line": 137 4100 "line": 140
3728 }, 4101 },
3729 "file": "src/lib/Menu.js", 4102 "file": "src/lib/Menu.js",
3730 "id": "menu.file", 4103 "id": "menu.file",
3731 "start": { 4104 "start": {
3732 "column": 8, 4105 "column": 8,
3733 "line": 134 4106 "line": 137
3734 } 4107 }
3735 }, 4108 },
3736 { 4109 {
3737 "defaultMessage": "!!!View", 4110 "defaultMessage": "!!!View",
3738 "end": { 4111 "end": {
3739 "column": 3, 4112 "column": 3,
3740 "line": 141 4113 "line": 144
3741 }, 4114 },
3742 "file": "src/lib/Menu.js", 4115 "file": "src/lib/Menu.js",
3743 "id": "menu.view", 4116 "id": "menu.view",
3744 "start": { 4117 "start": {
3745 "column": 8, 4118 "column": 8,
3746 "line": 138 4119 "line": 141
3747 } 4120 }
3748 }, 4121 },
3749 { 4122 {
3750 "defaultMessage": "!!!Services", 4123 "defaultMessage": "!!!Services",
3751 "end": { 4124 "end": {
3752 "column": 3, 4125 "column": 3,
3753 "line": 145 4126 "line": 148
3754 }, 4127 },
3755 "file": "src/lib/Menu.js", 4128 "file": "src/lib/Menu.js",
3756 "id": "menu.services", 4129 "id": "menu.services",
3757 "start": { 4130 "start": {
3758 "column": 12, 4131 "column": 12,
3759 "line": 142 4132 "line": 145
3760 } 4133 }
3761 }, 4134 },
3762 { 4135 {
3763 "defaultMessage": "!!!Window", 4136 "defaultMessage": "!!!Window",
3764 "end": { 4137 "end": {
3765 "column": 3, 4138 "column": 3,
3766 "line": 149 4139 "line": 152
3767 }, 4140 },
3768 "file": "src/lib/Menu.js", 4141 "file": "src/lib/Menu.js",
3769 "id": "menu.window", 4142 "id": "menu.window",
3770 "start": { 4143 "start": {
3771 "column": 10, 4144 "column": 10,
3772 "line": 146 4145 "line": 149
3773 } 4146 }
3774 }, 4147 },
3775 { 4148 {
3776 "defaultMessage": "!!!Help", 4149 "defaultMessage": "!!!Help",
3777 "end": { 4150 "end": {
3778 "column": 3, 4151 "column": 3,
3779 "line": 153 4152 "line": 156
3780 }, 4153 },
3781 "file": "src/lib/Menu.js", 4154 "file": "src/lib/Menu.js",
3782 "id": "menu.help", 4155 "id": "menu.help",
3783 "start": { 4156 "start": {
3784 "column": 8, 4157 "column": 8,
3785 "line": 150 4158 "line": 153
3786 } 4159 }
3787 }, 4160 },
3788 { 4161 {
3789 "defaultMessage": "!!!About Franz", 4162 "defaultMessage": "!!!About Franz",
3790 "end": { 4163 "end": {
3791 "column": 3, 4164 "column": 3,
3792 "line": 157 4165 "line": 160
3793 }, 4166 },
3794 "file": "src/lib/Menu.js", 4167 "file": "src/lib/Menu.js",
3795 "id": "menu.app.about", 4168 "id": "menu.app.about",
3796 "start": { 4169 "start": {
3797 "column": 9, 4170 "column": 9,
3798 "line": 154 4171 "line": 157
3799 } 4172 }
3800 }, 4173 },
3801 { 4174 {
3802 "defaultMessage": "!!!Settings", 4175 "defaultMessage": "!!!Settings",
3803 "end": { 4176 "end": {
3804 "column": 3, 4177 "column": 3,
3805 "line": 161 4178 "line": 164
3806 }, 4179 },
3807 "file": "src/lib/Menu.js", 4180 "file": "src/lib/Menu.js",
3808 "id": "menu.app.settings", 4181 "id": "menu.app.settings",
3809 "start": { 4182 "start": {
3810 "column": 12, 4183 "column": 12,
3811 "line": 158 4184 "line": 161
3812 } 4185 }
3813 }, 4186 },
3814 { 4187 {
3815 "defaultMessage": "!!!Hide", 4188 "defaultMessage": "!!!Hide",
3816 "end": { 4189 "end": {
3817 "column": 3, 4190 "column": 3,
3818 "line": 165 4191 "line": 168
3819 }, 4192 },
3820 "file": "src/lib/Menu.js", 4193 "file": "src/lib/Menu.js",
3821 "id": "menu.app.hide", 4194 "id": "menu.app.hide",
3822 "start": { 4195 "start": {
3823 "column": 8, 4196 "column": 8,
3824 "line": 162 4197 "line": 165
3825 } 4198 }
3826 }, 4199 },
3827 { 4200 {
3828 "defaultMessage": "!!!Hide Others", 4201 "defaultMessage": "!!!Hide Others",
3829 "end": { 4202 "end": {
3830 "column": 3, 4203 "column": 3,
3831 "line": 169 4204 "line": 172
3832 }, 4205 },
3833 "file": "src/lib/Menu.js", 4206 "file": "src/lib/Menu.js",
3834 "id": "menu.app.hideOthers", 4207 "id": "menu.app.hideOthers",
3835 "start": { 4208 "start": {
3836 "column": 14, 4209 "column": 14,
3837 "line": 166 4210 "line": 169
3838 } 4211 }
3839 }, 4212 },
3840 { 4213 {
3841 "defaultMessage": "!!!Unhide", 4214 "defaultMessage": "!!!Unhide",
3842 "end": { 4215 "end": {
3843 "column": 3, 4216 "column": 3,
3844 "line": 173 4217 "line": 176
3845 }, 4218 },
3846 "file": "src/lib/Menu.js", 4219 "file": "src/lib/Menu.js",
3847 "id": "menu.app.unhide", 4220 "id": "menu.app.unhide",
3848 "start": { 4221 "start": {
3849 "column": 10, 4222 "column": 10,
3850 "line": 170 4223 "line": 173
3851 } 4224 }
3852 }, 4225 },
3853 { 4226 {
3854 "defaultMessage": "!!!Quit", 4227 "defaultMessage": "!!!Quit",
3855 "end": { 4228 "end": {
3856 "column": 3, 4229 "column": 3,
3857 "line": 177 4230 "line": 180
3858 }, 4231 },
3859 "file": "src/lib/Menu.js", 4232 "file": "src/lib/Menu.js",
3860 "id": "menu.app.quit", 4233 "id": "menu.app.quit",
3861 "start": { 4234 "start": {
3862 "column": 8, 4235 "column": 8,
3863 "line": 174 4236 "line": 177
3864 } 4237 }
3865 }, 4238 },
3866 { 4239 {
3867 "defaultMessage": "!!!Add New Service...", 4240 "defaultMessage": "!!!Add New Service...",
3868 "end": { 4241 "end": {
3869 "column": 3, 4242 "column": 3,
3870 "line": 181 4243 "line": 184
3871 }, 4244 },
3872 "file": "src/lib/Menu.js", 4245 "file": "src/lib/Menu.js",
3873 "id": "menu.services.addNewService", 4246 "id": "menu.services.addNewService",
3874 "start": { 4247 "start": {
3875 "column": 17, 4248 "column": 17,
3876 "line": 178 4249 "line": 181
3877 } 4250 }
3878 }, 4251 },
3879 { 4252 {
3880 "defaultMessage": "!!!Activate next service...", 4253 "defaultMessage": "!!!Add New Workspace...",
3881 "end": { 4254 "end": {
3882 "column": 3, 4255 "column": 3,
4256 "line": 188
4257 },
4258 "file": "src/lib/Menu.js",
4259 "id": "menu.workspaces.addNewWorkspace",
4260 "start": {
4261 "column": 19,
3883 "line": 185 4262 "line": 185
4263 }
4264 },
4265 {
4266 "defaultMessage": "!!!Open workspace drawer",
4267 "end": {
4268 "column": 3,
4269 "line": 192
4270 },
4271 "file": "src/lib/Menu.js",
4272 "id": "menu.workspaces.openWorkspaceDrawer",
4273 "start": {
4274 "column": 23,
4275 "line": 189
4276 }
4277 },
4278 {
4279 "defaultMessage": "!!!Close workspace drawer",
4280 "end": {
4281 "column": 3,
4282 "line": 196
4283 },
4284 "file": "src/lib/Menu.js",
4285 "id": "menu.workspaces.closeWorkspaceDrawer",
4286 "start": {
4287 "column": 24,
4288 "line": 193
4289 }
4290 },
4291 {
4292 "defaultMessage": "!!!Activate next service...",
4293 "end": {
4294 "column": 3,
4295 "line": 200
3884 }, 4296 },
3885 "file": "src/lib/Menu.js", 4297 "file": "src/lib/Menu.js",
3886 "id": "menu.services.setNextServiceActive", 4298 "id": "menu.services.setNextServiceActive",
3887 "start": { 4299 "start": {
3888 "column": 23, 4300 "column": 23,
3889 "line": 182 4301 "line": 197
3890 } 4302 }
3891 }, 4303 },
3892 { 4304 {
3893 "defaultMessage": "!!!Activate previous service...", 4305 "defaultMessage": "!!!Activate previous service...",
3894 "end": { 4306 "end": {
3895 "column": 3, 4307 "column": 3,
3896 "line": 189 4308 "line": 204
3897 }, 4309 },
3898 "file": "src/lib/Menu.js", 4310 "file": "src/lib/Menu.js",
3899 "id": "menu.services.activatePreviousService", 4311 "id": "menu.services.activatePreviousService",
3900 "start": { 4312 "start": {
3901 "column": 27, 4313 "column": 27,
3902 "line": 186 4314 "line": 201
3903 } 4315 }
3904 }, 4316 },
3905 { 4317 {
3906 "defaultMessage": "!!!Disable notifications & audio", 4318 "defaultMessage": "!!!Disable notifications & audio",
3907 "end": { 4319 "end": {
3908 "column": 3, 4320 "column": 3,
3909 "line": 193 4321 "line": 208
3910 }, 4322 },
3911 "file": "src/lib/Menu.js", 4323 "file": "src/lib/Menu.js",
3912 "id": "sidebar.muteApp", 4324 "id": "sidebar.muteApp",
3913 "start": { 4325 "start": {
3914 "column": 11, 4326 "column": 11,
3915 "line": 190 4327 "line": 205
3916 } 4328 }
3917 }, 4329 },
3918 { 4330 {
3919 "defaultMessage": "!!!Enable notifications & audio", 4331 "defaultMessage": "!!!Enable notifications & audio",
3920 "end": { 4332 "end": {
3921 "column": 3, 4333 "column": 3,
3922 "line": 197 4334 "line": 212
3923 }, 4335 },
3924 "file": "src/lib/Menu.js", 4336 "file": "src/lib/Menu.js",
3925 "id": "sidebar.unmuteApp", 4337 "id": "sidebar.unmuteApp",
3926 "start": { 4338 "start": {
3927 "column": 13, 4339 "column": 13,
3928 "line": 194 4340 "line": 209
4341 }
4342 },
4343 {
4344 "defaultMessage": "!!!Workspaces",
4345 "end": {
4346 "column": 3,
4347 "line": 216
4348 },
4349 "file": "src/lib/Menu.js",
4350 "id": "menu.workspaces",
4351 "start": {
4352 "column": 14,
4353 "line": 213
4354 }
4355 },
4356 {
4357 "defaultMessage": "!!!Default",
4358 "end": {
4359 "column": 3,
4360 "line": 220
4361 },
4362 "file": "src/lib/Menu.js",
4363 "id": "menu.workspaces.defaultWorkspace",
4364 "start": {
4365 "column": 20,
4366 "line": 217
3929 } 4367 }
3930 } 4368 }
3931 ], 4369 ],
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 06cda4aca..c36801d4a 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -87,6 +87,11 @@
87 "menu.window": "Window", 87 "menu.window": "Window",
88 "menu.window.close": "Close", 88 "menu.window.close": "Close",
89 "menu.window.minimize": "Minimize", 89 "menu.window.minimize": "Minimize",
90 "menu.workspaces": "Workspaces",
91 "menu.workspaces.addNewWorkspace": "Add New Workspace...",
92 "menu.workspaces.closeWorkspaceDrawer": "Close workspace drawer",
93 "menu.workspaces.defaultWorkspace": "All services",
94 "menu.workspaces.openWorkspaceDrawer": "Open workspace drawer",
90 "password.email.label": "Email address", 95 "password.email.label": "Email address",
91 "password.headline": "Reset password", 96 "password.headline": "Reset password",
92 "password.link.login": "Sign in to your account", 97 "password.link.login": "Sign in to your account",
@@ -110,6 +115,7 @@
110 "service.errorHandler.headline": "Oh no!", 115 "service.errorHandler.headline": "Oh no!",
111 "service.errorHandler.message": "Error", 116 "service.errorHandler.message": "Error",
112 "service.errorHandler.text": "{name} has failed to load.", 117 "service.errorHandler.text": "{name} has failed to load.",
118 "service.webviewLoader.loading": "Loading",
113 "services.getStarted": "Get started", 119 "services.getStarted": "Get started",
114 "services.welcome": "Welcome to Franz", 120 "services.welcome": "Welcome to Franz",
115 "settings.account.account.editButton": "Edit account", 121 "settings.account.account.editButton": "Edit account",
@@ -169,6 +175,7 @@
169 "settings.navigation.logout": "Logout", 175 "settings.navigation.logout": "Logout",
170 "settings.navigation.settings": "Settings", 176 "settings.navigation.settings": "Settings",
171 "settings.navigation.yourServices": "Your services", 177 "settings.navigation.yourServices": "Your services",
178 "settings.navigation.yourWorkspaces": "Your workspaces",
172 "settings.recipes.all": "All services", 179 "settings.recipes.all": "All services",
173 "settings.recipes.dev": "Development", 180 "settings.recipes.dev": "Development",
174 "settings.recipes.headline": "Available services", 181 "settings.recipes.headline": "Available services",
@@ -235,8 +242,25 @@
235 "settings.user.form.firstname": "First Name", 242 "settings.user.form.firstname": "First Name",
236 "settings.user.form.lastname": "Last Name", 243 "settings.user.form.lastname": "Last Name",
237 "settings.user.form.newPassword": "New password", 244 "settings.user.form.newPassword": "New password",
245 "settings.workspace.add.form.name": "Name",
246 "settings.workspace.add.form.submitButton": "Create workspace",
247 "settings.workspace.form.buttonDelete": "Delete workspace",
248 "settings.workspace.form.buttonSave": "Save workspace",
249 "settings.workspace.form.name": "Name",
250 "settings.workspace.form.servicesInWorkspaceHeadline": "Services in this Workspace",
251 "settings.workspace.form.yourWorkspaces": "Your workspaces",
252 "settings.workspaces.deletedInfo": "Workspace has been deleted",
253 "settings.workspaces.headline": "Your workspaces",
254 "settings.workspaces.noWorkspacesAdded": "You haven't added any workspaces yet.",
255 "settings.workspaces.tryReloadWorkspaces": "Try again",
256 "settings.workspaces.updatedInfo": "Your changes have been saved",
257 "settings.workspaces.workspaceFeatureHeadline": "Less is More: Introducing Franz Workspaces",
258 "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.",
259 "settings.workspaces.workspacesRequestFailed": "Could not load your workspaces",
238 "sidebar.addNewService": "Add new service", 260 "sidebar.addNewService": "Add new service",
261 "sidebar.closeWorkspaceDrawer": "Close workspace drawer",
239 "sidebar.muteApp": "Disable notifications & audio", 262 "sidebar.muteApp": "Disable notifications & audio",
263 "sidebar.openWorkspaceDrawer": "Open workspace drawer",
240 "sidebar.settings": "Settings", 264 "sidebar.settings": "Settings",
241 "sidebar.unmuteApp": "Enable notifications & audio", 265 "sidebar.unmuteApp": "Enable notifications & audio",
242 "signup.company.label": "Company", 266 "signup.company.label": "Company",
@@ -281,5 +305,12 @@
281 "validation.required": "{field} is required", 305 "validation.required": "{field} is required",
282 "validation.url": "{field} is not a valid URL", 306 "validation.url": "{field} is not a valid URL",
283 "welcome.loginButton": "Login to your account", 307 "welcome.loginButton": "Login to your account",
284 "welcome.signupButton": "Create a free account" 308 "welcome.signupButton": "Create a free account",
309 "workspaceDrawer.addWorkspaceTooltip": "Add workspace",
310 "workspaceDrawer.allServices": "All services",
311 "workspaceDrawer.headline": "Workspaces",
312 "workspaceDrawer.item.noServicesAddedYet": "No services added yet",
313 "workspaceDrawer.premiumCtaButtonLabel": "Create your first workspace",
314 "workspaceDrawer.workspaceFeatureInfo": "<p>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.</p><p>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.</p>",
315 "workspaces.switchingIndicator.switchingTo": "Switching to"
285} 316}
diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json
index 07603d062..92593ed5c 100644
--- a/src/i18n/messages/src/components/layout/AppLayout.json
+++ b/src/i18n/messages/src/components/layout/AppLayout.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services have been updated.", 4 "defaultMessage": "!!!Your services have been updated.",
5 "file": "src/components/layout/AppLayout.js", 5 "file": "src/components/layout/AppLayout.js",
6 "start": { 6 "start": {
7 "line": 22, 7 "line": 25,
8 "column": 19 8 "column": 19
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 25, 11 "line": 28,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!A new update for Franz is available.", 17 "defaultMessage": "!!!A new update for Franz is available.",
18 "file": "src/components/layout/AppLayout.js", 18 "file": "src/components/layout/AppLayout.js",
19 "start": { 19 "start": {
20 "line": 26, 20 "line": 29,
21 "column": 19 21 "column": 19
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 29, 24 "line": 32,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Reload services", 30 "defaultMessage": "!!!Reload services",
31 "file": "src/components/layout/AppLayout.js", 31 "file": "src/components/layout/AppLayout.js",
32 "start": { 32 "start": {
33 "line": 30, 33 "line": 33,
34 "column": 24 34 "column": 24
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 33, 37 "line": 36,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Changelog", 43 "defaultMessage": "!!!Changelog",
44 "file": "src/components/layout/AppLayout.js", 44 "file": "src/components/layout/AppLayout.js",
45 "start": { 45 "start": {
46 "line": 34, 46 "line": 37,
47 "column": 13 47 "column": 13
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 37, 50 "line": 40,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Restart & install update", 56 "defaultMessage": "!!!Restart & install update",
57 "file": "src/components/layout/AppLayout.js", 57 "file": "src/components/layout/AppLayout.js",
58 "start": { 58 "start": {
59 "line": 38, 59 "line": 41,
60 "column": 23 60 "column": 23
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 41, 63 "line": 44,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Could not load services and user information", 69 "defaultMessage": "!!!Could not load services and user information",
70 "file": "src/components/layout/AppLayout.js", 70 "file": "src/components/layout/AppLayout.js",
71 "start": { 71 "start": {
72 "line": 42, 72 "line": 45,
73 "column": 26 73 "column": 26
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 45, 76 "line": 48,
77 "column": 3 77 "column": 3
78 } 78 }
79 } 79 }
diff --git a/src/i18n/messages/src/components/layout/Sidebar.json b/src/i18n/messages/src/components/layout/Sidebar.json
index 7aa00a186..d67adc96e 100644
--- a/src/i18n/messages/src/components/layout/Sidebar.json
+++ b/src/i18n/messages/src/components/layout/Sidebar.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Settings", 4 "defaultMessage": "!!!Settings",
5 "file": "src/components/layout/Sidebar.js", 5 "file": "src/components/layout/Sidebar.js",
6 "start": { 6 "start": {
7 "line": 11, 7 "line": 13,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 14, 11 "line": 16,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Add new service", 17 "defaultMessage": "!!!Add new service",
18 "file": "src/components/layout/Sidebar.js", 18 "file": "src/components/layout/Sidebar.js",
19 "start": { 19 "start": {
20 "line": 15, 20 "line": 17,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 18, 24 "line": 20,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Disable notifications & audio", 30 "defaultMessage": "!!!Disable notifications & audio",
31 "file": "src/components/layout/Sidebar.js", 31 "file": "src/components/layout/Sidebar.js",
32 "start": { 32 "start": {
33 "line": 19, 33 "line": 21,
34 "column": 8 34 "column": 8
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 22, 37 "line": 24,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,37 @@
43 "defaultMessage": "!!!Enable notifications & audio", 43 "defaultMessage": "!!!Enable notifications & audio",
44 "file": "src/components/layout/Sidebar.js", 44 "file": "src/components/layout/Sidebar.js",
45 "start": { 45 "start": {
46 "line": 23, 46 "line": 25,
47 "column": 10 47 "column": 10
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 26, 50 "line": 28,
51 "column": 3
52 }
53 },
54 {
55 "id": "sidebar.openWorkspaceDrawer",
56 "defaultMessage": "!!!Open workspace drawer",
57 "file": "src/components/layout/Sidebar.js",
58 "start": {
59 "line": 29,
60 "column": 23
61 },
62 "end": {
63 "line": 32,
64 "column": 3
65 }
66 },
67 {
68 "id": "sidebar.closeWorkspaceDrawer",
69 "defaultMessage": "!!!Close workspace drawer",
70 "file": "src/components/layout/Sidebar.js",
71 "start": {
72 "line": 33,
73 "column": 24
74 },
75 "end": {
76 "line": 36,
51 "column": 3 77 "column": 3
52 } 78 }
53 } 79 }
diff --git a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
index 785ce9f29..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 @@
4 "defaultMessage": "!!!Available services", 4 "defaultMessage": "!!!Available services",
5 "file": "src/components/settings/navigation/SettingsNavigation.js", 5 "file": "src/components/settings/navigation/SettingsNavigation.js",
6 "start": { 6 "start": {
7 "line": 9, 7 "line": 11,
8 "column": 21 8 "column": 21
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 12, 11 "line": 14,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,24 @@
17 "defaultMessage": "!!!Your services", 17 "defaultMessage": "!!!Your services",
18 "file": "src/components/settings/navigation/SettingsNavigation.js", 18 "file": "src/components/settings/navigation/SettingsNavigation.js",
19 "start": { 19 "start": {
20 "line": 13, 20 "line": 15,
21 "column": 16 21 "column": 16
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 16, 24 "line": 18,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.navigation.yourWorkspaces",
30 "defaultMessage": "!!!Your workspaces",
31 "file": "src/components/settings/navigation/SettingsNavigation.js",
32 "start": {
33 "line": 19,
34 "column": 18
35 },
36 "end": {
37 "line": 22,
25 "column": 3 38 "column": 3
26 } 39 }
27 }, 40 },
@@ -30,11 +43,11 @@
30 "defaultMessage": "!!!Account", 43 "defaultMessage": "!!!Account",
31 "file": "src/components/settings/navigation/SettingsNavigation.js", 44 "file": "src/components/settings/navigation/SettingsNavigation.js",
32 "start": { 45 "start": {
33 "line": 17, 46 "line": 23,
34 "column": 11 47 "column": 11
35 }, 48 },
36 "end": { 49 "end": {
37 "line": 20, 50 "line": 26,
38 "column": 3 51 "column": 3
39 } 52 }
40 }, 53 },
@@ -43,11 +56,11 @@
43 "defaultMessage": "!!!Settings", 56 "defaultMessage": "!!!Settings",
44 "file": "src/components/settings/navigation/SettingsNavigation.js", 57 "file": "src/components/settings/navigation/SettingsNavigation.js",
45 "start": { 58 "start": {
46 "line": 21, 59 "line": 27,
47 "column": 12 60 "column": 12
48 }, 61 },
49 "end": { 62 "end": {
50 "line": 24, 63 "line": 30,
51 "column": 3 64 "column": 3
52 } 65 }
53 }, 66 },
@@ -56,11 +69,11 @@
56 "defaultMessage": "!!!Invite Friends", 69 "defaultMessage": "!!!Invite Friends",
57 "file": "src/components/settings/navigation/SettingsNavigation.js", 70 "file": "src/components/settings/navigation/SettingsNavigation.js",
58 "start": { 71 "start": {
59 "line": 25, 72 "line": 31,
60 "column": 17 73 "column": 17
61 }, 74 },
62 "end": { 75 "end": {
63 "line": 28, 76 "line": 34,
64 "column": 3 77 "column": 3
65 } 78 }
66 }, 79 },
@@ -69,11 +82,11 @@
69 "defaultMessage": "!!!Logout", 82 "defaultMessage": "!!!Logout",
70 "file": "src/components/settings/navigation/SettingsNavigation.js", 83 "file": "src/components/settings/navigation/SettingsNavigation.js",
71 "start": { 84 "start": {
72 "line": 29, 85 "line": 35,
73 "column": 10 86 "column": 10
74 }, 87 },
75 "end": { 88 "end": {
76 "line": 32, 89 "line": 38,
77 "column": 3 90 "column": 3
78 } 91 }
79 } 92 }
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 @@
4 "defaultMessage": "!!!Upgrade account", 4 "defaultMessage": "!!!Upgrade account",
5 "file": "src/components/ui/PremiumFeatureContainer/index.js", 5 "file": "src/components/ui/PremiumFeatureContainer/index.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 15,
8 "column": 10 8 "column": 10
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 17, 11 "line": 18,
12 "column": 3 12 "column": 3
13 } 13 }
14 } 14 }
diff --git a/src/i18n/messages/src/components/ui/WebviewLoader/index.json b/src/i18n/messages/src/components/ui/WebviewLoader/index.json
new file mode 100644
index 000000000..ef3e4b593
--- /dev/null
+++ b/src/i18n/messages/src/components/ui/WebviewLoader/index.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "service.webviewLoader.loading",
4 "defaultMessage": "!!!Loading",
5 "file": "src/components/ui/WebviewLoader/index.js",
6 "start": {
7 "line": 11,
8 "column": 11
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json b/src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json
new file mode 100644
index 000000000..f62bac42c
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/CreateWorkspaceForm.json
@@ -0,0 +1,28 @@
1[
2 {
3 "id": "settings.workspace.add.form.submitButton",
4 "defaultMessage": "!!!Create workspace",
5 "file": "src/features/workspaces/components/CreateWorkspaceForm.js",
6 "start": {
7 "line": 13,
8 "column": 16
9 },
10 "end": {
11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "settings.workspace.add.form.name",
17 "defaultMessage": "!!!Name",
18 "file": "src/features/workspaces/components/CreateWorkspaceForm.js",
19 "start": {
20 "line": 17,
21 "column": 8
22 },
23 "end": {
24 "line": 20,
25 "column": 3
26 }
27 }
28] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json b/src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json
new file mode 100644
index 000000000..7b0c3e1ce
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/EditWorkspaceForm.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "settings.workspace.form.buttonDelete",
4 "defaultMessage": "!!!Delete workspace",
5 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
6 "start": {
7 "line": 19,
8 "column": 16
9 },
10 "end": {
11 "line": 22,
12 "column": 3
13 }
14 },
15 {
16 "id": "settings.workspace.form.buttonSave",
17 "defaultMessage": "!!!Save workspace",
18 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
19 "start": {
20 "line": 23,
21 "column": 14
22 },
23 "end": {
24 "line": 26,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.workspace.form.name",
30 "defaultMessage": "!!!Name",
31 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
32 "start": {
33 "line": 27,
34 "column": 8
35 },
36 "end": {
37 "line": 30,
38 "column": 3
39 }
40 },
41 {
42 "id": "settings.workspace.form.yourWorkspaces",
43 "defaultMessage": "!!!Your workspaces",
44 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
45 "start": {
46 "line": 31,
47 "column": 18
48 },
49 "end": {
50 "line": 34,
51 "column": 3
52 }
53 },
54 {
55 "id": "settings.workspace.form.servicesInWorkspaceHeadline",
56 "defaultMessage": "!!!Services in this Workspace",
57 "file": "src/features/workspaces/components/EditWorkspaceForm.js",
58 "start": {
59 "line": 35,
60 "column": 31
61 },
62 "end": {
63 "line": 38,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json
new file mode 100644
index 000000000..acd304253
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "workspaceDrawer.headline",
4 "defaultMessage": "!!!Workspaces",
5 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
6 "start": {
7 "line": 16,
8 "column": 12
9 },
10 "end": {
11 "line": 19,
12 "column": 3
13 }
14 },
15 {
16 "id": "workspaceDrawer.allServices",
17 "defaultMessage": "!!!All services",
18 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
19 "start": {
20 "line": 20,
21 "column": 15
22 },
23 "end": {
24 "line": 23,
25 "column": 3
26 }
27 },
28 {
29 "id": "workspaceDrawer.addWorkspaceTooltip",
30 "defaultMessage": "!!!Add workspace",
31 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
32 "start": {
33 "line": 24,
34 "column": 23
35 },
36 "end": {
37 "line": 27,
38 "column": 3
39 }
40 },
41 {
42 "id": "workspaceDrawer.workspaceFeatureInfo",
43 "defaultMessage": "!!!Info about workspace feature",
44 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
45 "start": {
46 "line": 28,
47 "column": 24
48 },
49 "end": {
50 "line": 31,
51 "column": 3
52 }
53 },
54 {
55 "id": "workspaceDrawer.premiumCtaButtonLabel",
56 "defaultMessage": "!!!Create your first workspace",
57 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
58 "start": {
59 "line": 32,
60 "column": 25
61 },
62 "end": {
63 "line": 35,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json
new file mode 100644
index 000000000..cdbd1d5b5
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawerItem.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "workspaceDrawer.item.noServicesAddedYet",
4 "defaultMessage": "!!!No services added yet",
5 "file": "src/features/workspaces/components/WorkspaceDrawerItem.js",
6 "start": {
7 "line": 9,
8 "column": 22
9 },
10 "end": {
11 "line": 12,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json
new file mode 100644
index 000000000..4f3e6d55c
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceSwitchingIndicator.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "workspaces.switchingIndicator.switchingTo",
4 "defaultMessage": "!!!Switching to",
5 "file": "src/features/workspaces/components/WorkspaceSwitchingIndicator.js",
6 "start": {
7 "line": 12,
8 "column": 15
9 },
10 "end": {
11 "line": 15,
12 "column": 3
13 }
14 }
15] \ 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
new file mode 100644
index 000000000..ef8f1bebc
--- /dev/null
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json
@@ -0,0 +1,106 @@
1[
2 {
3 "id": "settings.workspaces.headline",
4 "defaultMessage": "!!!Your workspaces",
5 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
6 "start": {
7 "line": 17,
8 "column": 12
9 },
10 "end": {
11 "line": 20,
12 "column": 3
13 }
14 },
15 {
16 "id": "settings.workspaces.noWorkspacesAdded",
17 "defaultMessage": "!!!You haven't added any workspaces yet.",
18 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
19 "start": {
20 "line": 21,
21 "column": 19
22 },
23 "end": {
24 "line": 24,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.workspaces.workspacesRequestFailed",
30 "defaultMessage": "!!!Could not load your workspaces",
31 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
32 "start": {
33 "line": 25,
34 "column": 27
35 },
36 "end": {
37 "line": 28,
38 "column": 3
39 }
40 },
41 {
42 "id": "settings.workspaces.tryReloadWorkspaces",
43 "defaultMessage": "!!!Try again",
44 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
45 "start": {
46 "line": 29,
47 "column": 23
48 },
49 "end": {
50 "line": 32,
51 "column": 3
52 }
53 },
54 {
55 "id": "settings.workspaces.updatedInfo",
56 "defaultMessage": "!!!Your changes have been saved",
57 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
58 "start": {
59 "line": 33,
60 "column": 15
61 },
62 "end": {
63 "line": 36,
64 "column": 3
65 }
66 },
67 {
68 "id": "settings.workspaces.deletedInfo",
69 "defaultMessage": "!!!Workspace has been deleted",
70 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
71 "start": {
72 "line": 37,
73 "column": 15
74 },
75 "end": {
76 "line": 40,
77 "column": 3
78 }
79 },
80 {
81 "id": "settings.workspaces.workspaceFeatureInfo",
82 "defaultMessage": "!!!Info about workspace feature",
83 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
84 "start": {
85 "line": 41,
86 "column": 24
87 },
88 "end": {
89 "line": 44,
90 "column": 3
91 }
92 },
93 {
94 "id": "settings.workspaces.workspaceFeatureHeadline",
95 "defaultMessage": "!!!Less is More: Introducing Franz Workspaces",
96 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
97 "start": {
98 "line": 45,
99 "column": 28
100 },
101 "end": {
102 "line": 48,
103 "column": 3
104 }
105 }
106] \ No newline at end of file
diff --git a/src/i18n/messages/src/lib/Menu.json b/src/i18n/messages/src/lib/Menu.json
index 9314f5cce..3889d39e0 100644
--- a/src/i18n/messages/src/lib/Menu.json
+++ b/src/i18n/messages/src/lib/Menu.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Edit", 4 "defaultMessage": "!!!Edit",
5 "file": "src/lib/Menu.js", 5 "file": "src/lib/Menu.js",
6 "start": { 6 "start": {
7 "line": 10, 7 "line": 13,
8 "column": 8 8 "column": 8
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 13, 11 "line": 16,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Undo", 17 "defaultMessage": "!!!Undo",
18 "file": "src/lib/Menu.js", 18 "file": "src/lib/Menu.js",
19 "start": { 19 "start": {
20 "line": 14, 20 "line": 17,
21 "column": 8 21 "column": 8
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 17, 24 "line": 20,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Redo", 30 "defaultMessage": "!!!Redo",
31 "file": "src/lib/Menu.js", 31 "file": "src/lib/Menu.js",
32 "start": { 32 "start": {
33 "line": 18, 33 "line": 21,
34 "column": 8 34 "column": 8
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 21, 37 "line": 24,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Cut", 43 "defaultMessage": "!!!Cut",
44 "file": "src/lib/Menu.js", 44 "file": "src/lib/Menu.js",
45 "start": { 45 "start": {
46 "line": 22, 46 "line": 25,
47 "column": 7 47 "column": 7
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 25, 50 "line": 28,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Copy", 56 "defaultMessage": "!!!Copy",
57 "file": "src/lib/Menu.js", 57 "file": "src/lib/Menu.js",
58 "start": { 58 "start": {
59 "line": 26, 59 "line": 29,
60 "column": 8 60 "column": 8
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 29, 63 "line": 32,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Paste", 69 "defaultMessage": "!!!Paste",
70 "file": "src/lib/Menu.js", 70 "file": "src/lib/Menu.js",
71 "start": { 71 "start": {
72 "line": 30, 72 "line": 33,
73 "column": 9 73 "column": 9
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 33, 76 "line": 36,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Paste And Match Style", 82 "defaultMessage": "!!!Paste And Match Style",
83 "file": "src/lib/Menu.js", 83 "file": "src/lib/Menu.js",
84 "start": { 84 "start": {
85 "line": 34, 85 "line": 37,
86 "column": 22 86 "column": 22
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 37, 89 "line": 40,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Delete", 95 "defaultMessage": "!!!Delete",
96 "file": "src/lib/Menu.js", 96 "file": "src/lib/Menu.js",
97 "start": { 97 "start": {
98 "line": 38, 98 "line": 41,
99 "column": 10 99 "column": 10
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 41, 102 "line": 44,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Select All", 108 "defaultMessage": "!!!Select All",
109 "file": "src/lib/Menu.js", 109 "file": "src/lib/Menu.js",
110 "start": { 110 "start": {
111 "line": 42, 111 "line": 45,
112 "column": 13 112 "column": 13
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 45, 115 "line": 48,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Speech", 121 "defaultMessage": "!!!Speech",
122 "file": "src/lib/Menu.js", 122 "file": "src/lib/Menu.js",
123 "start": { 123 "start": {
124 "line": 46, 124 "line": 49,
125 "column": 10 125 "column": 10
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 49, 128 "line": 52,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!Start Speaking", 134 "defaultMessage": "!!!Start Speaking",
135 "file": "src/lib/Menu.js", 135 "file": "src/lib/Menu.js",
136 "start": { 136 "start": {
137 "line": 50, 137 "line": 53,
138 "column": 17 138 "column": 17
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 53, 141 "line": 56,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Stop Speaking", 147 "defaultMessage": "!!!Stop Speaking",
148 "file": "src/lib/Menu.js", 148 "file": "src/lib/Menu.js",
149 "start": { 149 "start": {
150 "line": 54, 150 "line": 57,
151 "column": 16 151 "column": 16
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 57, 154 "line": 60,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!Start Dictation", 160 "defaultMessage": "!!!Start Dictation",
161 "file": "src/lib/Menu.js", 161 "file": "src/lib/Menu.js",
162 "start": { 162 "start": {
163 "line": 58, 163 "line": 61,
164 "column": 18 164 "column": 18
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 61, 167 "line": 64,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!Emoji & Symbols", 173 "defaultMessage": "!!!Emoji & Symbols",
174 "file": "src/lib/Menu.js", 174 "file": "src/lib/Menu.js",
175 "start": { 175 "start": {
176 "line": 62, 176 "line": 65,
177 "column": 16 177 "column": 16
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 65, 180 "line": 68,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,11 @@
186 "defaultMessage": "!!!Actual Size", 186 "defaultMessage": "!!!Actual Size",
187 "file": "src/lib/Menu.js", 187 "file": "src/lib/Menu.js",
188 "start": { 188 "start": {
189 "line": 66, 189 "line": 69,
190 "column": 13 190 "column": 13
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 69, 193 "line": 72,
194 "column": 3 194 "column": 3
195 } 195 }
196 }, 196 },
@@ -199,11 +199,11 @@
199 "defaultMessage": "!!!Zoom In", 199 "defaultMessage": "!!!Zoom In",
200 "file": "src/lib/Menu.js", 200 "file": "src/lib/Menu.js",
201 "start": { 201 "start": {
202 "line": 70, 202 "line": 73,
203 "column": 10 203 "column": 10
204 }, 204 },
205 "end": { 205 "end": {
206 "line": 73, 206 "line": 76,
207 "column": 3 207 "column": 3
208 } 208 }
209 }, 209 },
@@ -212,11 +212,11 @@
212 "defaultMessage": "!!!Zoom Out", 212 "defaultMessage": "!!!Zoom Out",
213 "file": "src/lib/Menu.js", 213 "file": "src/lib/Menu.js",
214 "start": { 214 "start": {
215 "line": 74, 215 "line": 77,
216 "column": 11 216 "column": 11
217 }, 217 },
218 "end": { 218 "end": {
219 "line": 77, 219 "line": 80,
220 "column": 3 220 "column": 3
221 } 221 }
222 }, 222 },
@@ -225,11 +225,11 @@
225 "defaultMessage": "!!!Enter Full Screen", 225 "defaultMessage": "!!!Enter Full Screen",
226 "file": "src/lib/Menu.js", 226 "file": "src/lib/Menu.js",
227 "start": { 227 "start": {
228 "line": 78, 228 "line": 81,
229 "column": 19 229 "column": 19
230 }, 230 },
231 "end": { 231 "end": {
232 "line": 81, 232 "line": 84,
233 "column": 3 233 "column": 3
234 } 234 }
235 }, 235 },
@@ -238,11 +238,11 @@
238 "defaultMessage": "!!!Exit Full Screen", 238 "defaultMessage": "!!!Exit Full Screen",
239 "file": "src/lib/Menu.js", 239 "file": "src/lib/Menu.js",
240 "start": { 240 "start": {
241 "line": 82, 241 "line": 85,
242 "column": 18 242 "column": 18
243 }, 243 },
244 "end": { 244 "end": {
245 "line": 85, 245 "line": 88,
246 "column": 3 246 "column": 3
247 } 247 }
248 }, 248 },
@@ -251,11 +251,11 @@
251 "defaultMessage": "!!!Toggle Full Screen", 251 "defaultMessage": "!!!Toggle Full Screen",
252 "file": "src/lib/Menu.js", 252 "file": "src/lib/Menu.js",
253 "start": { 253 "start": {
254 "line": 86, 254 "line": 89,
255 "column": 20 255 "column": 20
256 }, 256 },
257 "end": { 257 "end": {
258 "line": 89, 258 "line": 92,
259 "column": 3 259 "column": 3
260 } 260 }
261 }, 261 },
@@ -264,11 +264,11 @@
264 "defaultMessage": "!!!Toggle Developer Tools", 264 "defaultMessage": "!!!Toggle Developer Tools",
265 "file": "src/lib/Menu.js", 265 "file": "src/lib/Menu.js",
266 "start": { 266 "start": {
267 "line": 90, 267 "line": 93,
268 "column": 18 268 "column": 18
269 }, 269 },
270 "end": { 270 "end": {
271 "line": 93, 271 "line": 96,
272 "column": 3 272 "column": 3
273 } 273 }
274 }, 274 },
@@ -277,11 +277,11 @@
277 "defaultMessage": "!!!Toggle Service Developer Tools", 277 "defaultMessage": "!!!Toggle Service Developer Tools",
278 "file": "src/lib/Menu.js", 278 "file": "src/lib/Menu.js",
279 "start": { 279 "start": {
280 "line": 94, 280 "line": 97,
281 "column": 25 281 "column": 25
282 }, 282 },
283 "end": { 283 "end": {
284 "line": 97, 284 "line": 100,
285 "column": 3 285 "column": 3
286 } 286 }
287 }, 287 },
@@ -290,11 +290,11 @@
290 "defaultMessage": "!!!Reload Service", 290 "defaultMessage": "!!!Reload Service",
291 "file": "src/lib/Menu.js", 291 "file": "src/lib/Menu.js",
292 "start": { 292 "start": {
293 "line": 98, 293 "line": 101,
294 "column": 17 294 "column": 17
295 }, 295 },
296 "end": { 296 "end": {
297 "line": 101, 297 "line": 104,
298 "column": 3 298 "column": 3
299 } 299 }
300 }, 300 },
@@ -303,11 +303,11 @@
303 "defaultMessage": "!!!Reload Franz", 303 "defaultMessage": "!!!Reload Franz",
304 "file": "src/lib/Menu.js", 304 "file": "src/lib/Menu.js",
305 "start": { 305 "start": {
306 "line": 102, 306 "line": 105,
307 "column": 15 307 "column": 15
308 }, 308 },
309 "end": { 309 "end": {
310 "line": 105, 310 "line": 108,
311 "column": 3 311 "column": 3
312 } 312 }
313 }, 313 },
@@ -316,11 +316,11 @@
316 "defaultMessage": "!!!Minimize", 316 "defaultMessage": "!!!Minimize",
317 "file": "src/lib/Menu.js", 317 "file": "src/lib/Menu.js",
318 "start": { 318 "start": {
319 "line": 106, 319 "line": 109,
320 "column": 12 320 "column": 12
321 }, 321 },
322 "end": { 322 "end": {
323 "line": 109, 323 "line": 112,
324 "column": 3 324 "column": 3
325 } 325 }
326 }, 326 },
@@ -329,11 +329,11 @@
329 "defaultMessage": "!!!Close", 329 "defaultMessage": "!!!Close",
330 "file": "src/lib/Menu.js", 330 "file": "src/lib/Menu.js",
331 "start": { 331 "start": {
332 "line": 110, 332 "line": 113,
333 "column": 9 333 "column": 9
334 }, 334 },
335 "end": { 335 "end": {
336 "line": 113, 336 "line": 116,
337 "column": 3 337 "column": 3
338 } 338 }
339 }, 339 },
@@ -342,11 +342,11 @@
342 "defaultMessage": "!!!Learn More", 342 "defaultMessage": "!!!Learn More",
343 "file": "src/lib/Menu.js", 343 "file": "src/lib/Menu.js",
344 "start": { 344 "start": {
345 "line": 114, 345 "line": 117,
346 "column": 13 346 "column": 13
347 }, 347 },
348 "end": { 348 "end": {
349 "line": 117, 349 "line": 120,
350 "column": 3 350 "column": 3
351 } 351 }
352 }, 352 },
@@ -355,11 +355,11 @@
355 "defaultMessage": "!!!Changelog", 355 "defaultMessage": "!!!Changelog",
356 "file": "src/lib/Menu.js", 356 "file": "src/lib/Menu.js",
357 "start": { 357 "start": {
358 "line": 118, 358 "line": 121,
359 "column": 13 359 "column": 13
360 }, 360 },
361 "end": { 361 "end": {
362 "line": 121, 362 "line": 124,
363 "column": 3 363 "column": 3
364 } 364 }
365 }, 365 },
@@ -368,11 +368,11 @@
368 "defaultMessage": "!!!Support", 368 "defaultMessage": "!!!Support",
369 "file": "src/lib/Menu.js", 369 "file": "src/lib/Menu.js",
370 "start": { 370 "start": {
371 "line": 122, 371 "line": 125,
372 "column": 11 372 "column": 11
373 }, 373 },
374 "end": { 374 "end": {
375 "line": 125, 375 "line": 128,
376 "column": 3 376 "column": 3
377 } 377 }
378 }, 378 },
@@ -381,11 +381,11 @@
381 "defaultMessage": "!!!Terms of Service", 381 "defaultMessage": "!!!Terms of Service",
382 "file": "src/lib/Menu.js", 382 "file": "src/lib/Menu.js",
383 "start": { 383 "start": {
384 "line": 126, 384 "line": 129,
385 "column": 7 385 "column": 7
386 }, 386 },
387 "end": { 387 "end": {
388 "line": 129, 388 "line": 132,
389 "column": 3 389 "column": 3
390 } 390 }
391 }, 391 },
@@ -394,11 +394,11 @@
394 "defaultMessage": "!!!Privacy Statement", 394 "defaultMessage": "!!!Privacy Statement",
395 "file": "src/lib/Menu.js", 395 "file": "src/lib/Menu.js",
396 "start": { 396 "start": {
397 "line": 130, 397 "line": 133,
398 "column": 11 398 "column": 11
399 }, 399 },
400 "end": { 400 "end": {
401 "line": 133, 401 "line": 136,
402 "column": 3 402 "column": 3
403 } 403 }
404 }, 404 },
@@ -407,11 +407,11 @@
407 "defaultMessage": "!!!File", 407 "defaultMessage": "!!!File",
408 "file": "src/lib/Menu.js", 408 "file": "src/lib/Menu.js",
409 "start": { 409 "start": {
410 "line": 134, 410 "line": 137,
411 "column": 8 411 "column": 8
412 }, 412 },
413 "end": { 413 "end": {
414 "line": 137, 414 "line": 140,
415 "column": 3 415 "column": 3
416 } 416 }
417 }, 417 },
@@ -420,11 +420,11 @@
420 "defaultMessage": "!!!View", 420 "defaultMessage": "!!!View",
421 "file": "src/lib/Menu.js", 421 "file": "src/lib/Menu.js",
422 "start": { 422 "start": {
423 "line": 138, 423 "line": 141,
424 "column": 8 424 "column": 8
425 }, 425 },
426 "end": { 426 "end": {
427 "line": 141, 427 "line": 144,
428 "column": 3 428 "column": 3
429 } 429 }
430 }, 430 },
@@ -433,11 +433,11 @@
433 "defaultMessage": "!!!Services", 433 "defaultMessage": "!!!Services",
434 "file": "src/lib/Menu.js", 434 "file": "src/lib/Menu.js",
435 "start": { 435 "start": {
436 "line": 142, 436 "line": 145,
437 "column": 12 437 "column": 12
438 }, 438 },
439 "end": { 439 "end": {
440 "line": 145, 440 "line": 148,
441 "column": 3 441 "column": 3
442 } 442 }
443 }, 443 },
@@ -446,11 +446,11 @@
446 "defaultMessage": "!!!Window", 446 "defaultMessage": "!!!Window",
447 "file": "src/lib/Menu.js", 447 "file": "src/lib/Menu.js",
448 "start": { 448 "start": {
449 "line": 146, 449 "line": 149,
450 "column": 10 450 "column": 10
451 }, 451 },
452 "end": { 452 "end": {
453 "line": 149, 453 "line": 152,
454 "column": 3 454 "column": 3
455 } 455 }
456 }, 456 },
@@ -459,11 +459,11 @@
459 "defaultMessage": "!!!Help", 459 "defaultMessage": "!!!Help",
460 "file": "src/lib/Menu.js", 460 "file": "src/lib/Menu.js",
461 "start": { 461 "start": {
462 "line": 150, 462 "line": 153,
463 "column": 8 463 "column": 8
464 }, 464 },
465 "end": { 465 "end": {
466 "line": 153, 466 "line": 156,
467 "column": 3 467 "column": 3
468 } 468 }
469 }, 469 },
@@ -472,11 +472,11 @@
472 "defaultMessage": "!!!About Franz", 472 "defaultMessage": "!!!About Franz",
473 "file": "src/lib/Menu.js", 473 "file": "src/lib/Menu.js",
474 "start": { 474 "start": {
475 "line": 154, 475 "line": 157,
476 "column": 9 476 "column": 9
477 }, 477 },
478 "end": { 478 "end": {
479 "line": 157, 479 "line": 160,
480 "column": 3 480 "column": 3
481 } 481 }
482 }, 482 },
@@ -485,11 +485,11 @@
485 "defaultMessage": "!!!Settings", 485 "defaultMessage": "!!!Settings",
486 "file": "src/lib/Menu.js", 486 "file": "src/lib/Menu.js",
487 "start": { 487 "start": {
488 "line": 158, 488 "line": 161,
489 "column": 12 489 "column": 12
490 }, 490 },
491 "end": { 491 "end": {
492 "line": 161, 492 "line": 164,
493 "column": 3 493 "column": 3
494 } 494 }
495 }, 495 },
@@ -498,11 +498,11 @@
498 "defaultMessage": "!!!Hide", 498 "defaultMessage": "!!!Hide",
499 "file": "src/lib/Menu.js", 499 "file": "src/lib/Menu.js",
500 "start": { 500 "start": {
501 "line": 162, 501 "line": 165,
502 "column": 8 502 "column": 8
503 }, 503 },
504 "end": { 504 "end": {
505 "line": 165, 505 "line": 168,
506 "column": 3 506 "column": 3
507 } 507 }
508 }, 508 },
@@ -511,11 +511,11 @@
511 "defaultMessage": "!!!Hide Others", 511 "defaultMessage": "!!!Hide Others",
512 "file": "src/lib/Menu.js", 512 "file": "src/lib/Menu.js",
513 "start": { 513 "start": {
514 "line": 166, 514 "line": 169,
515 "column": 14 515 "column": 14
516 }, 516 },
517 "end": { 517 "end": {
518 "line": 169, 518 "line": 172,
519 "column": 3 519 "column": 3
520 } 520 }
521 }, 521 },
@@ -524,11 +524,11 @@
524 "defaultMessage": "!!!Unhide", 524 "defaultMessage": "!!!Unhide",
525 "file": "src/lib/Menu.js", 525 "file": "src/lib/Menu.js",
526 "start": { 526 "start": {
527 "line": 170, 527 "line": 173,
528 "column": 10 528 "column": 10
529 }, 529 },
530 "end": { 530 "end": {
531 "line": 173, 531 "line": 176,
532 "column": 3 532 "column": 3
533 } 533 }
534 }, 534 },
@@ -537,11 +537,11 @@
537 "defaultMessage": "!!!Quit", 537 "defaultMessage": "!!!Quit",
538 "file": "src/lib/Menu.js", 538 "file": "src/lib/Menu.js",
539 "start": { 539 "start": {
540 "line": 174, 540 "line": 177,
541 "column": 8 541 "column": 8
542 }, 542 },
543 "end": { 543 "end": {
544 "line": 177, 544 "line": 180,
545 "column": 3 545 "column": 3
546 } 546 }
547 }, 547 },
@@ -550,11 +550,50 @@
550 "defaultMessage": "!!!Add New Service...", 550 "defaultMessage": "!!!Add New Service...",
551 "file": "src/lib/Menu.js", 551 "file": "src/lib/Menu.js",
552 "start": { 552 "start": {
553 "line": 178, 553 "line": 181,
554 "column": 17 554 "column": 17
555 }, 555 },
556 "end": { 556 "end": {
557 "line": 181, 557 "line": 184,
558 "column": 3
559 }
560 },
561 {
562 "id": "menu.workspaces.addNewWorkspace",
563 "defaultMessage": "!!!Add New Workspace...",
564 "file": "src/lib/Menu.js",
565 "start": {
566 "line": 185,
567 "column": 19
568 },
569 "end": {
570 "line": 188,
571 "column": 3
572 }
573 },
574 {
575 "id": "menu.workspaces.openWorkspaceDrawer",
576 "defaultMessage": "!!!Open workspace drawer",
577 "file": "src/lib/Menu.js",
578 "start": {
579 "line": 189,
580 "column": 23
581 },
582 "end": {
583 "line": 192,
584 "column": 3
585 }
586 },
587 {
588 "id": "menu.workspaces.closeWorkspaceDrawer",
589 "defaultMessage": "!!!Close workspace drawer",
590 "file": "src/lib/Menu.js",
591 "start": {
592 "line": 193,
593 "column": 24
594 },
595 "end": {
596 "line": 196,
558 "column": 3 597 "column": 3
559 } 598 }
560 }, 599 },
@@ -563,11 +602,11 @@
563 "defaultMessage": "!!!Activate next service...", 602 "defaultMessage": "!!!Activate next service...",
564 "file": "src/lib/Menu.js", 603 "file": "src/lib/Menu.js",
565 "start": { 604 "start": {
566 "line": 182, 605 "line": 197,
567 "column": 23 606 "column": 23
568 }, 607 },
569 "end": { 608 "end": {
570 "line": 185, 609 "line": 200,
571 "column": 3 610 "column": 3
572 } 611 }
573 }, 612 },
@@ -576,11 +615,11 @@
576 "defaultMessage": "!!!Activate previous service...", 615 "defaultMessage": "!!!Activate previous service...",
577 "file": "src/lib/Menu.js", 616 "file": "src/lib/Menu.js",
578 "start": { 617 "start": {
579 "line": 186, 618 "line": 201,
580 "column": 27 619 "column": 27
581 }, 620 },
582 "end": { 621 "end": {
583 "line": 189, 622 "line": 204,
584 "column": 3 623 "column": 3
585 } 624 }
586 }, 625 },
@@ -589,11 +628,11 @@
589 "defaultMessage": "!!!Disable notifications & audio", 628 "defaultMessage": "!!!Disable notifications & audio",
590 "file": "src/lib/Menu.js", 629 "file": "src/lib/Menu.js",
591 "start": { 630 "start": {
592 "line": 190, 631 "line": 205,
593 "column": 11 632 "column": 11
594 }, 633 },
595 "end": { 634 "end": {
596 "line": 193, 635 "line": 208,
597 "column": 3 636 "column": 3
598 } 637 }
599 }, 638 },
@@ -602,11 +641,37 @@
602 "defaultMessage": "!!!Enable notifications & audio", 641 "defaultMessage": "!!!Enable notifications & audio",
603 "file": "src/lib/Menu.js", 642 "file": "src/lib/Menu.js",
604 "start": { 643 "start": {
605 "line": 194, 644 "line": 209,
606 "column": 13 645 "column": 13
607 }, 646 },
608 "end": { 647 "end": {
609 "line": 197, 648 "line": 212,
649 "column": 3
650 }
651 },
652 {
653 "id": "menu.workspaces",
654 "defaultMessage": "!!!Workspaces",
655 "file": "src/lib/Menu.js",
656 "start": {
657 "line": 213,
658 "column": 14
659 },
660 "end": {
661 "line": 216,
662 "column": 3
663 }
664 },
665 {
666 "id": "menu.workspaces.defaultWorkspace",
667 "defaultMessage": "!!!Default",
668 "file": "src/lib/Menu.js",
669 "start": {
670 "line": 217,
671 "column": 20
672 },
673 "end": {
674 "line": 220,
610 "column": 3 675 "column": 3
611 } 676 }
612 } 677 }
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
index 7a60c448f..a4e41c17c 100644
--- a/src/lib/Menu.js
+++ b/src/lib/Menu.js
@@ -3,6 +3,9 @@ import { observable, autorun } from 'mobx';
3import { defineMessages } from 'react-intl'; 3import { defineMessages } from 'react-intl';
4 4
5import { isMac, ctrlKey, cmdKey } from '../environment'; 5import { isMac, ctrlKey, cmdKey } from '../environment';
6import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../features/workspaces/index';
7import { workspaceActions } from '../features/workspaces/actions';
8import { gaEvent } from './analytics';
6 9
7const { app, Menu, dialog } = remote; 10const { app, Menu, dialog } = remote;
8 11
@@ -179,6 +182,18 @@ const menuItems = defineMessages({
179 id: 'menu.services.addNewService', 182 id: 'menu.services.addNewService',
180 defaultMessage: '!!!Add New Service...', 183 defaultMessage: '!!!Add New Service...',
181 }, 184 },
185 addNewWorkspace: {
186 id: 'menu.workspaces.addNewWorkspace',
187 defaultMessage: '!!!Add New Workspace...',
188 },
189 openWorkspaceDrawer: {
190 id: 'menu.workspaces.openWorkspaceDrawer',
191 defaultMessage: '!!!Open workspace drawer',
192 },
193 closeWorkspaceDrawer: {
194 id: 'menu.workspaces.closeWorkspaceDrawer',
195 defaultMessage: '!!!Close workspace drawer',
196 },
182 activateNextService: { 197 activateNextService: {
183 id: 'menu.services.setNextServiceActive', 198 id: 'menu.services.setNextServiceActive',
184 defaultMessage: '!!!Activate next service...', 199 defaultMessage: '!!!Activate next service...',
@@ -195,6 +210,14 @@ const menuItems = defineMessages({
195 id: 'sidebar.unmuteApp', 210 id: 'sidebar.unmuteApp',
196 defaultMessage: '!!!Enable notifications & audio', 211 defaultMessage: '!!!Enable notifications & audio',
197 }, 212 },
213 workspaces: {
214 id: 'menu.workspaces',
215 defaultMessage: '!!!Workspaces',
216 },
217 defaultWorkspace: {
218 id: 'menu.workspaces.defaultWorkspace',
219 defaultMessage: '!!!Default',
220 },
198}); 221});
199 222
200function getActiveWebview() { 223function getActiveWebview() {
@@ -298,6 +321,11 @@ const _templateFactory = intl => [
298 submenu: [], 321 submenu: [],
299 }, 322 },
300 { 323 {
324 label: intl.formatMessage(menuItems.workspaces),
325 submenu: [],
326 visible: workspaceStore.isFeatureEnabled,
327 },
328 {
301 label: intl.formatMessage(menuItems.window), 329 label: intl.formatMessage(menuItems.window),
302 role: 'window', 330 role: 'window',
303 submenu: [ 331 submenu: [
@@ -669,7 +697,7 @@ export default class FranzMenu {
669 }, 697 },
670 ); 698 );
671 699
672 tpl[4].submenu.unshift(about, { 700 tpl[5].submenu.unshift(about, {
673 type: 'separator', 701 type: 'separator',
674 }); 702 });
675 } else { 703 } else {
@@ -704,6 +732,10 @@ export default class FranzMenu {
704 tpl[3].submenu = serviceTpl; 732 tpl[3].submenu = serviceTpl;
705 } 733 }
706 734
735 if (workspaceStore.isFeatureEnabled) {
736 tpl[4].submenu = this.workspacesMenu();
737 }
738
707 this.currentTemplate = tpl; 739 this.currentTemplate = tpl;
708 const menu = Menu.buildFromTemplate(tpl); 740 const menu = Menu.buildFromTemplate(tpl);
709 Menu.setApplicationMenu(menu); 741 Menu.setApplicationMenu(menu);
@@ -754,6 +786,66 @@ export default class FranzMenu {
754 return menu; 786 return menu;
755 } 787 }
756 788
789 workspacesMenu() {
790 const { workspaces, activeWorkspace, isWorkspaceDrawerOpen } = workspaceStore;
791 const { intl } = window.franz;
792 const menu = [];
793
794 // Add new workspace item:
795 menu.push({
796 label: intl.formatMessage(menuItems.addNewWorkspace),
797 accelerator: `${cmdKey}+Shift+N`,
798 click: () => {
799 workspaceActions.openWorkspaceSettings();
800 },
801 enabled: this.stores.user.isLoggedIn,
802 });
803
804 // Open workspace drawer:
805 const drawerLabel = (
806 isWorkspaceDrawerOpen ? menuItems.closeWorkspaceDrawer : menuItems.openWorkspaceDrawer
807 );
808 menu.push({
809 label: intl.formatMessage(drawerLabel),
810 accelerator: `${cmdKey}+D`,
811 click: () => {
812 workspaceActions.toggleWorkspaceDrawer();
813 gaEvent(GA_CATEGORY_WORKSPACES, 'toggleDrawer', 'menu');
814 },
815 enabled: this.stores.user.isLoggedIn,
816 }, {
817 type: 'separator',
818 });
819
820 // Default workspace
821 menu.push({
822 label: intl.formatMessage(menuItems.defaultWorkspace),
823 accelerator: `${cmdKey}+Alt+0`,
824 type: 'radio',
825 checked: !activeWorkspace,
826 click: () => {
827 workspaceActions.deactivate();
828 gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'menu');
829 },
830 });
831
832 // Workspace items
833 if (this.stores.user.isPremium) {
834 workspaces.forEach((workspace, i) => menu.push({
835 label: workspace.name,
836 accelerator: i < 9 ? `${cmdKey}+Alt+${i + 1}` : null,
837 type: 'radio',
838 checked: activeWorkspace ? workspace.id === activeWorkspace.id : false,
839 click: () => {
840 workspaceActions.activate({ workspace });
841 gaEvent(GA_CATEGORY_WORKSPACES, 'switch', 'menu');
842 },
843 }));
844 }
845
846 return menu;
847 }
848
757 _getServiceName(service) { 849 _getServiceName(service) {
758 if (service.name) { 850 if (service.name) {
759 return service.name; 851 return service.name;
diff --git a/src/lib/analytics.js b/src/lib/analytics.js
index 0519192d1..e7daa9d06 100644
--- a/src/lib/analytics.js
+++ b/src/lib/analytics.js
@@ -28,12 +28,10 @@ ga('send', 'App');
28 28
29export function gaPage(page) { 29export function gaPage(page) {
30 ga('send', 'pageview', page); 30 ga('send', 'pageview', page);
31
32 debug('GA track page', page); 31 debug('GA track page', page);
33} 32}
34 33
35export function gaEvent(category, action, label) { 34export function gaEvent(category, action, label) {
36 ga('send', 'event', category, action, label); 35 ga('send', 'event', category, action, label);
37 36 debug('GA track event', category, action, label);
38 debug('GA track event', category, action);
39} 37}
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index d2842083c..8fe576813 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -1,4 +1,9 @@
1import { computed, observable, reaction } from 'mobx'; 1import {
2 computed,
3 observable,
4 reaction,
5 runInAction,
6} from 'mobx';
2 7
3import Store from './lib/Store'; 8import Store from './lib/Store';
4import CachedRequest from './lib/CachedRequest'; 9import CachedRequest from './lib/CachedRequest';
@@ -7,6 +12,7 @@ import delayApp from '../features/delayApp';
7import spellchecker from '../features/spellchecker'; 12import spellchecker from '../features/spellchecker';
8import serviceProxy from '../features/serviceProxy'; 13import serviceProxy from '../features/serviceProxy';
9import basicAuth from '../features/basicAuth'; 14import basicAuth from '../features/basicAuth';
15import workspaces from '../features/workspaces';
10import shareFranz from '../features/shareFranz'; 16import shareFranz from '../features/shareFranz';
11 17
12import { DEFAULT_FEATURES_CONFIG } from '../config'; 18import { DEFAULT_FEATURES_CONFIG } from '../config';
@@ -16,13 +22,16 @@ export default class FeaturesStore extends Store {
16 22
17 @observable featuresRequest = new CachedRequest(this.api.features, 'features'); 23 @observable featuresRequest = new CachedRequest(this.api.features, 'features');
18 24
25 @observable features = Object.assign({}, DEFAULT_FEATURES_CONFIG);
26
19 async setup() { 27 async setup() {
20 this.registerReactions([ 28 this.registerReactions([
29 this._updateFeatures,
21 this._monitorLoginStatus.bind(this), 30 this._monitorLoginStatus.bind(this),
22 ]); 31 ]);
23 32
24 await this.featuresRequest._promise; 33 await this.featuresRequest._promise;
25 setTimeout(this._enableFeatures.bind(this), 1); 34 setTimeout(this._setupFeatures.bind(this), 1);
26 35
27 // single key reaction 36 // single key reaction
28 reaction(() => this.stores.user.data.isPremium, () => { 37 reaction(() => this.stores.user.data.isPremium, () => {
@@ -36,13 +45,16 @@ export default class FeaturesStore extends Store {
36 return this.defaultFeaturesRequest.execute().result || DEFAULT_FEATURES_CONFIG; 45 return this.defaultFeaturesRequest.execute().result || DEFAULT_FEATURES_CONFIG;
37 } 46 }
38 47
39 @computed get features() { 48 _updateFeatures = () => {
49 const features = Object.assign({}, DEFAULT_FEATURES_CONFIG);
40 if (this.stores.user.isLoggedIn) { 50 if (this.stores.user.isLoggedIn) {
41 return this.featuresRequest.execute().result || DEFAULT_FEATURES_CONFIG; 51 const requestResult = this.featuresRequest.execute().result;
52 Object.assign(features, requestResult);
42 } 53 }
43 54 runInAction('FeaturesStore::_updateFeatures', () => {
44 return DEFAULT_FEATURES_CONFIG; 55 this.features = features;
45 } 56 });
57 };
46 58
47 _monitorLoginStatus() { 59 _monitorLoginStatus() {
48 if (this.stores.user.isLoggedIn) { 60 if (this.stores.user.isLoggedIn) {
@@ -52,11 +64,12 @@ export default class FeaturesStore extends Store {
52 } 64 }
53 } 65 }
54 66
55 _enableFeatures() { 67 _setupFeatures() {
56 delayApp(this.stores, this.actions); 68 delayApp(this.stores, this.actions);
57 spellchecker(this.stores, this.actions); 69 spellchecker(this.stores, this.actions);
58 serviceProxy(this.stores, this.actions); 70 serviceProxy(this.stores, this.actions);
59 basicAuth(this.stores, this.actions); 71 basicAuth(this.stores, this.actions);
72 workspaces(this.stores, this.actions);
60 shareFranz(this.stores, this.actions); 73 shareFranz(this.stores, this.actions);
61 } 74 }
62} 75}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 69e616f0c..0ec6bf550 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -12,6 +12,7 @@ import Request from './lib/Request';
12import CachedRequest from './lib/CachedRequest'; 12import CachedRequest from './lib/CachedRequest';
13import { matchRoute } from '../helpers/routing-helpers'; 13import { matchRoute } from '../helpers/routing-helpers';
14import { gaEvent } from '../lib/analytics'; 14import { gaEvent } from '../lib/analytics';
15import { workspaceStore } from '../features/workspaces';
15 16
16const debug = require('debug')('Franz:ServiceStore'); 17const debug = require('debug')('Franz:ServiceStore');
17 18
@@ -99,7 +100,6 @@ export default class ServicesStore extends Store {
99 return observable(services.slice().slice().sort((a, b) => a.order - b.order)); 100 return observable(services.slice().slice().sort((a, b) => a.order - b.order));
100 } 101 }
101 } 102 }
102
103 return []; 103 return [];
104 } 104 }
105 105
@@ -108,13 +108,16 @@ export default class ServicesStore extends Store {
108 } 108 }
109 109
110 @computed get allDisplayed() { 110 @computed get allDisplayed() {
111 return this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled; 111 const services = this.stores.settings.all.app.showDisabledServices ? this.all : this.enabled;
112 return workspaceStore.filterServicesByActiveWorkspace(services);
112 } 113 }
113 114
114 // This is just used to avoid unnecessary rerendering of resource-heavy webviews 115 // This is just used to avoid unnecessary rerendering of resource-heavy webviews
115 @computed get allDisplayedUnordered() { 116 @computed get allDisplayedUnordered() {
117 const { showDisabledServices } = this.stores.settings.all.app;
116 const services = this.allServicesRequest.execute().result || []; 118 const services = this.allServicesRequest.execute().result || [];
117 return this.stores.settings.all.app.showDisabledServices ? services : services.filter(service => service.isEnabled); 119 const filteredServices = showDisabledServices ? services : services.filter(service => service.isEnabled);
120 return workspaceStore.filterServicesByActiveWorkspace(filteredServices);
118 } 121 }
119 122
120 @computed get filtered() { 123 @computed get filtered() {
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 {
142 return this.getUserInfoRequest.execute().result || {}; 142 return this.getUserInfoRequest.execute().result || {};
143 } 143 }
144 144
145 @computed get isPremium() {
146 return !!this.data.isPremium;
147 }
148
145 @computed get legacyServices() { 149 @computed get legacyServices() {
146 return this.getLegacyServicesRequest.execute() || {}; 150 return this.getLegacyServicesRequest.execute() || {};
147 } 151 }
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.js
index 04f528156..486de8a49 100644
--- a/src/stores/lib/Request.js
+++ b/src/stores/lib/Request.js
@@ -85,6 +85,8 @@ export default class Request {
85 return this.execute(...this._currentApiCall.args); 85 return this.execute(...this._currentApiCall.args);
86 } 86 }
87 87
88 retry = () => this.reload();
89
88 isExecutingWithArgs(...args) { 90 isExecutingWithArgs(...args) {
89 return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args); 91 return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args);
90 } 92 }
@@ -107,7 +109,7 @@ export default class Request {
107 Request._hooks.forEach(hook => hook(this)); 109 Request._hooks.forEach(hook => hook(this));
108 } 110 }
109 111
110 reset() { 112 reset = () => {
111 this.result = null; 113 this.result = null;
112 this.isExecuting = false; 114 this.isExecuting = false;
113 this.isError = false; 115 this.isError = false;
@@ -116,5 +118,5 @@ export default class Request {
116 this._promise = Promise; 118 this._promise = Promise;
117 119
118 return this; 120 return this;
119 } 121 };
120} 122}
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
index 9a003a922..e858b7904 100644
--- a/src/styles/layout.scss
+++ b/src/styles/layout.scss
@@ -18,8 +18,14 @@ html { overflow: hidden; }
18 font-size: 22px; 18 font-size: 22px;
19 19
20 &:hover, 20 &:hover,
21 &:active { color: $dark-theme-gray-smoke; } 21 &:active {
22 &.is-muted { color: $theme-brand-primary; } 22 color: $dark-theme-gray-smoke;
23 }
24
25 &.is-muted,
26 &.is-active {
27 color: $theme-brand-primary;
28 }
23 } 29 }
24 } 30 }
25 31
@@ -33,6 +39,7 @@ html { overflow: hidden; }
33 .app__content { display: flex; } 39 .app__content { display: flex; }
34 40
35 .app__service { 41 .app__service {
42 position: relative;
36 display: flex; 43 display: flex;
37 flex: 1; 44 flex: 1;
38 flex-direction: column; 45 flex-direction: column;
@@ -84,7 +91,7 @@ html { overflow: hidden; }
84 91
85 &:hover, 92 &:hover,
86 &:active { color: lighten($theme-gray-light, 10%); } 93 &:active { color: lighten($theme-gray-light, 10%); }
87 &.is-muted { color: $theme-brand-primary; } 94 &.is-muted, &.is-active { color: $theme-brand-primary; }
88 &--new-service { padding-bottom: 6px; } 95 &--new-service { padding-bottom: 6px; }
89 } 96 }
90 97
diff --git a/src/styles/main.scss b/src/styles/main.scss
index 784a04d3d..9ba7f5827 100644
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -31,6 +31,9 @@ $mdi-font-path: '../node_modules/mdi/fonts';
31@import './invite.scss'; 31@import './invite.scss';
32@import './title-bar.scss'; 32@import './title-bar.scss';
33 33
34// Workspaces legacy css
35@import '../features/workspaces/styles/workspaces-table';
36
34// form 37// form
35@import './input.scss'; 38@import './input.scss';
36@import './radio.scss'; 39@import './radio.scss';
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
index 750b6bedd..d97d4ac2c 100644
--- a/src/styles/settings.scss
+++ b/src/styles/settings.scss
@@ -68,7 +68,7 @@
68 } 68 }
69 } 69 }
70 70
71 .premium-info { 71 .premium-info {
72 background: $dark-theme-gray-darker; 72 background: $dark-theme-gray-darker;
73 border: 2px solid $theme-brand-primary; 73 border: 2px solid $theme-brand-primary;
74 } 74 }
@@ -85,6 +85,11 @@
85 .badge { 85 .badge {
86 background: $dark-theme-gray-lighter; 86 background: $dark-theme-gray-lighter;
87 color: $dark-theme-gray-smoke; 87 color: $dark-theme-gray-smoke;
88
89 &.badge--pro {
90 background: $theme-brand-primary;
91 padding: 4px 6px 3px 7px;
92 }
88 } 93 }
89 94
90 &:hover { 95 &:hover {
@@ -93,6 +98,11 @@
93 .badge { 98 .badge {
94 background: $dark-theme-gray-lighter; 99 background: $dark-theme-gray-lighter;
95 color: $dark-theme-gray-smoke; 100 color: $dark-theme-gray-smoke;
101
102 &.badge--pro {
103 background: $theme-brand-primary;
104 padding: 4px 6px 3px 7px;
105 }
96 } 106 }
97 } 107 }
98 108
@@ -414,6 +424,7 @@
414 424
415 .settings-navigation__link { 425 .settings-navigation__link {
416 align-items: center; 426 align-items: center;
427 justify-content: space-between;
417 color: $theme-text-color; 428 color: $theme-text-color;
418 display: flex; 429 display: flex;
419 flex-shrink: 0; 430 flex-shrink: 0;
@@ -442,8 +453,8 @@
442 .settings-navigation__expander { flex: 1; } 453 .settings-navigation__expander { flex: 1; }
443 454
444 .badge { 455 .badge {
456
445 display: initial; 457 display: initial;
446 margin-left: 5px;
447 transition: background $theme-transition-time, color $theme-transition-time; 458 transition: background $theme-transition-time, color $theme-transition-time;
448 } 459 }
449 460