aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/appearance/index.ts (renamed from src/features/appearance/index.js)133
-rw-r--r--src/features/basicAuth/Component.js41
-rw-r--r--src/features/basicAuth/Form.ts (renamed from src/features/basicAuth/Form.js)1
-rw-r--r--src/features/basicAuth/mainIpcHandler.ts (renamed from src/features/basicAuth/mainIpcHandler.js)4
-rw-r--r--src/features/basicAuth/store.ts (renamed from src/features/basicAuth/store.js)0
-rw-r--r--src/features/basicAuth/styles.ts (renamed from src/features/basicAuth/styles.js)0
-rw-r--r--src/features/communityRecipes/index.ts (renamed from src/features/communityRecipes/index.js)0
-rw-r--r--src/features/communityRecipes/store.ts (renamed from src/features/communityRecipes/store.js)21
-rw-r--r--src/features/nightlyBuilds/Component.js26
-rw-r--r--src/features/nightlyBuilds/store.ts (renamed from src/features/nightlyBuilds/store.js)0
-rw-r--r--src/features/publishDebugInfo/Component.js94
-rw-r--r--src/features/publishDebugInfo/store.ts (renamed from src/features/publishDebugInfo/store.js)0
-rw-r--r--src/features/quickSwitch/Component.js71
-rw-r--r--src/features/quickSwitch/store.ts (renamed from src/features/quickSwitch/store.js)0
-rw-r--r--src/features/serviceProxy/index.js38
-rw-r--r--src/features/serviceProxy/index.ts54
-rwxr-xr-xsrc/features/settingsWS/actions.ts (renamed from src/features/settingsWS/actions.js)0
-rwxr-xr-xsrc/features/settingsWS/index.ts (renamed from src/features/settingsWS/index.js)11
-rwxr-xr-xsrc/features/settingsWS/state.ts (renamed from src/features/settingsWS/state.js)0
-rwxr-xr-xsrc/features/settingsWS/store.js22
-rw-r--r--src/features/todos/actions.js28
-rw-r--r--src/features/todos/actions.ts31
-rw-r--r--src/features/todos/constants.ts (renamed from src/features/todos/constants.js)0
-rw-r--r--src/features/todos/index.ts (renamed from src/features/todos/index.js)11
-rw-r--r--src/features/todos/preload.js12
-rw-r--r--src/features/utils/ActionBinding.ts (renamed from src/features/utils/ActionBinding.js)0
-rw-r--r--src/features/utils/FeatureStore.js8
-rw-r--r--src/features/webControls/components/WebControls.js20
-rw-r--r--src/features/webControls/containers/WebControlsScreen.js28
-rw-r--r--src/features/workspaces/actions.js27
-rw-r--r--src/features/workspaces/actions.ts30
-rw-r--r--src/features/workspaces/api.ts (renamed from src/features/workspaces/api.js)44
-rw-r--r--src/features/workspaces/components/CreateWorkspaceForm.js21
-rw-r--r--src/features/workspaces/components/EditWorkspaceForm.js36
-rw-r--r--src/features/workspaces/components/WorkspaceDrawer.js59
-rw-r--r--src/features/workspaces/components/WorkspaceDrawerItem.js27
-rw-r--r--src/features/workspaces/components/WorkspaceItem.js14
-rw-r--r--src/features/workspaces/components/WorkspaceSwitchingIndicator.js12
-rw-r--r--src/features/workspaces/components/WorkspacesDashboard.js26
-rw-r--r--src/features/workspaces/constants.ts (renamed from src/features/workspaces/constants.js)0
-rw-r--r--src/features/workspaces/index.ts (renamed from src/features/workspaces/index.js)6
-rw-r--r--src/features/workspaces/models/Workspace.ts (renamed from src/features/workspaces/models/Workspace.js)6
-rw-r--r--src/features/workspaces/store.js19
43 files changed, 540 insertions, 441 deletions
diff --git a/src/features/appearance/index.js b/src/features/appearance/index.ts
index 3aab2fcad..bb93507dc 100644
--- a/src/features/appearance/index.js
+++ b/src/features/appearance/index.ts
@@ -1,8 +1,6 @@
1import color from 'color'; 1import color from 'color';
2import { reaction } from 'mobx'; 2import { reaction } from 'mobx';
3import themeInfo from '../../assets/themeInfo.json'; 3import { DEFAULT_APP_SETTINGS, iconSizeBias } from '../../config';
4import { iconSizeBias } from '../../config';
5import { DEFAULT_APP_SETTINGS } from '../../environment';
6 4
7const STYLE_ELEMENT_ID = 'custom-appearance-style'; 5const STYLE_ELEMENT_ID = 'custom-appearance-style';
8 6
@@ -10,13 +8,15 @@ function createStyleElement() {
10 const styles = document.createElement('style'); 8 const styles = document.createElement('style');
11 styles.id = STYLE_ELEMENT_ID; 9 styles.id = STYLE_ELEMENT_ID;
12 10
13 document.querySelector('head').appendChild(styles); 11 document.querySelector('head')?.appendChild(styles);
14} 12}
15 13
16function setAppearance(style) { 14function setAppearance(style) {
17 const styleElement = document.getElementById(STYLE_ELEMENT_ID); 15 const styleElement = document.querySelector(`#${STYLE_ELEMENT_ID}`);
18 16
19 styleElement.innerHTML = style; 17 if (styleElement) {
18 styleElement.innerHTML = style;
19 }
20} 20}
21 21
22// See https://github.com/Qix-/color/issues/53#issuecomment-656590710 22// See https://github.com/Qix-/color/issues/53#issuecomment-656590710
@@ -26,24 +26,64 @@ function darkenAbsolute(originalColor, absoluteChange) {
26} 26}
27 27
28function generateAccentStyle(accentColorStr) { 28function generateAccentStyle(accentColorStr) {
29 let style = '';
30
31 Object.keys(themeInfo).forEach((property) => {
32 style += `
33 ${themeInfo[property]} {
34 ${property}: ${accentColorStr};
35 }
36 `;
37 });
38
39 let accentColor = color(DEFAULT_APP_SETTINGS.accentColor); 29 let accentColor = color(DEFAULT_APP_SETTINGS.accentColor);
40 try { 30 try {
41 accentColor = color(accentColorStr); 31 accentColor = color(accentColorStr);
42 } catch (e) { 32 } catch {
43 // Ignore invalid accent color. 33 // Ignore invalid accent color.
44 } 34 }
45 const darkerColorStr = darkenAbsolute(accentColor, 5).hex(); 35 const darkerColorStr = darkenAbsolute(accentColor, 5).hex();
46 style += ` 36 return `
37 .theme__dark .app .sidebar .sidebar__button.is-muted,
38 .theme__dark .app .sidebar .sidebar__button.is-active,
39 .sidebar .sidebar__button.is-muted,
40 .sidebar .sidebar__button.is-active,
41 .tab-item.is-active,
42 .settings .account .invoices .invoices__action button,
43 .settings-navigation .settings-navigation__link.is-active .badge,
44 a.link,
45 button.link,
46 .auth .welcome .button:hover,
47 .auth .welcome .button__inverted,
48 .franz-form .franz-form__radio.is-selected,
49 .theme__dark .franz-form__button.franz-form__button--inverted,
50 .franz-form__button.franz-form__button--inverted {
51 color: ${accentColorStr};
52 }
53
54 .settings .settings__header,
55 .settings .settings__close,
56 .settings-navigation .settings-navigation__link.is-active,
57 a.button,
58 button.button,
59 .auth,
60 .info-bar,
61 .info-bar.info-bar--primary,
62 .infobox.infobox--primary,
63 .theme__dark .badge.badge--primary,
64 .theme__dark,
65 .badge.badge--primary,
66 .content-tabs .content-tabs__tabs .content-tabs__item.is-active,
67 #electron-app-title-bar .toolbar-dropdown:not(.open) > .toolbar-button > button:hover,
68 #electron-app-title-bar .list-item.selected .menu-item,
69 #electron-app-title-bar .list-item.selected:focus .menu-item,
70 .theme__dark .quick-switch .active,
71 .franz-form .franz-form__toggle-wrapper .franz-form__toggle.is-active .franz-form__toggle-button,
72 .theme__dark .franz-form__button,
73 .franz-form__button,
74 .ferdi__fab,
75 .franz-form .franz-form__slider-wrapper .slider::-webkit-slider-thumb {
76 background: ${accentColorStr};
77 }
78
79 .settings .settings__header .separator {
80 border-right-color: ${accentColorStr};
81 }
82
83 .franz-form .franz-form__radio.is-selected {
84 border-color: ${accentColorStr};
85 }
86
47 a.button:hover, button.button:hover { 87 a.button:hover, button.button:hover {
48 background: ${darkenAbsolute(accentColor, 10).hex()}; 88 background: ${darkenAbsolute(accentColor, 10).hex()};
49 } 89 }
@@ -72,27 +112,27 @@ function generateAccentStyle(accentColorStr) {
72 background: ${accentColor.lighten(0.35).hex()}; 112 background: ${accentColor.lighten(0.35).hex()};
73 } 113 }
74 `; 114 `;
75
76 return style;
77} 115}
78 116
79function generateServiceRibbonWidthStyle(widthStr, iconSizeStr, vertical) { 117function generateServiceRibbonWidthStyle(widthStr, iconSizeStr, vertical) {
80 const width = Number(widthStr); 118 const width = Number(widthStr);
81 const iconSize = Number(iconSizeStr) - iconSizeBias; 119 const iconSize = Number(iconSizeStr) - iconSizeBias;
82 120
83 return vertical ? ` 121 return vertical
122 ? `
84 .tab-item { 123 .tab-item {
85 width: ${width - 2}px !important; 124 width: ${width - 2}px !important;
86 height: ${width - 5 + iconSize}px !important; 125 height: ${width - 5 + iconSize}px !important;
87 min-height: unset; 126 min-height: unset;
88 } 127 }
89 .tab-item .tab-item__icon { 128 .tab-item .tab-item__icon {
90 width: ${(width / 2) + iconSize}px !important; 129 width: ${width / 2 + iconSize}px !important;
91 } 130 }
92 .sidebar__button { 131 .sidebar__button {
93 font-size: ${width / 3}px !important; 132 font-size: ${width / 3}px !important;
94 } 133 }
95 ` : ` 134 `
135 : `
96 .sidebar { 136 .sidebar {
97 width: ${width}px !important; 137 width: ${width}px !important;
98 } 138 }
@@ -101,7 +141,7 @@ function generateServiceRibbonWidthStyle(widthStr, iconSizeStr, vertical) {
101 height: ${width - 5 + iconSize}px !important; 141 height: ${width - 5 + iconSize}px !important;
102 } 142 }
103 .tab-item .tab-item__icon { 143 .tab-item .tab-item__icon {
104 width: ${(width / 2) + iconSize}px !important; 144 width: ${width / 2 + iconSize}px !important;
105 } 145 }
106 .sidebar__button { 146 .sidebar__button {
107 font-size: ${width / 3}px !important; 147 font-size: ${width / 3}px !important;
@@ -129,14 +169,14 @@ function generateShowDragAreaStyle(accentColor) {
129} 169}
130 170
131function generateVerticalStyle(widthStr, alwaysShowWorkspaces) { 171function generateVerticalStyle(widthStr, alwaysShowWorkspaces) {
132 if (!document.getElementById('vertical-style')) { 172 if (!document.querySelector('#vertical-style')) {
133 const link = document.createElement('link'); 173 const link = document.createElement('link');
134 link.id = 'vertical-style'; 174 link.id = 'vertical-style';
135 link.rel = 'stylesheet'; 175 link.rel = 'stylesheet';
136 link.type = 'text/css'; 176 link.type = 'text/css';
137 link.href = './styles/vertical.css'; 177 link.href = './styles/vertical.css';
138 178
139 document.head.appendChild(link); 179 document.head.append(link);
140 } 180 }
141 const width = Number(widthStr); 181 const width = Number(widthStr);
142 const sidebarWidth = width - 4; 182 const sidebarWidth = width - 4;
@@ -144,20 +184,19 @@ function generateVerticalStyle(widthStr, alwaysShowWorkspaces) {
144 184
145 return ` 185 return `
146 .sidebar { 186 .sidebar {
147 height: ${sidebarWidth + verticalStyleOffset + 1}px !important; 187 ${
148 ${alwaysShowWorkspaces ? ` 188 alwaysShowWorkspaces
189 ? `
149 width: calc(100% - 300px) !important; 190 width: calc(100% - 300px) !important;
150 ` : ''} 191 `
192 : ''
193 }
151 } 194 }
152 195
153 .sidebar .sidebar__button { 196 .sidebar .sidebar__button {
154 width: ${width}px; 197 width: ${width}px;
155 } 198 }
156 199
157 .app .app__content {
158 padding-top: ${sidebarWidth + verticalStyleOffset + 1}px !important;
159 }
160
161 .workspaces-drawer { 200 .workspaces-drawer {
162 margin-top: -${sidebarWidth - verticalStyleOffset - 1}px !important; 201 margin-top: -${sidebarWidth - verticalStyleOffset - 1}px !important;
163 } 202 }
@@ -192,21 +231,31 @@ function generateStyle(settings) {
192 alwaysShowWorkspaces, 231 alwaysShowWorkspaces,
193 } = settings; 232 } = settings;
194 233
195 if (accentColor.toLowerCase() !== DEFAULT_APP_SETTINGS.accentColor.toLowerCase()) { 234 if (
235 accentColor.toLowerCase() !== DEFAULT_APP_SETTINGS.accentColor.toLowerCase()
236 ) {
196 style += generateAccentStyle(accentColor); 237 style += generateAccentStyle(accentColor);
197 } 238 }
198 if (serviceRibbonWidth !== DEFAULT_APP_SETTINGS.serviceRibbonWidth 239 if (
199 || iconSize !== DEFAULT_APP_SETTINGS.iconSize) { 240 serviceRibbonWidth !== DEFAULT_APP_SETTINGS.serviceRibbonWidth ||
200 style += generateServiceRibbonWidthStyle(serviceRibbonWidth, iconSize, useVerticalStyle); 241 iconSize !== DEFAULT_APP_SETTINGS.iconSize
242 ) {
243 style += generateServiceRibbonWidthStyle(
244 serviceRibbonWidth,
245 iconSize,
246 useVerticalStyle,
247 );
201 } 248 }
202 if (showDragArea) { 249 if (showDragArea) {
203 style += generateShowDragAreaStyle(accentColor); 250 style += generateShowDragAreaStyle(accentColor);
204 } 251 }
205 if (useVerticalStyle) { 252 if (useVerticalStyle) {
206 style += generateVerticalStyle(serviceRibbonWidth, alwaysShowWorkspaces); 253 style += generateVerticalStyle(serviceRibbonWidth, alwaysShowWorkspaces);
207 } else if (document.getElementById('vertical-style')) { 254 } else if (document.querySelector('#vertical-style')) {
208 const link = document.getElementById('vertical-style'); 255 const link = document.querySelector('#vertical-style');
209 document.head.removeChild(link); 256 if (link) {
257 link.remove();
258 }
210 } 259 }
211 if (alwaysShowWorkspaces) { 260 if (alwaysShowWorkspaces) {
212 style += generateOpenWorkspaceStyle(); 261 style += generateOpenWorkspaceStyle();
@@ -225,14 +274,14 @@ export default function initAppearance(stores) {
225 274
226 // Update style when settings change 275 // Update style when settings change
227 reaction( 276 reaction(
228 () => ([ 277 () => [
229 settings.all.app.accentColor, 278 settings.all.app.accentColor,
230 settings.all.app.serviceRibbonWidth, 279 settings.all.app.serviceRibbonWidth,
231 settings.all.app.iconSize, 280 settings.all.app.iconSize,
232 settings.all.app.showDragArea, 281 settings.all.app.showDragArea,
233 settings.all.app.useVerticalStyle, 282 settings.all.app.useVerticalStyle,
234 settings.all.app.alwaysShowWorkspaces, 283 settings.all.app.alwaysShowWorkspaces,
235 ]), 284 ],
236 () => { 285 () => {
237 updateStyle(settings.all.app); 286 updateStyle(settings.all.app);
238 }, 287 },
diff --git a/src/features/basicAuth/Component.js b/src/features/basicAuth/Component.js
index a9601836b..3cf937f98 100644
--- a/src/features/basicAuth/Component.js
+++ b/src/features/basicAuth/Component.js
@@ -2,19 +2,14 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import injectSheet from 'react-jss'; 3import injectSheet from 'react-jss';
4import { observer } from 'mobx-react'; 4import { observer } from 'mobx-react';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6import classnames from 'classnames'; 6import classnames from 'classnames';
7 7
8import Modal from '../../components/ui/Modal'; 8import Modal from '../../components/ui/Modal';
9import Input from '../../components/ui/Input'; 9import Input from '../../components/ui/Input';
10import Button from '../../components/ui/Button'; 10import Button from '../../components/ui/Button';
11 11
12import { 12import { state, resetState, sendCredentials, cancelLogin } from './store';
13 state,
14 resetState,
15 sendCredentials,
16 cancelLogin,
17} from './store';
18import Form from './Form'; 13import Form from './Form';
19 14
20import styles from './styles'; 15import styles from './styles';
@@ -23,17 +18,15 @@ import globalMessages from '../../i18n/globalMessages';
23const messages = defineMessages({ 18const messages = defineMessages({
24 signIn: { 19 signIn: {
25 id: 'feature.basicAuth.signIn', 20 id: 'feature.basicAuth.signIn',
26 defaultMessage: '!!!Sign In', 21 defaultMessage: 'Sign In',
27 }, 22 },
28}); 23});
29 24
30export default @injectSheet(styles) @observer class BasicAuthModal extends Component { 25@injectSheet(styles)
26@observer
27class BasicAuthModal extends Component {
31 static propTypes = { 28 static propTypes = {
32 classes: PropTypes.object.isRequired, 29 classes: PropTypes.object.isRequired,
33 }
34
35 static contextTypes = {
36 intl: intlShape,
37 }; 30 };
38 31
39 submit(e) { 32 submit(e) {
@@ -56,20 +49,15 @@ export default @injectSheet(styles) @observer class BasicAuthModal extends Compo
56 } 49 }
57 50
58 render() { 51 render() {
59 const { 52 const { classes } = this.props;
60 classes,
61 } = this.props;
62 53
63 const { 54 const { isModalVisible, authInfo } = state;
64 isModalVisible,
65 authInfo,
66 } = state;
67 55
68 if (!authInfo) { 56 if (!authInfo) {
69 return null; 57 return null;
70 } 58 }
71 59
72 const { intl } = this.context; 60 const { intl } = this.props;
73 61
74 return ( 62 return (
75 <Modal 63 <Modal
@@ -89,10 +77,7 @@ export default @injectSheet(styles) @observer class BasicAuthModal extends Compo
89 onSubmit={this.submit.bind(this)} 77 onSubmit={this.submit.bind(this)}
90 className={classnames('franz-form', classes.form)} 78 className={classnames('franz-form', classes.form)}
91 > 79 >
92 <Input 80 <Input field={Form.$('user')} showLabel={false} />
93 field={Form.$('user')}
94 showLabel={false}
95 />
96 <Input 81 <Input
97 field={Form.$('password')} 82 field={Form.$('password')}
98 showLabel={false} 83 showLabel={false}
@@ -105,13 +90,11 @@ export default @injectSheet(styles) @observer class BasicAuthModal extends Compo
105 buttonType="secondary" 90 buttonType="secondary"
106 onClick={this.cancel.bind(this)} 91 onClick={this.cancel.bind(this)}
107 /> 92 />
108 <Button 93 <Button type="submit" label={intl.formatMessage(messages.signIn)} />
109 type="submit"
110 label={intl.formatMessage(messages.signIn)}
111 />
112 </div> 94 </div>
113 </form> 95 </form>
114 </Modal> 96 </Modal>
115 ); 97 );
116 } 98 }
117} 99}
100export default injectIntl(BasicAuthModal);
diff --git a/src/features/basicAuth/Form.js b/src/features/basicAuth/Form.ts
index 95721d0e9..e84156d96 100644
--- a/src/features/basicAuth/Form.js
+++ b/src/features/basicAuth/Form.ts
@@ -1,5 +1,6 @@
1import Form from '../../lib/Form'; 1import Form from '../../lib/Form';
2 2
3// @ts-expect-error Expected 0 arguments, but got 1
3export default new Form({ 4export default new Form({
4 fields: { 5 fields: {
5 user: { 6 user: {
diff --git a/src/features/basicAuth/mainIpcHandler.js b/src/features/basicAuth/mainIpcHandler.ts
index ae4e7cf93..4ec3848e8 100644
--- a/src/features/basicAuth/mainIpcHandler.js
+++ b/src/features/basicAuth/mainIpcHandler.ts
@@ -1,6 +1,8 @@
1import { BrowserWindow } from 'electron';
2
1const debug = require('debug')('Ferdi:feature:basicAuth:main'); 3const debug = require('debug')('Ferdi:feature:basicAuth:main');
2 4
3export default function mainIpcHandler(mainWindow, authInfo) { 5export default function mainIpcHandler(mainWindow: BrowserWindow, authInfo) {
4 debug('Sending basic auth call', authInfo); 6 debug('Sending basic auth call', authInfo);
5 7
6 mainWindow.webContents.send('feature:basic-auth', { 8 mainWindow.webContents.send('feature:basic-auth', {
diff --git a/src/features/basicAuth/store.js b/src/features/basicAuth/store.ts
index 0713ff572..0713ff572 100644
--- a/src/features/basicAuth/store.js
+++ b/src/features/basicAuth/store.ts
diff --git a/src/features/basicAuth/styles.js b/src/features/basicAuth/styles.ts
index 6bdaf9a6e..6bdaf9a6e 100644
--- a/src/features/basicAuth/styles.js
+++ b/src/features/basicAuth/styles.ts
diff --git a/src/features/communityRecipes/index.js b/src/features/communityRecipes/index.ts
index 828c6d867..828c6d867 100644
--- a/src/features/communityRecipes/index.js
+++ b/src/features/communityRecipes/index.ts
diff --git a/src/features/communityRecipes/store.js b/src/features/communityRecipes/store.ts
index a3614dd11..a8d358ba0 100644
--- a/src/features/communityRecipes/store.js
+++ b/src/features/communityRecipes/store.ts
@@ -4,7 +4,11 @@ import { FeatureStore } from '../utils/FeatureStore';
4const debug = require('debug')('Ferdi:feature:communityRecipes:store'); 4const debug = require('debug')('Ferdi:feature:communityRecipes:store');
5 5
6export class CommunityRecipesStore extends FeatureStore { 6export class CommunityRecipesStore extends FeatureStore {
7 start(stores, actions) { 7 stores: any;
8
9 actions: any;
10
11 start(stores: any, actions: any) {
8 debug('start'); 12 debug('start');
9 this.stores = stores; 13 this.stores = stores;
10 this.actions = actions; 14 this.actions = actions;
@@ -18,12 +22,17 @@ export class CommunityRecipesStore extends FeatureStore {
18 @computed get communityRecipes() { 22 @computed get communityRecipes() {
19 if (!this.stores) return []; 23 if (!this.stores) return [];
20 24
21 return this.stores.recipePreviews.dev.map((r) => { 25 return this.stores.recipePreviews.dev.map(
22 // TODO: Need to figure out if this is even necessary/used 26 (recipePreview: { isDevRecipe: boolean; author: any[] }) => {
23 r.isDevRecipe = !!r.author.find((a) => a.email === this.stores.user.data.email); 27 // TODO: Need to figure out if this is even necessary/used
28 recipePreview.isDevRecipe = !!recipePreview.author.some(
29 (author: { email: any }) =>
30 author.email === this.stores.user.data.email,
31 );
24 32
25 return r; 33 return recipePreview;
26 }); 34 },
35 );
27 } 36 }
28} 37}
29 38
diff --git a/src/features/nightlyBuilds/Component.js b/src/features/nightlyBuilds/Component.js
index e43287db5..814d529d9 100644
--- a/src/features/nightlyBuilds/Component.js
+++ b/src/features/nightlyBuilds/Component.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, inject } from 'mobx-react'; 3import { observer, inject } from 'mobx-react';
4import injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6import { H1 } from '@meetfranz/ui'; 6import { H1 } from '@meetfranz/ui';
7 7
8import Modal from '../../components/ui/Modal'; 8import Modal from '../../components/ui/Modal';
@@ -16,15 +16,16 @@ import globalMessages from '../../i18n/globalMessages';
16const messages = defineMessages({ 16const messages = defineMessages({
17 title: { 17 title: {
18 id: 'feature.nightlyBuilds.title', 18 id: 'feature.nightlyBuilds.title',
19 defaultMessage: '!!!Nightly Builds', 19 defaultMessage: 'Nightly Builds',
20 }, 20 },
21 info: { 21 info: {
22 id: 'feature.nightlyBuilds.info', 22 id: 'feature.nightlyBuilds.info',
23 defaultMessage: '!!!Nightly builds are highly experimental versions of Ferdi that may contain unpolished or uncompleted features. These nightly builds are mainly used by developers to test their newly developed features and how they will perform in the final build. If you don\'t know what you are doing, we suggest not activating nightly builds.', 23 defaultMessage:
24 "Nightly builds are highly experimental versions of Ferdi that may contain unpolished or uncompleted features. These nightly builds are mainly used by developers to test their newly developed features and how they will perform in the final build. If you don't know what you are doing, we suggest not activating nightly builds.",
24 }, 25 },
25 activate: { 26 activate: {
26 id: 'feature.nightlyBuilds.activate', 27 id: 'feature.nightlyBuilds.activate',
27 defaultMessage: '!!!Activate', 28 defaultMessage: 'Activate',
28 }, 29 },
29}); 30});
30 31
@@ -52,11 +53,10 @@ const styles = () => ({
52 }, 53 },
53}); 54});
54 55
55export default @injectSheet(styles) @inject('stores', 'actions') @observer class NightlyBuildsModal extends Component { 56@injectSheet(styles)
56 static contextTypes = { 57@inject('stores', 'actions')
57 intl: intlShape, 58@observer
58 }; 59class NightlyBuildsModal extends Component {
59
60 close() { 60 close() {
61 ModalState.isModalVisible = false; 61 ModalState.isModalVisible = false;
62 62
@@ -84,11 +84,9 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
84 render() { 84 render() {
85 const { isModalVisible } = ModalState; 85 const { isModalVisible } = ModalState;
86 86
87 const { 87 const { classes } = this.props;
88 classes,
89 } = this.props;
90 88
91 const { intl } = this.context; 89 const { intl } = this.props;
92 90
93 return ( 91 return (
94 <Modal 92 <Modal
@@ -132,3 +130,5 @@ NightlyBuildsModal.wrappedComponent.propTypes = {
132 }).isRequired, 130 }).isRequired,
133 classes: PropTypes.object.isRequired, 131 classes: PropTypes.object.isRequired,
134}; 132};
133
134export default injectIntl(NightlyBuildsModal);
diff --git a/src/features/nightlyBuilds/store.js b/src/features/nightlyBuilds/store.ts
index ed06e5a7d..ed06e5a7d 100644
--- a/src/features/nightlyBuilds/store.js
+++ b/src/features/nightlyBuilds/store.ts
diff --git a/src/features/publishDebugInfo/Component.js b/src/features/publishDebugInfo/Component.js
index 5387bd358..5b5036752 100644
--- a/src/features/publishDebugInfo/Component.js
+++ b/src/features/publishDebugInfo/Component.js
@@ -2,7 +2,7 @@ import { H1 } from '@meetfranz/ui';
2import { inject, observer } from 'mobx-react'; 2import { inject, observer } from 'mobx-react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import React, { Component } from 'react'; 4import React, { Component } from 'react';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7import { state as ModalState } from './store'; 7import { state as ModalState } from './store';
8import { sendAuthRequest } from '../../api/utils/auth'; 8import { sendAuthRequest } from '../../api/utils/auth';
@@ -18,35 +18,37 @@ const debug = require('debug')('Ferdi:feature:publishDebugInfo');
18const messages = defineMessages({ 18const messages = defineMessages({
19 title: { 19 title: {
20 id: 'feature.publishDebugInfo.title', 20 id: 'feature.publishDebugInfo.title',
21 defaultMessage: '!!!Publish debug information', 21 defaultMessage: 'Publish debug information',
22 }, 22 },
23 info: { 23 info: {
24 id: 'feature.publishDebugInfo.info', 24 id: 'feature.publishDebugInfo.info',
25 defaultMessage: '!!!Publishing your debug information helps us find issues and errors in Ferdi. By publishing your debug information you accept Ferdi Debugger\'s privacy policy and terms of service', 25 defaultMessage:
26 "Publishing your debug information helps us find issues and errors in Ferdi. By publishing your debug information you accept Ferdi Debugger's privacy policy and terms of service",
26 }, 27 },
27 error: { 28 error: {
28 id: 'feature.publishDebugInfo.error', 29 id: 'feature.publishDebugInfo.error',
29 defaultMessage: '!!!There was an error while trying to publish the debug information. Please try again later or view the console for more information.', 30 defaultMessage:
31 'There was an error while trying to publish the debug information. Please try again later or view the console for more information.',
30 }, 32 },
31 privacy: { 33 privacy: {
32 id: 'feature.publishDebugInfo.privacy', 34 id: 'feature.publishDebugInfo.privacy',
33 defaultMessage: '!!!Privacy policy', 35 defaultMessage: 'Privacy policy',
34 }, 36 },
35 terms: { 37 terms: {
36 id: 'feature.publishDebugInfo.terms', 38 id: 'feature.publishDebugInfo.terms',
37 defaultMessage: '!!!Terms of service', 39 defaultMessage: 'Terms of service',
38 }, 40 },
39 publish: { 41 publish: {
40 id: 'feature.publishDebugInfo.publish', 42 id: 'feature.publishDebugInfo.publish',
41 defaultMessage: '!!!Accept and publish', 43 defaultMessage: 'Accept and publish',
42 }, 44 },
43 published: { 45 published: {
44 id: 'feature.publishDebugInfo.published', 46 id: 'feature.publishDebugInfo.published',
45 defaultMessage: '!!!Your debug log was published and is now availible at', 47 defaultMessage: 'Your debug log was published and is now availible at',
46 }, 48 },
47}); 49});
48 50
49const styles = (theme) => ({ 51const styles = theme => ({
50 container: { 52 container: {
51 minWidth: '70vw', 53 minWidth: '70vw',
52 }, 54 },
@@ -69,7 +71,8 @@ const styles = (theme) => ({
69 width: '100%', 71 width: '100%',
70 72
71 '& div': { 73 '& div': {
72 fontFamily: 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace', 74 fontFamily:
75 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace',
73 }, 76 },
74 77
75 '& input': { 78 '& input': {
@@ -81,20 +84,19 @@ const styles = (theme) => ({
81 }, 84 },
82}); 85});
83 86
84export default @injectSheet(styles) @inject('stores', 'actions') @observer class PublishDebugLogModal extends Component { 87@injectSheet(styles)
88@inject('stores', 'actions')
89@observer
90class PublishDebugLogModal extends Component {
85 static propTypes = { 91 static propTypes = {
86 classes: PropTypes.object.isRequired, 92 classes: PropTypes.object.isRequired,
87 }; 93 };
88 94
89 static contextTypes = {
90 intl: intlShape,
91 };
92
93 state = { 95 state = {
94 log: null, 96 log: null,
95 error: false, 97 error: false,
96 isSendingLog: false, 98 isSendingLog: false,
97 } 99 };
98 100
99 // Close this modal 101 // Close this modal
100 close() { 102 close() {
@@ -109,12 +111,16 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
109 111
110 const debugInfo = JSON.stringify(this.props.stores.app.debugInfo); 112 const debugInfo = JSON.stringify(this.props.stores.app.debugInfo);
111 113
112 const request = await sendAuthRequest(`${DEBUG_API}/create`, { 114 const request = await sendAuthRequest(
113 method: 'POST', 115 `${DEBUG_API}/create`,
114 body: JSON.stringify({ 116 {
115 log: debugInfo, 117 method: 'POST',
116 }), 118 body: JSON.stringify({
117 }, false); 119 log: debugInfo,
120 }),
121 },
122 false,
123 );
118 124
119 debug(`debugInfo: publishing status: ${request.status}`); 125 debug(`debugInfo: publishing status: ${request.status}`);
120 if (request.status === 200) { 126 if (request.status === 200) {
@@ -140,17 +146,11 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
140 render() { 146 render() {
141 const { isModalVisible } = ModalState; 147 const { isModalVisible } = ModalState;
142 148
143 const { 149 const { classes } = this.props;
144 classes,
145 } = this.props;
146 150
147 const { 151 const { log, error, isSendingLog } = this.state;
148 log,
149 error,
150 isSendingLog,
151 } = this.state;
152 152
153 const { intl } = this.context; 153 const { intl } = this.props;
154 154
155 return ( 155 return (
156 <Modal 156 <Modal
@@ -159,12 +159,12 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
159 close={() => this.close()} 159 close={() => this.close()}
160 > 160 >
161 <div className={classes.container}> 161 <div className={classes.container}>
162 <H1> 162 <H1>{intl.formatMessage(messages.title)}</H1>
163 {intl.formatMessage(messages.title)} 163 {log && (
164 </H1>
165 { log && (
166 <> 164 <>
167 <p className={classes.info}>{intl.formatMessage(messages.published)}</p> 165 <p className={classes.info}>
166 {intl.formatMessage(messages.published)}
167 </p>
168 <Input 168 <Input
169 className={classes.url} 169 className={classes.url}
170 showLabel={false} 170 showLabel={false}
@@ -184,12 +184,24 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
184 184
185 {!log && !error && ( 185 {!log && !error && (
186 <> 186 <>
187 <p className={classes.info}>{intl.formatMessage(messages.info)}</p> 187 <p className={classes.info}>
188 188 {intl.formatMessage(messages.info)}
189 <a href={`${DEBUG_API}/privacy.html`} target="_blank" className={classes.link} rel="noreferrer"> 189 </p>
190
191 <a
192 href={`${DEBUG_API}/privacy.html`}
193 target="_blank"
194 className={classes.link}
195 rel="noreferrer"
196 >
190 {intl.formatMessage(messages.privacy)} 197 {intl.formatMessage(messages.privacy)}
191 </a> 198 </a>
192 <a href={`${DEBUG_API}/terms.html`} target="_blank" className={classes.link} rel="noreferrer"> 199 <a
200 href={`${DEBUG_API}/terms.html`}
201 target="_blank"
202 className={classes.link}
203 rel="noreferrer"
204 >
193 {intl.formatMessage(messages.terms)} 205 {intl.formatMessage(messages.terms)}
194 </a> 206 </a>
195 207
@@ -216,3 +228,5 @@ PublishDebugLogModal.wrappedComponent.propTypes = {
216 service: PropTypes.instanceOf(ServicesStore).isRequired, 228 service: PropTypes.instanceOf(ServicesStore).isRequired,
217 }).isRequired, 229 }).isRequired,
218}; 230};
231
232export default injectIntl(PublishDebugLogModal);
diff --git a/src/features/publishDebugInfo/store.js b/src/features/publishDebugInfo/store.ts
index ed06e5a7d..ed06e5a7d 100644
--- a/src/features/publishDebugInfo/store.js
+++ b/src/features/publishDebugInfo/store.ts
diff --git a/src/features/quickSwitch/Component.js b/src/features/quickSwitch/Component.js
index 78d5d94a0..f21db0ebd 100644
--- a/src/features/quickSwitch/Component.js
+++ b/src/features/quickSwitch/Component.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
4import { observer, inject } from 'mobx-react'; 4import { observer, inject } from 'mobx-react';
5import { reaction } from 'mobx'; 5import { reaction } from 'mobx';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7import { defineMessages, intlShape } from 'react-intl'; 7import { defineMessages, injectIntl } from 'react-intl';
8import { Input } from '@meetfranz/forms'; 8import { Input } from '@meetfranz/forms';
9import { H1 } from '@meetfranz/ui'; 9import { H1 } from '@meetfranz/ui';
10 10
@@ -16,19 +16,20 @@ import ServicesStore from '../../stores/ServicesStore';
16const messages = defineMessages({ 16const messages = defineMessages({
17 title: { 17 title: {
18 id: 'feature.quickSwitch.title', 18 id: 'feature.quickSwitch.title',
19 defaultMessage: '!!!QuickSwitch', 19 defaultMessage: 'QuickSwitch',
20 }, 20 },
21 search: { 21 search: {
22 id: 'feature.quickSwitch.search', 22 id: 'feature.quickSwitch.search',
23 defaultMessage: '!!!Search...', 23 defaultMessage: 'Search...',
24 }, 24 },
25 info: { 25 info: {
26 id: 'feature.quickSwitch.info', 26 id: 'feature.quickSwitch.info',
27 defaultMessage: '!!!Select a service with TAB, ↑ and ↓. Open a service with ENTER.', 27 defaultMessage:
28 'Select a service with TAB, ↑ and ↓. Open a service with ENTER.',
28 }, 29 },
29}); 30});
30 31
31const styles = (theme) => ({ 32const styles = theme => ({
32 modal: { 33 modal: {
33 width: '80%', 34 width: '80%',
34 maxWidth: 600, 35 maxWidth: 600,
@@ -80,20 +81,19 @@ const styles = (theme) => ({
80 }, 81 },
81}); 82});
82 83
83export default @injectSheet(styles) @inject('stores', 'actions') @observer class QuickSwitchModal extends Component { 84@injectSheet(styles)
85@inject('stores', 'actions')
86@observer
87class QuickSwitchModal extends Component {
84 static propTypes = { 88 static propTypes = {
85 classes: PropTypes.object.isRequired, 89 classes: PropTypes.object.isRequired,
86 }; 90 };
87 91
88 static contextTypes = {
89 intl: intlShape,
90 };
91
92 state = { 92 state = {
93 selected: 0, 93 selected: 0,
94 search: '', 94 search: '',
95 wasPrevVisible: false, 95 wasPrevVisible: false,
96 } 96 };
97 97
98 ARROW_DOWN = 40; 98 ARROW_DOWN = 40;
99 99
@@ -118,9 +118,7 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
118 this.openService = this.openService.bind(this); 118 this.openService = this.openService.bind(this);
119 119
120 reaction( 120 reaction(
121 () => ( 121 () => ModalState.isModalVisible,
122 ModalState.isModalVisible
123 ),
124 () => { 122 () => {
125 this._handleVisibilityChange(); 123 this._handleVisibilityChange();
126 }, 124 },
@@ -140,10 +138,17 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
140 // Get currently shown services 138 // Get currently shown services
141 services() { 139 services() {
142 let services = []; 140 let services = [];
143 if (this.state.search && compact(invoke(this.state.search, 'match', /^[a-z0-9]/i)).length > 0) { 141 if (
142 this.state.search &&
143 compact(invoke(this.state.search, 'match', /^[\da-z]/i)).length > 0
144 ) {
144 // Apply simple search algorythm to list of all services 145 // Apply simple search algorythm to list of all services
145 services = this.props.stores.services.allDisplayed; 146 services = this.props.stores.services.allDisplayed;
146 services = services.filter((service) => service.name.toLowerCase().search(this.state.search.toLowerCase()) !== -1); 147 services = services.filter(
148 service =>
149 service.name.toLowerCase().search(this.state.search.toLowerCase()) !==
150 -1,
151 );
147 } else { 152 } else {
148 // Add the currently active service first 153 // Add the currently active service first
149 const currentService = this.props.stores.services.active; 154 const currentService = this.props.stores.services.active;
@@ -186,14 +191,14 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
186 // Change the selected service 191 // Change the selected service
187 // factor should be -1 or 1 192 // factor should be -1 or 1
188 changeSelected(factor) { 193 changeSelected(factor) {
189 this.setState((state) => { 194 this.setState(state => {
190 let newSelected = state.selected + factor; 195 let newSelected = state.selected + factor;
191 const services = this.services().length; 196 const services = this.services().length;
192 197
193 // Roll around when on edge of list 198 // Roll around when on edge of list
194 if (state.selected < 1 && factor === -1) { 199 if (state.selected < 1 && factor === -1) {
195 newSelected = services - 1; 200 newSelected = services - 1;
196 } else if ((state.selected >= (services - 1)) && factor === 1) { 201 } else if (state.selected >= services - 1 && factor === 1) {
197 newSelected = 0; 202 newSelected = 0;
198 } 203 }
199 204
@@ -256,7 +261,7 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
256 // Wrapped inside timeout to let the modal render first 261 // Wrapped inside timeout to let the modal render first
257 setTimeout(() => { 262 setTimeout(() => {
258 if (this.inputRef.current) { 263 if (this.inputRef.current) {
259 this.inputRef.current.getElementsByTagName('input')[0].focus(); 264 this.inputRef.current.querySelectorAll('input')[0].focus();
260 } 265 }
261 }, 10); 266 }, 10);
262 267
@@ -268,7 +273,7 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
268 // search query change when modal not visible 273 // search query change when modal not visible
269 setTimeout(() => { 274 setTimeout(() => {
270 if (this.inputRef.current) { 275 if (this.inputRef.current) {
271 this.inputRef.current.getElementsByTagName('input')[0].blur(); 276 this.inputRef.current.querySelectorAll('input')[0].blur();
272 } 277 }
273 }, 100); 278 }, 100);
274 279
@@ -286,17 +291,13 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
286 render() { 291 render() {
287 const { isModalVisible } = ModalState; 292 const { isModalVisible } = ModalState;
288 293
289 const { 294 const { openService } = this;
290 openService,
291 } = this;
292 295
293 const { 296 const { classes } = this.props;
294 classes,
295 } = this.props;
296 297
297 const services = this.services(); 298 const services = this.services();
298 299
299 const { intl } = this.context; 300 const { intl } = this.props;
300 301
301 return ( 302 return (
302 <Modal 303 <Modal
@@ -318,12 +319,16 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
318 </div> 319 </div>
319 320
320 <div className={classes.services}> 321 <div className={classes.services}>
321 { services.map((service, index) => ( 322 {services.map((service, index) => (
322 <div 323 <div
323 className={`${classes.service} ${this.state.selected === index ? `${classes.activeService} active` : ''} service`} 324 className={`${classes.service} ${
325 this.state.selected === index
326 ? `${classes.activeService} active`
327 : ''
328 } service`}
324 onClick={() => openService(index)} 329 onClick={() => openService(index)}
325 key={service.id} 330 key={service.id}
326 ref={(el) => { 331 ref={el => {
327 this.serviceElements[index] = el; 332 this.serviceElements[index] = el;
328 }} 333 }}
329 > 334 >
@@ -332,9 +337,7 @@ export default @injectSheet(styles) @inject('stores', 'actions') @observer class
332 className={classes.serviceIcon} 337 className={classes.serviceIcon}
333 alt={service.recipe.name} 338 alt={service.recipe.name}
334 /> 339 />
335 <div> 340 <div>{service.name}</div>
336 { service.name }
337 </div>
338 </div> 341 </div>
339 ))} 342 ))}
340 </div> 343 </div>
@@ -356,3 +359,5 @@ QuickSwitchModal.wrappedComponent.propTypes = {
356 service: PropTypes.instanceOf(ServicesStore).isRequired, 359 service: PropTypes.instanceOf(ServicesStore).isRequired,
357 }).isRequired, 360 }).isRequired,
358}; 361};
362
363export default injectIntl(QuickSwitchModal);
diff --git a/src/features/quickSwitch/store.js b/src/features/quickSwitch/store.ts
index ed06e5a7d..ed06e5a7d 100644
--- a/src/features/quickSwitch/store.js
+++ b/src/features/quickSwitch/store.ts
diff --git a/src/features/serviceProxy/index.js b/src/features/serviceProxy/index.js
deleted file mode 100644
index eb7116651..000000000
--- a/src/features/serviceProxy/index.js
+++ /dev/null
@@ -1,38 +0,0 @@
1import { autorun, observable } from 'mobx';
2import { session } from '@electron/remote';
3
4const debug = require('debug')('Ferdi:feature:serviceProxy');
5
6export const config = observable({
7 isEnabled: true,
8});
9
10export default function init(stores) {
11 debug('Initializing `serviceProxy` feature');
12
13 autorun(() => {
14 config.isEnabled = true;
15
16 const services = stores.services.enabled;
17 const proxySettings = stores.settings.proxy;
18
19 debug('Service Proxy autorun');
20
21 services.forEach((service) => {
22 const s = session.fromPartition(`persist:service-${service.id}`);
23
24 if (config.isEnabled) {
25 const serviceProxyConfig = proxySettings[service.id];
26
27 if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) {
28 const proxyHost = `${serviceProxyConfig.host}${serviceProxyConfig.port ? `:${serviceProxyConfig.port}` : ''}`;
29 debug(`Setting proxy config from service settings for "${service.name}" (${service.id}) to`, proxyHost);
30
31 s.setProxy({ proxyRules: proxyHost }, () => {
32 debug(`Using proxy "${proxyHost}" for "${service.name}" (${service.id})`);
33 });
34 }
35 }
36 });
37 });
38}
diff --git a/src/features/serviceProxy/index.ts b/src/features/serviceProxy/index.ts
new file mode 100644
index 000000000..f095b286a
--- /dev/null
+++ b/src/features/serviceProxy/index.ts
@@ -0,0 +1,54 @@
1import { autorun, observable } from 'mobx';
2import { session } from '@electron/remote';
3
4const debug = require('debug')('Ferdi:feature:serviceProxy');
5
6export const config = observable({
7 isEnabled: true,
8});
9
10export default function init(stores: {
11 services: { enabled: any };
12 settings: { proxy: any };
13}) {
14 debug('Initializing `serviceProxy` feature');
15
16 autorun(() => {
17 config.isEnabled = true;
18
19 const services = stores.services.enabled;
20 const proxySettings = stores.settings.proxy;
21
22 debug('Service Proxy autorun');
23
24 for (const service of services) {
25 const s = session.fromPartition(`persist:service-${service.id}`);
26
27 if (config.isEnabled) {
28 const serviceProxyConfig = proxySettings[service.id];
29
30 if (
31 serviceProxyConfig &&
32 serviceProxyConfig.isEnabled &&
33 serviceProxyConfig.host
34 ) {
35 const proxyHost = `${serviceProxyConfig.host}${
36 serviceProxyConfig.port ? `:${serviceProxyConfig.port}` : ''
37 }`;
38 debug(
39 `Setting proxy config from service settings for "${service.name}" (${service.id}) to`,
40 proxyHost,
41 );
42
43 s.setProxy({ proxyRules: proxyHost })
44 .then(() => {
45 debug(
46 `Using proxy "${proxyHost}" for "${service.name}" (${service.id})`,
47 );
48 })
49 .catch(error => console.error(error));
50 }
51 }
52 }
53 });
54}
diff --git a/src/features/settingsWS/actions.js b/src/features/settingsWS/actions.ts
index 631670c8a..631670c8a 100755
--- a/src/features/settingsWS/actions.js
+++ b/src/features/settingsWS/actions.ts
diff --git a/src/features/settingsWS/index.js b/src/features/settingsWS/index.ts
index 7771421d6..9bb206d82 100755
--- a/src/features/settingsWS/index.js
+++ b/src/features/settingsWS/index.ts
@@ -5,15 +5,16 @@ const debug = require('debug')('Ferdi:feature:settingsWS');
5 5
6export const settingsStore = new SettingsWSStore(); 6export const settingsStore = new SettingsWSStore();
7 7
8export default function initSettingsWebSocket(stores, actions) { 8export default function initSettingsWebSocket(
9 stores: { features: any },
10 actions: any,
11) {
9 const { features } = stores; 12 const { features } = stores;
10 13
11 // Toggle SettingsWebSocket feature 14 // Toggle SettingsWebSocket feature
12 reaction( 15 reaction(
13 () => ( 16 () => features.features.isSettingsWSEnabled,
14 features.features.isSettingsWSEnabled 17 isEnabled => {
15 ),
16 (isEnabled) => {
17 if (isEnabled) { 18 if (isEnabled) {
18 debug('Initializing `settingsWS` feature'); 19 debug('Initializing `settingsWS` feature');
19 settingsStore.start(stores, actions); 20 settingsStore.start(stores, actions);
diff --git a/src/features/settingsWS/state.js b/src/features/settingsWS/state.ts
index 7b16b2b6e..7b16b2b6e 100755
--- a/src/features/settingsWS/state.js
+++ b/src/features/settingsWS/state.ts
diff --git a/src/features/settingsWS/store.js b/src/features/settingsWS/store.js
index 9100f33d1..3b9e10825 100755
--- a/src/features/settingsWS/store.js
+++ b/src/features/settingsWS/store.js
@@ -4,7 +4,7 @@ import ms from 'ms';
4 4
5import { FeatureStore } from '../utils/FeatureStore'; 5import { FeatureStore } from '../utils/FeatureStore';
6import { createReactions } from '../../stores/lib/Reaction'; 6import { createReactions } from '../../stores/lib/Reaction';
7import { WS_API } from '../../environment'; 7import { WS_API } from '../../environment-remote';
8 8
9const debug = require('debug')('Ferdi:feature:settingsWS:store'); 9const debug = require('debug')('Ferdi:feature:settingsWS:store');
10 10
@@ -25,11 +25,13 @@ export class SettingsWSStore extends FeatureStore {
25 this.stores = stores; 25 this.stores = stores;
26 this.actions = actions; 26 this.actions = actions;
27 27
28 this._registerReactions(createReactions([ 28 this._registerReactions(
29 this._initialize.bind(this), 29 createReactions([
30 this._reconnect.bind(this), 30 this._initialize.bind(this),
31 this._close.bind(this), 31 this._reconnect.bind(this),
32 ])); 32 this._close.bind(this),
33 ]),
34 );
33 } 35 }
34 36
35 connect() { 37 connect() {
@@ -51,12 +53,12 @@ export class SettingsWSStore extends FeatureStore {
51 this.heartbeat(); 53 this.heartbeat();
52 }); 54 });
53 55
54 this.ws.on('message', (data) => { 56 this.ws.on('message', data => {
55 const resp = JSON.parse(data); 57 const resp = JSON.parse(data);
56 debug('Received message', resp); 58 debug('Received message', resp);
57 59
58 if (resp.id) { 60 if (resp.id) {
59 this.stores.user.getUserInfoRequest.patch((result) => { 61 this.stores.user.getUserInfoRequest.patch(result => {
60 if (!result) return; 62 if (!result) return;
61 63
62 debug('Patching user object with new values'); 64 debug('Patching user object with new values');
@@ -66,8 +68,8 @@ export class SettingsWSStore extends FeatureStore {
66 }); 68 });
67 69
68 this.ws.on('ping', this.heartbeat.bind(this)); 70 this.ws.on('ping', this.heartbeat.bind(this));
69 } catch (err) { 71 } catch (error) {
70 console.err(err); 72 console.error(error);
71 } 73 }
72 } 74 }
73 75
diff --git a/src/features/todos/actions.js b/src/features/todos/actions.js
deleted file mode 100644
index cc17e919b..000000000
--- a/src/features/todos/actions.js
+++ /dev/null
@@ -1,28 +0,0 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../actions/lib/actions';
3
4export const todoActions = createActionsFromDefinitions({
5 resize: {
6 width: PropTypes.number.isRequired,
7 },
8 toggleTodosPanel: {},
9 toggleTodosFeatureVisibility: {},
10 setTodosWebview: {
11 webview: PropTypes.instanceOf(Element).isRequired,
12 },
13 handleHostMessage: {
14 action: PropTypes.string.isRequired,
15 data: PropTypes.object,
16 },
17 handleClientMessage: {
18 channel: PropTypes.string.isRequired,
19 message: PropTypes.shape({
20 action: PropTypes.string.isRequired,
21 data: PropTypes.object,
22 }),
23 },
24 openDevTools: {},
25 reload: {},
26}, PropTypes.checkPropTypes);
27
28export default todoActions;
diff --git a/src/features/todos/actions.ts b/src/features/todos/actions.ts
new file mode 100644
index 000000000..04e299e71
--- /dev/null
+++ b/src/features/todos/actions.ts
@@ -0,0 +1,31 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../actions/lib/actions';
3
4export const todoActions = createActionsFromDefinitions(
5 {
6 resize: {
7 width: PropTypes.number.isRequired,
8 },
9 toggleTodosPanel: {},
10 toggleTodosFeatureVisibility: {},
11 setTodosWebview: {
12 webview: PropTypes.instanceOf(Element).isRequired,
13 },
14 handleHostMessage: {
15 action: PropTypes.string.isRequired,
16 data: PropTypes.object,
17 },
18 handleClientMessage: {
19 channel: PropTypes.string.isRequired,
20 message: PropTypes.shape({
21 action: PropTypes.string.isRequired,
22 data: PropTypes.object,
23 }),
24 },
25 openDevTools: {},
26 reload: {},
27 },
28 PropTypes.checkPropTypes,
29);
30
31export default todoActions;
diff --git a/src/features/todos/constants.js b/src/features/todos/constants.ts
index 303a7a16e..303a7a16e 100644
--- a/src/features/todos/constants.js
+++ b/src/features/todos/constants.ts
diff --git a/src/features/todos/index.js b/src/features/todos/index.ts
index 573190881..3665812e6 100644
--- a/src/features/todos/index.js
+++ b/src/features/todos/index.ts
@@ -5,16 +5,17 @@ const debug = require('debug')('Ferdi:feature:todos');
5 5
6export const todosStore = new TodoStore(); 6export const todosStore = new TodoStore();
7 7
8export default function initTodos(stores, actions) { 8export default function initTodos(
9 stores: { todos?: any; features?: any },
10 actions: any,
11) {
9 stores.todos = todosStore; 12 stores.todos = todosStore;
10 const { features } = stores; 13 const { features } = stores;
11 14
12 // Toggle todos feature 15 // Toggle todos feature
13 reaction( 16 reaction(
14 () => ( 17 () => features.features.isTodosEnabled,
15 features.features.isTodosEnabled 18 isEnabled => {
16 ),
17 (isEnabled) => {
18 if (isEnabled) { 19 if (isEnabled) {
19 debug('Initializing `todos` feature'); 20 debug('Initializing `todos` feature');
20 todosStore.start(stores, actions); 21 todosStore.start(stores, actions);
diff --git a/src/features/todos/preload.js b/src/features/todos/preload.js
index 9bd76a704..3b86ddbc5 100644
--- a/src/features/todos/preload.js
+++ b/src/features/todos/preload.js
@@ -7,7 +7,9 @@ debug('Preloading Todos Webview');
7 7
8let hostMessageListener = ({ action }) => { 8let hostMessageListener = ({ action }) => {
9 switch (action) { 9 switch (action) {
10 case 'todos:initialize-as-service': ipcRenderer.sendToHost('hello'); break; 10 case 'todos:initialize-as-service':
11 ipcRenderer.sendToHost('hello');
12 break;
11 default: 13 default:
12 } 14 }
13}; 15};
@@ -15,7 +17,9 @@ let hostMessageListener = ({ action }) => {
15window.ferdi = { 17window.ferdi = {
16 onInitialize(ipcHostMessageListener) { 18 onInitialize(ipcHostMessageListener) {
17 hostMessageListener = ipcHostMessageListener; 19 hostMessageListener = ipcHostMessageListener;
18 ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, { action: 'todos:initialized' }); 20 ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, {
21 action: 'todos:initialized',
22 });
19 }, 23 },
20 sendToHost(message) { 24 sendToHost(message) {
21 ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, message); 25 ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, message);
@@ -30,7 +34,7 @@ ipcRenderer.on(IPC.TODOS_HOST_CHANNEL, (event, message) => {
30if (window.location.href === 'https://app.franztodos.com/login/') { 34if (window.location.href === 'https://app.franztodos.com/login/') {
31 // Insert info element informing about Franz accounts 35 // Insert info element informing about Franz accounts
32 const infoElement = document.createElement('p'); 36 const infoElement = document.createElement('p');
33 infoElement.innerText = `You are using Franz's official Todo Service. 37 infoElement.textContent = `You are using Franz's official Todo Service.
34This service will only work with accounts registered with Franz - no Ferdi accounts will work here! 38This service will only work with accounts registered with Franz - no Ferdi accounts will work here!
35If you do not have a Franz account you can change the Todo service by going into Ferdi's settings and changing the "Todo server". 39If you do not have a Franz account you can change the Todo service by going into Ferdi's settings and changing the "Todo server".
36You can choose any service as this Todo server, e.g. Todoist or Apple Notes.`; 40You can choose any service as this Todo server, e.g. Todoist or Apple Notes.`;
@@ -42,7 +46,7 @@ You can choose any service as this Todo server, e.g. Todoist or Apple Notes.`;
42 const textElement = document.querySelector('p'); 46 const textElement = document.querySelector('p');
43 if (textElement) { 47 if (textElement) {
44 clearInterval(waitForReact); 48 clearInterval(waitForReact);
45 textElement.parentElement.insertBefore(infoElement, textElement); 49 textElement.parentElement?.insertBefore(infoElement, textElement);
46 } else { 50 } else {
47 numChecks += 1; 51 numChecks += 1;
48 52
diff --git a/src/features/utils/ActionBinding.js b/src/features/utils/ActionBinding.ts
index 787166d44..787166d44 100644
--- a/src/features/utils/ActionBinding.js
+++ b/src/features/utils/ActionBinding.ts
diff --git a/src/features/utils/FeatureStore.js b/src/features/utils/FeatureStore.js
index 4d4e217a9..afe726294 100644
--- a/src/features/utils/FeatureStore.js
+++ b/src/features/utils/FeatureStore.js
@@ -16,11 +16,11 @@ export class FeatureStore {
16 } 16 }
17 17
18 _startActions(actions = this._actions) { 18 _startActions(actions = this._actions) {
19 actions.forEach((a) => a.start()); 19 for (const a of actions) a.start();
20 } 20 }
21 21
22 _stopActions(actions = this._actions) { 22 _stopActions(actions = this._actions) {
23 actions.forEach((a) => a.stop()); 23 for (const a of actions) a.stop();
24 } 24 }
25 25
26 // REACTIONS 26 // REACTIONS
@@ -31,10 +31,10 @@ export class FeatureStore {
31 } 31 }
32 32
33 _startReactions(reactions = this._reactions) { 33 _startReactions(reactions = this._reactions) {
34 reactions.forEach((r) => r.start()); 34 for (const r of reactions) r.start();
35 } 35 }
36 36
37 _stopReactions(reactions = this._reactions) { 37 _stopReactions(reactions = this._reactions) {
38 reactions.forEach((r) => r.stop()); 38 for (const r of reactions) r.stop();
39 } 39 }
40} 40}
diff --git a/src/features/webControls/components/WebControls.js b/src/features/webControls/components/WebControls.js
index bebf52c08..97fa20dcc 100644
--- a/src/features/webControls/components/WebControls.js
+++ b/src/features/webControls/components/WebControls.js
@@ -3,7 +3,7 @@ import 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 { Icon } from '@meetfranz/ui'; 5import { Icon } from '@meetfranz/ui';
6import { defineMessages, intlShape } from 'react-intl'; 6import { defineMessages, injectIntl } from 'react-intl';
7 7
8import { 8import {
9 mdiReload, 9 mdiReload,
@@ -16,23 +16,23 @@ import {
16const messages = defineMessages({ 16const messages = defineMessages({
17 goHome: { 17 goHome: {
18 id: 'webControls.goHome', 18 id: 'webControls.goHome',
19 defaultMessage: '!!!Home', 19 defaultMessage: 'Home',
20 }, 20 },
21 openInBrowser: { 21 openInBrowser: {
22 id: 'webControls.openInBrowser', 22 id: 'webControls.openInBrowser',
23 defaultMessage: '!!!Open in Browser', 23 defaultMessage: 'Open in Browser',
24 }, 24 },
25 back: { 25 back: {
26 id: 'webControls.back', 26 id: 'webControls.back',
27 defaultMessage: '!!!Back', 27 defaultMessage: 'Back',
28 }, 28 },
29 forward: { 29 forward: {
30 id: 'webControls.forward', 30 id: 'webControls.forward',
31 defaultMessage: '!!!Forward', 31 defaultMessage: 'Forward',
32 }, 32 },
33 reload: { 33 reload: {
34 id: 'webControls.reload', 34 id: 'webControls.reload',
35 defaultMessage: '!!!Reload', 35 defaultMessage: 'Reload',
36 }, 36 },
37}); 37});
38 38
@@ -109,10 +109,6 @@ class WebControls extends Component {
109 navigate: PropTypes.func.isRequired, 109 navigate: PropTypes.func.isRequired,
110 }; 110 };
111 111
112 static contextTypes = {
113 intl: intlShape,
114 };
115
116 static getDerivedStateFromProps(props, state) { 112 static getDerivedStateFromProps(props, state) {
117 const { url } = props; 113 const { url } = props;
118 const { editUrl } = state; 114 const { editUrl } = state;
@@ -148,7 +144,7 @@ class WebControls extends Component {
148 144
149 const { inputUrl, editUrl } = this.state; 145 const { inputUrl, editUrl } = this.state;
150 146
151 const { intl } = this.context; 147 const { intl } = this.props;
152 148
153 return ( 149 return (
154 <div className={classes.root}> 150 <div className={classes.root}>
@@ -241,4 +237,4 @@ class WebControls extends Component {
241 } 237 }
242} 238}
243 239
244export default WebControls; 240export default injectIntl(WebControls);
diff --git a/src/features/webControls/containers/WebControlsScreen.js b/src/features/webControls/containers/WebControlsScreen.js
index e1e1b9991..0273bb13e 100644
--- a/src/features/webControls/containers/WebControlsScreen.js
+++ b/src/features/webControls/containers/WebControlsScreen.js
@@ -16,7 +16,8 @@ const URL_EVENTS = [
16 'did-navigate-in-page', 16 'did-navigate-in-page',
17]; 17];
18 18
19@inject('stores', 'actions') @observer 19@inject('stores', 'actions')
20@observer
20class WebControlsScreen extends Component { 21class WebControlsScreen extends Component {
21 @observable url = ''; 22 @observable url = '';
22 23
@@ -36,15 +37,15 @@ class WebControlsScreen extends Component {
36 this.webview = service.webview; 37 this.webview = service.webview;
37 this.url = this.webview.getURL(); 38 this.url = this.webview.getURL();
38 39
39 URL_EVENTS.forEach((event) => { 40 for (const event of URL_EVENTS) {
40 this.webview.addEventListener(event, (e) => { 41 this.webview.addEventListener(event, e => {
41 if (!e.isMainFrame) return; 42 if (!e.isMainFrame) return;
42 43
43 this.url = e.url; 44 this.url = e.url;
44 this.canGoBack = this.webview.canGoBack(); 45 this.canGoBack = this.webview.canGoBack();
45 this.canGoForward = this.webview.canGoForward(); 46 this.canGoForward = this.webview.canGoForward();
46 }); 47 });
47 }); 48 }
48 } 49 }
49 }); 50 });
50 } 51 }
@@ -83,13 +84,16 @@ class WebControlsScreen extends Component {
83 84
84 try { 85 try {
85 url = new URL(url).toString(); 86 url = new URL(url).toString();
86 } catch (err) { 87 } catch {
87 // eslint-disable-next-line no-useless-escape 88 url =
88 if (url.match(/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/)) { 89 // eslint-disable-next-line no-useless-escape
89 url = `http://${url}`; 90 /^((?!-))(xn--)?[\da-z][\d_a-z-]{0,61}[\da-z]{0,1}\.(xn--)?([\da-z\-]{1,61}|[\da-z-]{1,30}\.[a-z]{2,})$/.test(
90 } else { 91 url,
91 url = SEARCH_ENGINE_URLS[this.settings.app.searchEngine]({ searchTerm: url }); 92 )
92 } 93 ? `http://${url}`
94 : SEARCH_ENGINE_URLS[this.settings.app.searchEngine]({
95 searchTerm: url,
96 });
93 } 97 }
94 98
95 this.webview.loadURL(url); 99 this.webview.loadURL(url);
@@ -114,7 +118,7 @@ class WebControlsScreen extends Component {
114 goBack={() => this.goBack()} 118 goBack={() => this.goBack()}
115 canGoForward={this.canGoForward} 119 canGoForward={this.canGoForward}
116 goForward={() => this.goForward()} 120 goForward={() => this.goForward()}
117 navigate={(url) => this.navigate(url)} 121 navigate={url => this.navigate(url)}
118 url={this.url} 122 url={this.url}
119 /> 123 />
120 ); 124 );
diff --git a/src/features/workspaces/actions.js b/src/features/workspaces/actions.js
deleted file mode 100644
index 5b5db422e..000000000
--- a/src/features/workspaces/actions.js
+++ /dev/null
@@ -1,27 +0,0 @@
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 toggleKeepAllWorkspacesLoadedSetting: {},
25}, PropTypes.checkPropTypes);
26
27export default workspaceActions;
diff --git a/src/features/workspaces/actions.ts b/src/features/workspaces/actions.ts
new file mode 100644
index 000000000..5e7e6e721
--- /dev/null
+++ b/src/features/workspaces/actions.ts
@@ -0,0 +1,30 @@
1import PropTypes from 'prop-types';
2import Workspace from './models/Workspace';
3import { createActionsFromDefinitions } from '../../actions/lib/actions';
4
5export const workspaceActions = createActionsFromDefinitions(
6 {
7 edit: {
8 workspace: PropTypes.instanceOf(Workspace).isRequired,
9 },
10 create: {
11 name: PropTypes.string.isRequired,
12 },
13 delete: {
14 workspace: PropTypes.instanceOf(Workspace).isRequired,
15 },
16 update: {
17 workspace: PropTypes.instanceOf(Workspace).isRequired,
18 },
19 activate: {
20 workspace: PropTypes.instanceOf(Workspace).isRequired,
21 },
22 deactivate: {},
23 toggleWorkspaceDrawer: {},
24 openWorkspaceSettings: {},
25 toggleKeepAllWorkspacesLoadedSetting: {},
26 },
27 PropTypes.checkPropTypes,
28);
29
30export default workspaceActions;
diff --git a/src/features/workspaces/api.js b/src/features/workspaces/api.ts
index 322695ed2..8447fc247 100644
--- a/src/features/workspaces/api.js
+++ b/src/features/workspaces/api.ts
@@ -12,12 +12,14 @@ export const workspaceApi = {
12 debug('getUserWorkspaces GET', url); 12 debug('getUserWorkspaces GET', url);
13 const result = await sendAuthRequest(url, { method: 'GET' }); 13 const result = await sendAuthRequest(url, { method: 'GET' });
14 debug('getUserWorkspaces RESULT', result); 14 debug('getUserWorkspaces RESULT', result);
15 if (!result.ok) throw result; 15 if (!result.ok) {
16 throw new Error("Couldn't getUserWorkspaces");
17 }
16 const workspaces = await result.json(); 18 const workspaces = await result.json();
17 return workspaces.map((data) => new Workspace(data)); 19 return workspaces.map(data => new Workspace(data));
18 }, 20 },
19 21
20 createWorkspace: async (name) => { 22 createWorkspace: async name => {
21 const url = `${apiBase()}/workspace`; 23 const url = `${apiBase()}/workspace`;
22 const options = { 24 const options = {
23 method: 'POST', 25 method: 'POST',
@@ -26,20 +28,24 @@ export const workspaceApi = {
26 debug('createWorkspace POST', url, options); 28 debug('createWorkspace POST', url, options);
27 const result = await sendAuthRequest(url, options); 29 const result = await sendAuthRequest(url, options);
28 debug('createWorkspace RESULT', result); 30 debug('createWorkspace RESULT', result);
29 if (!result.ok) throw result; 31 if (!result.ok) {
32 throw new Error("Couldn't createWorkspace");
33 }
30 return new Workspace(await result.json()); 34 return new Workspace(await result.json());
31 }, 35 },
32 36
33 deleteWorkspace: async (workspace) => { 37 deleteWorkspace: async workspace => {
34 const url = `${apiBase()}/workspace/${workspace.id}`; 38 const url = `${apiBase()}/workspace/${workspace.id}`;
35 debug('deleteWorkspace DELETE', url); 39 debug('deleteWorkspace DELETE', url);
36 const result = await sendAuthRequest(url, { method: 'DELETE' }); 40 const result = await sendAuthRequest(url, { method: 'DELETE' });
37 debug('deleteWorkspace RESULT', result); 41 debug('deleteWorkspace RESULT', result);
38 if (!result.ok) throw result; 42 if (!result.ok) {
43 throw new Error("Couldn't deleteWorkspace");
44 }
39 return true; 45 return true;
40 }, 46 },
41 47
42 updateWorkspace: async (workspace) => { 48 updateWorkspace: async workspace => {
43 const url = `${apiBase()}/workspace/${workspace.id}`; 49 const url = `${apiBase()}/workspace/${workspace.id}`;
44 const options = { 50 const options = {
45 method: 'PUT', 51 method: 'PUT',
@@ -48,15 +54,29 @@ export const workspaceApi = {
48 debug('updateWorkspace UPDATE', url, options); 54 debug('updateWorkspace UPDATE', url, options);
49 const result = await sendAuthRequest(url, options); 55 const result = await sendAuthRequest(url, options);
50 debug('updateWorkspace RESULT', result); 56 debug('updateWorkspace RESULT', result);
51 if (!result.ok) throw result; 57 if (!result.ok) {
58 throw new Error("Couldn't updateWorkspace");
59 }
52 return new Workspace(await result.json()); 60 return new Workspace(await result.json());
53 }, 61 },
54}; 62};
55 63
56export const getUserWorkspacesRequest = new Request(workspaceApi, 'getUserWorkspaces'); 64export const getUserWorkspacesRequest = new Request(
57export const createWorkspaceRequest = new Request(workspaceApi, 'createWorkspace'); 65 workspaceApi,
58export const deleteWorkspaceRequest = new Request(workspaceApi, 'deleteWorkspace'); 66 'getUserWorkspaces',
59export const updateWorkspaceRequest = new Request(workspaceApi, 'updateWorkspace'); 67);
68export const createWorkspaceRequest = new Request(
69 workspaceApi,
70 'createWorkspace',
71);
72export const deleteWorkspaceRequest = new Request(
73 workspaceApi,
74 'deleteWorkspace',
75);
76export const updateWorkspaceRequest = new Request(
77 workspaceApi,
78 'updateWorkspace',
79);
60 80
61export const resetApiRequests = () => { 81export const resetApiRequests = () => {
62 getUserWorkspacesRequest.reset(); 82 getUserWorkspacesRequest.reset();
diff --git a/src/features/workspaces/components/CreateWorkspaceForm.js b/src/features/workspaces/components/CreateWorkspaceForm.js
index 15b97121d..c9b05b87f 100644
--- a/src/features/workspaces/components/CreateWorkspaceForm.js
+++ b/src/features/workspaces/components/CreateWorkspaceForm.js
@@ -1,7 +1,7 @@
1import React, { Component } from 'react'; 1import 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 { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5import { Input, Button } from '@meetfranz/forms'; 5import { Input, Button } from '@meetfranz/forms';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7import Form from '../../../lib/Form'; 7import Form from '../../../lib/Form';
@@ -11,11 +11,11 @@ import { workspaceStore } from '../index';
11const messages = defineMessages({ 11const messages = defineMessages({
12 submitButton: { 12 submitButton: {
13 id: 'settings.workspace.add.form.submitButton', 13 id: 'settings.workspace.add.form.submitButton',
14 defaultMessage: '!!!Create workspace', 14 defaultMessage: 'Create workspace',
15 }, 15 },
16 name: { 16 name: {
17 id: 'settings.workspace.add.form.name', 17 id: 'settings.workspace.add.form.name',
18 defaultMessage: '!!!Name', 18 defaultMessage: 'Name',
19 }, 19 },
20}); 20});
21 21
@@ -32,12 +32,9 @@ const styles = () => ({
32 }, 32 },
33}); 33});
34 34
35@injectSheet(styles) @observer 35@injectSheet(styles)
36@observer
36class CreateWorkspaceForm extends Component { 37class CreateWorkspaceForm extends Component {
37 static contextTypes = {
38 intl: intlShape,
39 };
40
41 static propTypes = { 38 static propTypes = {
42 classes: PropTypes.object.isRequired, 39 classes: PropTypes.object.isRequired,
43 isSubmitting: PropTypes.bool.isRequired, 40 isSubmitting: PropTypes.bool.isRequired,
@@ -45,7 +42,7 @@ class CreateWorkspaceForm extends Component {
45 }; 42 };
46 43
47 form = (() => { 44 form = (() => {
48 const { intl } = this.context; 45 const { intl } = this.props;
49 return new Form({ 46 return new Form({
50 fields: { 47 fields: {
51 name: { 48 name: {
@@ -61,7 +58,7 @@ class CreateWorkspaceForm extends Component {
61 submitForm() { 58 submitForm() {
62 const { form } = this; 59 const { form } = this;
63 form.submit({ 60 form.submit({
64 onSuccess: async (f) => { 61 onSuccess: async f => {
65 const { onSubmit } = this.props; 62 const { onSubmit } = this.props;
66 const values = f.values(); 63 const values = f.values();
67 onSubmit(values); 64 onSubmit(values);
@@ -70,7 +67,7 @@ class CreateWorkspaceForm extends Component {
70 } 67 }
71 68
72 render() { 69 render() {
73 const { intl } = this.context; 70 const { intl } = this.props;
74 const { classes, isSubmitting } = this.props; 71 const { classes, isSubmitting } = this.props;
75 const { form } = this; 72 const { form } = this;
76 return ( 73 return (
@@ -95,4 +92,4 @@ class CreateWorkspaceForm extends Component {
95 } 92 }
96} 93}
97 94
98export default CreateWorkspaceForm; 95export default injectIntl(CreateWorkspaceForm);
diff --git a/src/features/workspaces/components/EditWorkspaceForm.js b/src/features/workspaces/components/EditWorkspaceForm.js
index c97d4bd9c..f562733dd 100644
--- a/src/features/workspaces/components/EditWorkspaceForm.js
+++ b/src/features/workspaces/components/EditWorkspaceForm.js
@@ -1,7 +1,7 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5import { Link } from 'react-router'; 5import { Link } from 'react-router';
6import { Input, Button } from '@meetfranz/forms'; 6import { Input, Button } from '@meetfranz/forms';
7import injectSheet from 'react-jss'; 7import injectSheet from 'react-jss';
@@ -20,40 +20,40 @@ import Toggle from '../../../components/ui/Toggle';
20const messages = defineMessages({ 20const messages = defineMessages({
21 buttonDelete: { 21 buttonDelete: {
22 id: 'settings.workspace.form.buttonDelete', 22 id: 'settings.workspace.form.buttonDelete',
23 defaultMessage: '!!!Delete workspace', 23 defaultMessage: 'Delete workspace',
24 }, 24 },
25 buttonSave: { 25 buttonSave: {
26 id: 'settings.workspace.form.buttonSave', 26 id: 'settings.workspace.form.buttonSave',
27 defaultMessage: '!!!Save workspace', 27 defaultMessage: 'Save workspace',
28 }, 28 },
29 name: { 29 name: {
30 id: 'settings.workspace.form.name', 30 id: 'settings.workspace.form.name',
31 defaultMessage: '!!!Name', 31 defaultMessage: 'Name',
32 }, 32 },
33 yourWorkspaces: { 33 yourWorkspaces: {
34 id: 'settings.workspace.form.yourWorkspaces', 34 id: 'settings.workspace.form.yourWorkspaces',
35 defaultMessage: '!!!Your workspaces', 35 defaultMessage: 'Your workspaces',
36 }, 36 },
37 keepLoaded: { 37 keepLoaded: {
38 id: 'settings.workspace.form.keepLoaded', 38 id: 'settings.workspace.form.keepLoaded',
39 defaultMessage: '!!!Keep this workspace loaded*', 39 defaultMessage: 'Keep this workspace loaded*',
40 }, 40 },
41 keepLoadedInfo: { 41 keepLoadedInfo: {
42 id: 'settings.workspace.form.keepLoadedInfo', 42 id: 'settings.workspace.form.keepLoadedInfo',
43 defaultMessage: 43 defaultMessage:
44 '!!!*This option will be overwritten by the global "Keep all workspaces loaded" option.', 44 '*This option will be overwritten by the global "Keep all workspaces loaded" option.',
45 }, 45 },
46 servicesInWorkspaceHeadline: { 46 servicesInWorkspaceHeadline: {
47 id: 'settings.workspace.form.servicesInWorkspaceHeadline', 47 id: 'settings.workspace.form.servicesInWorkspaceHeadline',
48 defaultMessage: '!!!Services in this Workspace', 48 defaultMessage: 'Services in this Workspace',
49 }, 49 },
50 noServicesAdded: { 50 noServicesAdded: {
51 id: 'settings.services.noServicesAdded', 51 id: 'settings.services.noServicesAdded',
52 defaultMessage: '!!!Start by adding a service.', 52 defaultMessage: 'Start by adding a service.',
53 }, 53 },
54 discoverServices: { 54 discoverServices: {
55 id: 'settings.services.discoverServices', 55 id: 'settings.services.discoverServices',
56 defaultMessage: '!!!Discover services', 56 defaultMessage: 'Discover services',
57 }, 57 },
58}); 58});
59 59
@@ -72,10 +72,6 @@ const styles = () => ({
72@injectSheet(styles) 72@injectSheet(styles)
73@observer 73@observer
74class EditWorkspaceForm extends Component { 74class EditWorkspaceForm extends Component {
75 static contextTypes = {
76 intl: intlShape,
77 };
78
79 static propTypes = { 75 static propTypes = {
80 classes: PropTypes.object.isRequired, 76 classes: PropTypes.object.isRequired,
81 onDelete: PropTypes.func.isRequired, 77 onDelete: PropTypes.func.isRequired,
@@ -97,7 +93,7 @@ class EditWorkspaceForm extends Component {
97 } 93 }
98 94
99 prepareWorkspaceForm(workspace) { 95 prepareWorkspaceForm(workspace) {
100 const { intl } = this.context; 96 const { intl } = this.props;
101 return new Form({ 97 return new Form({
102 fields: { 98 fields: {
103 name: { 99 name: {
@@ -112,7 +108,7 @@ class EditWorkspaceForm extends Component {
112 default: false, 108 default: false,
113 }, 109 },
114 services: { 110 services: {
115 value: workspace.services.slice(), 111 value: [...workspace.services],
116 }, 112 },
117 }, 113 },
118 }); 114 });
@@ -120,7 +116,7 @@ class EditWorkspaceForm extends Component {
120 116
121 save(form) { 117 save(form) {
122 form.submit({ 118 form.submit({
123 onSuccess: async (f) => { 119 onSuccess: async f => {
124 const { onSave } = this.props; 120 const { onSave } = this.props;
125 const values = f.values(); 121 const values = f.values();
126 onSave(values); 122 onSave(values);
@@ -146,7 +142,7 @@ class EditWorkspaceForm extends Component {
146 } 142 }
147 143
148 render() { 144 render() {
149 const { intl } = this.context; 145 const { intl } = this.props;
150 const { 146 const {
151 classes, 147 classes,
152 workspace, 148 workspace,
@@ -194,7 +190,7 @@ class EditWorkspaceForm extends Component {
194 </div> 190 </div>
195 ) : ( 191 ) : (
196 <> 192 <>
197 {services.map((s) => ( 193 {services.map(s => (
198 <WorkspaceServiceListItem 194 <WorkspaceServiceListItem
199 key={s.id} 195 key={s.id}
200 service={s} 196 service={s}
@@ -233,4 +229,4 @@ class EditWorkspaceForm extends Component {
233 } 229 }
234} 230}
235 231
236export default EditWorkspaceForm; 232export default injectIntl(EditWorkspaceForm);
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js
index 1138f23d7..3dac77bc2 100644
--- a/src/features/workspaces/components/WorkspaceDrawer.js
+++ b/src/features/workspaces/components/WorkspaceDrawer.js
@@ -2,11 +2,11 @@ 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'; 5import { defineMessages, injectIntl } from 'react-intl';
6import { H1, Icon } from '@meetfranz/ui'; 6import { H1, Icon } from '@meetfranz/ui';
7import ReactTooltip from 'react-tooltip'; 7import ReactTooltip from 'react-tooltip';
8 8
9import { mdiPlusBox, mdiSettings } from '@mdi/js'; 9import { mdiPlusBox, mdiCog } from '@mdi/js';
10import WorkspaceDrawerItem from './WorkspaceDrawerItem'; 10import WorkspaceDrawerItem from './WorkspaceDrawerItem';
11import { workspaceActions } from '../actions'; 11import { workspaceActions } from '../actions';
12import { workspaceStore } from '../index'; 12import { workspaceStore } from '../index';
@@ -14,27 +14,28 @@ import { workspaceStore } from '../index';
14const messages = defineMessages({ 14const messages = defineMessages({
15 headline: { 15 headline: {
16 id: 'workspaceDrawer.headline', 16 id: 'workspaceDrawer.headline',
17 defaultMessage: '!!!Workspaces', 17 defaultMessage: 'Workspaces',
18 }, 18 },
19 allServices: { 19 allServices: {
20 id: 'workspaceDrawer.allServices', 20 id: 'workspaceDrawer.allServices',
21 defaultMessage: '!!!All services', 21 defaultMessage: 'All services',
22 }, 22 },
23 workspacesSettingsTooltip: { 23 workspacesSettingsTooltip: {
24 id: 'workspaceDrawer.workspacesSettingsTooltip', 24 id: 'workspaceDrawer.workspacesSettingsTooltip',
25 defaultMessage: '!!!Workspaces settings', 25 defaultMessage: 'Edit workspaces settings',
26 }, 26 },
27 workspaceFeatureInfo: { 27 workspaceFeatureInfo: {
28 id: 'workspaceDrawer.workspaceFeatureInfo', 28 id: 'workspaceDrawer.workspaceFeatureInfo',
29 defaultMessage: '!!!Info about workspace feature', 29 defaultMessage:
30 '<p>Ferdi 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>',
30 }, 31 },
31 addNewWorkspaceLabel: { 32 addNewWorkspaceLabel: {
32 id: 'workspaceDrawer.addNewWorkspaceLabel', 33 id: 'workspaceDrawer.addNewWorkspaceLabel',
33 defaultMessage: '!!!add new workspace', 34 defaultMessage: 'Add new workspace',
34 }, 35 },
35}); 36});
36 37
37const styles = (theme) => ({ 38const styles = theme => ({
38 drawer: { 39 drawer: {
39 background: theme.workspaces.drawer.background, 40 background: theme.workspaces.drawer.background,
40 width: `${theme.workspaces.drawer.width}px`, 41 width: `${theme.workspaces.drawer.width}px`,
@@ -85,34 +86,26 @@ const styles = (theme) => ({
85 }, 86 },
86}); 87});
87 88
88@injectSheet(styles) @observer 89@injectSheet(styles)
90@observer
89class WorkspaceDrawer extends Component { 91class WorkspaceDrawer extends Component {
90 static propTypes = { 92 static propTypes = {
91 classes: PropTypes.object.isRequired, 93 classes: PropTypes.object.isRequired,
92 getServicesForWorkspace: PropTypes.func.isRequired, 94 getServicesForWorkspace: PropTypes.func.isRequired,
93 }; 95 };
94 96
95 static contextTypes = {
96 intl: intlShape,
97 };
98
99 componentDidMount() { 97 componentDidMount() {
100 ReactTooltip.rebuild(); 98 ReactTooltip.rebuild();
101 } 99 }
102 100
103 render() { 101 render() {
104 const { 102 const { classes, getServicesForWorkspace } = this.props;
105 classes, 103 const { intl } = this.props;
106 getServicesForWorkspace, 104 const { activeWorkspace, isSwitchingWorkspace, nextWorkspace, workspaces } =
107 } = this.props; 105 workspaceStore;
108 const { intl } = this.context; 106 const actualWorkspace = isSwitchingWorkspace
109 const { 107 ? nextWorkspace
110 activeWorkspace, 108 : activeWorkspace;
111 isSwitchingWorkspace,
112 nextWorkspace,
113 workspaces,
114 } = workspaceStore;
115 const actualWorkspace = isSwitchingWorkspace ? nextWorkspace : activeWorkspace;
116 return ( 109 return (
117 <div className={`${classes.drawer} workspaces-drawer`}> 110 <div className={`${classes.drawer} workspaces-drawer`}>
118 <H1 className={classes.headline}> 111 <H1 className={classes.headline}>
@@ -122,10 +115,12 @@ class WorkspaceDrawer extends Component {
122 onClick={() => { 115 onClick={() => {
123 workspaceActions.openWorkspaceSettings(); 116 workspaceActions.openWorkspaceSettings();
124 }} 117 }}
125 data-tip={`${intl.formatMessage(messages.workspacesSettingsTooltip)}`} 118 data-tip={`${intl.formatMessage(
119 messages.workspacesSettingsTooltip,
120 )}`}
126 > 121 >
127 <Icon 122 <Icon
128 icon={mdiSettings} 123 icon={mdiCog}
129 size={1.5} 124 size={1.5}
130 className={classes.workspacesSettingsButtonIcon} 125 className={classes.workspacesSettingsButtonIcon}
131 /> 126 />
@@ -152,7 +147,9 @@ class WorkspaceDrawer extends Component {
152 workspaceActions.activate({ workspace }); 147 workspaceActions.activate({ workspace });
153 workspaceActions.toggleWorkspaceDrawer(); 148 workspaceActions.toggleWorkspaceDrawer();
154 }} 149 }}
155 onContextMenuEditClick={() => workspaceActions.edit({ workspace })} 150 onContextMenuEditClick={() =>
151 workspaceActions.edit({ workspace })
152 }
156 services={getServicesForWorkspace(workspace)} 153 services={getServicesForWorkspace(workspace)}
157 shortcutIndex={index + 1} 154 shortcutIndex={index + 1}
158 /> 155 />
@@ -168,9 +165,7 @@ class WorkspaceDrawer extends Component {
168 size={1} 165 size={1}
169 className={classes.workspacesSettingsButtonIcon} 166 className={classes.workspacesSettingsButtonIcon}
170 /> 167 />
171 <span> 168 <span>{intl.formatMessage(messages.addNewWorkspaceLabel)}</span>
172 {intl.formatMessage(messages.addNewWorkspaceLabel)}
173 </span>
174 </div> 169 </div>
175 </div> 170 </div>
176 <ReactTooltip place="right" type="dark" effect="solid" /> 171 <ReactTooltip place="right" type="dark" effect="solid" />
@@ -179,4 +174,4 @@ class WorkspaceDrawer extends Component {
179 } 174 }
180} 175}
181 176
182export default WorkspaceDrawer; 177export default injectIntl(WorkspaceDrawer);
diff --git a/src/features/workspaces/components/WorkspaceDrawerItem.js b/src/features/workspaces/components/WorkspaceDrawerItem.js
index 252158364..4afb9c108 100644
--- a/src/features/workspaces/components/WorkspaceDrawerItem.js
+++ b/src/features/workspaces/components/WorkspaceDrawerItem.js
@@ -1,20 +1,20 @@
1import { Menu, getCurrentWindow } from '@electron/remote'; 1import { Menu } from '@electron/remote';
2import React, { Component } from 'react'; 2import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { observer } from 'mobx-react'; 4import { observer } from 'mobx-react';
5import injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
6import classnames from 'classnames'; 6import classnames from 'classnames';
7import { defineMessages, intlShape } from 'react-intl'; 7import { defineMessages, injectIntl } from 'react-intl';
8import { altKey, cmdOrCtrlShortcutKey } from '../../../environment'; 8import { altKey, cmdOrCtrlShortcutKey } from '../../../environment';
9 9
10const messages = defineMessages({ 10const messages = defineMessages({
11 noServicesAddedYet: { 11 noServicesAddedYet: {
12 id: 'workspaceDrawer.item.noServicesAddedYet', 12 id: 'workspaceDrawer.item.noServicesAddedYet',
13 defaultMessage: '!!!No services added yet', 13 defaultMessage: 'No services added yet',
14 }, 14 },
15 contextMenuEdit: { 15 contextMenuEdit: {
16 id: 'workspaceDrawer.item.contextMenuEdit', 16 id: 'workspaceDrawer.item.contextMenuEdit',
17 defaultMessage: '!!!edit', 17 defaultMessage: 'edit',
18 }, 18 },
19}); 19});
20 20
@@ -82,10 +82,6 @@ class WorkspaceDrawerItem extends Component {
82 onContextMenuEditClick: null, 82 onContextMenuEditClick: null,
83 }; 83 };
84 84
85 static contextTypes = {
86 intl: intlShape,
87 };
88
89 render() { 85 render() {
90 const { 86 const {
91 classes, 87 classes,
@@ -96,7 +92,8 @@ class WorkspaceDrawerItem extends Component {
96 services, 92 services,
97 shortcutIndex, 93 shortcutIndex,
98 } = this.props; 94 } = this.props;
99 const { intl } = this.context; 95
96 const { intl } = this.props;
100 97
101 const contextMenuTemplate = [ 98 const contextMenuTemplate = [
102 { 99 {
@@ -122,10 +119,14 @@ class WorkspaceDrawerItem extends Component {
122 ])} 119 ])}
123 onClick={onClick} 120 onClick={onClick}
124 onContextMenu={() => 121 onContextMenu={() =>
125 onContextMenuEditClick && contextMenu.popup(getCurrentWindow()) 122 onContextMenuEditClick && contextMenu.popup()
126 } 123 }
127 data-tip={`${ 124 data-tip={`${
128 shortcutIndex <= 9 ? `(${cmdOrCtrlShortcutKey(false)}+${altKey(false)}+${shortcutIndex})` : '' 125 shortcutIndex <= 9
126 ? `(${cmdOrCtrlShortcutKey(false)}+${altKey(
127 false,
128 )}+${shortcutIndex})`
129 : ''
129 }`} 130 }`}
130 > 131 >
131 <span 132 <span
@@ -142,7 +143,7 @@ class WorkspaceDrawerItem extends Component {
142 isActive ? classes.activeServices : null, 143 isActive ? classes.activeServices : null,
143 ])} 144 ])}
144 > 145 >
145 {services.length 146 {services.length > 0
146 ? services.join(', ') 147 ? services.join(', ')
147 : intl.formatMessage(messages.noServicesAddedYet)} 148 : intl.formatMessage(messages.noServicesAddedYet)}
148 </span> 149 </span>
@@ -151,4 +152,4 @@ class WorkspaceDrawerItem extends Component {
151 } 152 }
152} 153}
153 154
154export default WorkspaceDrawerItem; 155export default injectIntl(WorkspaceDrawerItem);
diff --git a/src/features/workspaces/components/WorkspaceItem.js b/src/features/workspaces/components/WorkspaceItem.js
index 85fc02d51..ec7b19add 100644
--- a/src/features/workspaces/components/WorkspaceItem.js
+++ b/src/features/workspaces/components/WorkspaceItem.js
@@ -1,12 +1,11 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { intlShape } from 'react-intl';
4import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
5import injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
6 5
7import Workspace from '../models/Workspace'; 6import Workspace from '../models/Workspace';
8 7
9const styles = (theme) => ({ 8const styles = theme => ({
10 row: { 9 row: {
11 height: theme.workspaces.settings.listItems.height, 10 height: theme.workspaces.settings.listItems.height,
12 borderBottom: `1px solid ${theme.workspaces.settings.listItems.borderColor}`, 11 borderBottom: `1px solid ${theme.workspaces.settings.listItems.borderColor}`,
@@ -17,7 +16,8 @@ const styles = (theme) => ({
17 columnName: {}, 16 columnName: {},
18}); 17});
19 18
20@injectSheet(styles) @observer 19@injectSheet(styles)
20@observer
21class WorkspaceItem extends Component { 21class WorkspaceItem extends Component {
22 static propTypes = { 22 static propTypes = {
23 classes: PropTypes.object.isRequired, 23 classes: PropTypes.object.isRequired,
@@ -25,18 +25,12 @@ class WorkspaceItem extends Component {
25 onItemClick: PropTypes.func.isRequired, 25 onItemClick: PropTypes.func.isRequired,
26 }; 26 };
27 27
28 static contextTypes = {
29 intl: intlShape,
30 };
31
32 render() { 28 render() {
33 const { classes, workspace, onItemClick } = this.props; 29 const { classes, workspace, onItemClick } = this.props;
34 30
35 return ( 31 return (
36 <tr className={classes.row}> 32 <tr className={classes.row}>
37 <td onClick={() => onItemClick(workspace)}> 33 <td onClick={() => onItemClick(workspace)}>{workspace.name}</td>
38 {workspace.name}
39 </td>
40 </tr> 34 </tr>
41 ); 35 );
42 } 36 }
diff --git a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
index c8ec0bc4c..33a82cf4b 100644
--- a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
+++ b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
@@ -4,14 +4,14 @@ import { observer } from 'mobx-react';
4import injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
5import classnames from 'classnames'; 5import classnames from 'classnames';
6import { Loader } from '@meetfranz/ui'; 6import { Loader } from '@meetfranz/ui';
7import { defineMessages, intlShape } from 'react-intl'; 7import { defineMessages, injectIntl } from 'react-intl';
8 8
9import { workspaceStore } from '../index'; 9import { workspaceStore } from '../index';
10 10
11const messages = defineMessages({ 11const messages = defineMessages({
12 switchingTo: { 12 switchingTo: {
13 id: 'workspaces.switchingIndicator.switchingTo', 13 id: 'workspaces.switchingIndicator.switchingTo',
14 defaultMessage: '!!!Switching to', 14 defaultMessage: 'Switching to',
15 }, 15 },
16}); 16});
17 17
@@ -61,13 +61,9 @@ class WorkspaceSwitchingIndicator extends Component {
61 theme: PropTypes.object.isRequired, 61 theme: PropTypes.object.isRequired,
62 }; 62 };
63 63
64 static contextTypes = {
65 intl: intlShape,
66 };
67
68 render() { 64 render() {
69 const { classes, theme } = this.props; 65 const { classes, theme } = this.props;
70 const { intl } = this.context; 66 const { intl } = this.props;
71 const { isSwitchingWorkspace, nextWorkspace } = workspaceStore; 67 const { isSwitchingWorkspace, nextWorkspace } = workspaceStore;
72 if (!isSwitchingWorkspace) return null; 68 if (!isSwitchingWorkspace) return null;
73 const nextWorkspaceName = nextWorkspace 69 const nextWorkspaceName = nextWorkspace
@@ -89,4 +85,4 @@ class WorkspaceSwitchingIndicator extends Component {
89 } 85 }
90} 86}
91 87
92export default WorkspaceSwitchingIndicator; 88export default injectIntl(WorkspaceSwitchingIndicator);
diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js
index 5f34204f1..49552df6b 100644
--- a/src/features/workspaces/components/WorkspacesDashboard.js
+++ b/src/features/workspaces/components/WorkspacesDashboard.js
@@ -1,7 +1,7 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5import injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
6import { Infobox } from '@meetfranz/ui'; 6import { Infobox } from '@meetfranz/ui';
7 7
@@ -16,35 +16,35 @@ import UIStore from '../../../stores/UIStore';
16const messages = defineMessages({ 16const messages = defineMessages({
17 headline: { 17 headline: {
18 id: 'settings.workspaces.headline', 18 id: 'settings.workspaces.headline',
19 defaultMessage: '!!!Your workspaces', 19 defaultMessage: 'Your workspaces',
20 }, 20 },
21 noServicesAdded: { 21 noServicesAdded: {
22 id: 'settings.workspaces.noWorkspacesAdded', 22 id: 'settings.workspaces.noWorkspacesAdded',
23 defaultMessage: "!!!You haven't created any workspaces yet.", 23 defaultMessage: "You haven't created any workspaces yet.",
24 }, 24 },
25 workspacesRequestFailed: { 25 workspacesRequestFailed: {
26 id: 'settings.workspaces.workspacesRequestFailed', 26 id: 'settings.workspaces.workspacesRequestFailed',
27 defaultMessage: '!!!Could not load your workspaces', 27 defaultMessage: 'Could not load your workspaces',
28 }, 28 },
29 tryReloadWorkspaces: { 29 tryReloadWorkspaces: {
30 id: 'settings.workspaces.tryReloadWorkspaces', 30 id: 'settings.workspaces.tryReloadWorkspaces',
31 defaultMessage: '!!!Try again', 31 defaultMessage: 'Try again',
32 }, 32 },
33 updatedInfo: { 33 updatedInfo: {
34 id: 'settings.workspaces.updatedInfo', 34 id: 'settings.workspaces.updatedInfo',
35 defaultMessage: '!!!Your changes have been saved', 35 defaultMessage: 'Your changes have been saved',
36 }, 36 },
37 deletedInfo: { 37 deletedInfo: {
38 id: 'settings.workspaces.deletedInfo', 38 id: 'settings.workspaces.deletedInfo',
39 defaultMessage: '!!!Workspace has been deleted', 39 defaultMessage: 'Workspace has been deleted',
40 }, 40 },
41 workspaceFeatureInfo: { 41 workspaceFeatureInfo: {
42 id: 'settings.workspaces.workspaceFeatureInfo', 42 id: 'settings.workspaces.workspaceFeatureInfo',
43 defaultMessage: '!!!Info about workspace feature', 43 defaultMessage: 'Ferdi 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.',
44 }, 44 },
45 workspaceFeatureHeadline: { 45 workspaceFeatureHeadline: {
46 id: 'settings.workspaces.workspaceFeatureHeadline', 46 id: 'settings.workspaces.workspaceFeatureHeadline',
47 defaultMessage: '!!!Less is More: Introducing Ferdi Workspaces', 47 defaultMessage: 'Less is More: Introducing Ferdi Workspaces',
48 }, 48 },
49}); 49});
50 50
@@ -83,10 +83,6 @@ class WorkspacesDashboard extends Component {
83 workspaces: MobxPropTypes.arrayOrObservableArray.isRequired, 83 workspaces: MobxPropTypes.arrayOrObservableArray.isRequired,
84 }; 84 };
85 85
86 static contextTypes = {
87 intl: intlShape,
88 };
89
90 render() { 86 render() {
91 const { 87 const {
92 classes, 88 classes,
@@ -99,7 +95,7 @@ class WorkspacesDashboard extends Component {
99 workspaces, 95 workspaces,
100 } = this.props; 96 } = this.props;
101 97
102 const { intl } = this.context; 98 const { intl } = this.props;
103 99
104 return ( 100 return (
105 <div className="settings__main"> 101 <div className="settings__main">
@@ -193,7 +189,7 @@ class WorkspacesDashboard extends Component {
193 } 189 }
194} 190}
195 191
196export default WorkspacesDashboard; 192export default injectIntl(WorkspacesDashboard);
197 193
198WorkspacesDashboard.wrappedComponent.propTypes = { 194WorkspacesDashboard.wrappedComponent.propTypes = {
199 stores: PropTypes.shape({ 195 stores: PropTypes.shape({
diff --git a/src/features/workspaces/constants.js b/src/features/workspaces/constants.ts
index 2d1416ee0..2d1416ee0 100644
--- a/src/features/workspaces/constants.js
+++ b/src/features/workspaces/constants.ts
diff --git a/src/features/workspaces/index.js b/src/features/workspaces/index.ts
index 83e4d9049..ecca64b41 100644
--- a/src/features/workspaces/index.js
+++ b/src/features/workspaces/index.ts
@@ -12,10 +12,8 @@ export default function initWorkspaces(stores, actions) {
12 12
13 // Toggle workspace feature 13 // Toggle workspace feature
14 reaction( 14 reaction(
15 () => ( 15 () => features.features.isWorkspaceEnabled,
16 features.features.isWorkspaceEnabled 16 isEnabled => {
17 ),
18 (isEnabled) => {
19 if (isEnabled && !workspaceStore.isFeatureActive) { 17 if (isEnabled && !workspaceStore.isFeatureActive) {
20 debug('Initializing `workspaces` feature'); 18 debug('Initializing `workspaces` feature');
21 workspaceStore.start(stores, actions); 19 workspaceStore.start(stores, actions);
diff --git a/src/features/workspaces/models/Workspace.js b/src/features/workspaces/models/Workspace.ts
index d9488e991..cd3918fba 100644
--- a/src/features/workspaces/models/Workspace.js
+++ b/src/features/workspaces/models/Workspace.ts
@@ -15,7 +15,7 @@ export default class Workspace {
15 15
16 constructor(data) { 16 constructor(data) {
17 if (!data.id) { 17 if (!data.id) {
18 throw Error('Workspace requires Id'); 18 throw new Error('Workspace requires Id');
19 } 19 }
20 20
21 this.id = data.id; 21 this.id = data.id;
@@ -28,8 +28,10 @@ export default class Workspace {
28 services.push(KEEP_WS_LOADED_USID); 28 services.push(KEEP_WS_LOADED_USID);
29 } else if (data.saving && data.services.includes(KEEP_WS_LOADED_USID)) { 29 } else if (data.saving && data.services.includes(KEEP_WS_LOADED_USID)) {
30 // Don't keep loaded 30 // Don't keep loaded
31 services = services.filter((e) => e !== KEEP_WS_LOADED_USID); 31 services = services.filter(e => e !== KEEP_WS_LOADED_USID);
32 } 32 }
33
34 // @ts-expect-error Property 'replace' does not exist on type 'never[]'.
33 this.services.replace(services); 35 this.services.replace(services);
34 36
35 this.userId = data.userId; 37 this.userId = data.userId;
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js
index ec9d7ee7f..db2b69f99 100644
--- a/src/features/workspaces/store.js
+++ b/src/features/workspaces/store.js
@@ -190,6 +190,12 @@ export default class WorkspacesStore extends FeatureStore {
190 setTimeout(() => { 190 setTimeout(() => {
191 this.isSwitchingWorkspace = false; 191 this.isSwitchingWorkspace = false;
192 this.nextWorkspace = null; 192 this.nextWorkspace = null;
193 if (this.stores.settings.app.splitMode) {
194 const serviceNames = new Set(this.getWorkspaceServices(workspace).map(service => service.name));
195 for (const wrapper of document.querySelectorAll('.services__webview-wrapper')) {
196 wrapper.style.display = serviceNames.has(wrapper.dataset.name) ? '' : 'none';
197 }
198 }
193 }, 1000); 199 }, 1000);
194 }; 200 };
195 201
@@ -205,6 +211,11 @@ export default class WorkspacesStore extends FeatureStore {
205 // Indicate that we are done switching to the default workspace 211 // Indicate that we are done switching to the default workspace
206 setTimeout(() => { 212 setTimeout(() => {
207 this.isSwitchingWorkspace = false; 213 this.isSwitchingWorkspace = false;
214 if (this.stores.settings.app.splitMode) {
215 for (const wrapper of document.querySelectorAll('.services__webview-wrapper')) {
216 wrapper.style.display = '';
217 }
218 }
208 }, 1000); 219 }, 1000);
209 }; 220 };
210 221
@@ -302,8 +313,8 @@ export default class WorkspacesStore extends FeatureStore {
302 const { allServicesRequest } = services; 313 const { allServicesRequest } = services;
303 const servicesHaveBeenLoaded = allServicesRequest.wasExecuted && !allServicesRequest.isError; 314 const servicesHaveBeenLoaded = allServicesRequest.wasExecuted && !allServicesRequest.isError;
304 // Loop through all workspaces and remove invalid service ids (locally) 315 // Loop through all workspaces and remove invalid service ids (locally)
305 this.workspaces.forEach((workspace) => { 316 for (const workspace of this.workspaces) {
306 workspace.services.forEach((serviceId) => { 317 for (const serviceId of workspace.services) {
307 if ( 318 if (
308 servicesHaveBeenLoaded 319 servicesHaveBeenLoaded
309 && !services.one(serviceId) 320 && !services.one(serviceId)
@@ -311,7 +322,7 @@ export default class WorkspacesStore extends FeatureStore {
311 ) { 322 ) {
312 workspace.services.remove(serviceId); 323 workspace.services.remove(serviceId);
313 } 324 }
314 }); 325 }
315 }); 326 }
316 }; 327 };
317} 328}