aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/services')
-rw-r--r--src/components/services/content/ConnectionLostBanner.js16
-rw-r--r--src/components/services/content/ErrorHandlers/WebviewErrorHandler.js38
-rw-r--r--src/components/services/content/ServiceDisabled.js17
-rw-r--r--src/components/services/content/ServiceView.js45
-rw-r--r--src/components/services/content/ServiceWebview.js3
-rw-r--r--src/components/services/content/Services.js103
-rw-r--r--src/components/services/content/WebviewCrashHandler.js26
-rw-r--r--src/components/services/tabs/TabItem.js94
-rw-r--r--src/components/services/tabs/Tabbar.js20
9 files changed, 199 insertions, 163 deletions
diff --git a/src/components/services/content/ConnectionLostBanner.js b/src/components/services/content/ConnectionLostBanner.js
index ebe863333..423edb3c7 100644
--- a/src/components/services/content/ConnectionLostBanner.js
+++ b/src/components/services/content/ConnectionLostBanner.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 { intlShape, defineMessages } from 'react-intl'; 6import { defineMessages, injectIntl } from 'react-intl';
7 7
8import { mdiAlert } from '@mdi/js'; 8import { mdiAlert } from '@mdi/js';
9import { LIVE_API_FERDI_WEBSITE } from '../../../config'; 9import { LIVE_API_FERDI_WEBSITE } from '../../../config';
@@ -12,15 +12,15 @@ import { LIVE_API_FERDI_WEBSITE } from '../../../config';
12const messages = defineMessages({ 12const messages = defineMessages({
13 text: { 13 text: {
14 id: 'connectionLostBanner.message', 14 id: 'connectionLostBanner.message',
15 defaultMessage: '!!!Oh no! Ferdi lost the connection to {name}.', 15 defaultMessage: 'Oh no! Ferdi lost the connection to {name}.',
16 }, 16 },
17 moreInformation: { 17 moreInformation: {
18 id: 'connectionLostBanner.informationLink', 18 id: 'connectionLostBanner.informationLink',
19 defaultMessage: '!!!What happened?', 19 defaultMessage: 'What happened?',
20 }, 20 },
21 cta: { 21 cta: {
22 id: 'connectionLostBanner.cta', 22 id: 'connectionLostBanner.cta',
23 defaultMessage: '!!!Reload Service', 23 defaultMessage: 'Reload Service',
24 }, 24 },
25}); 25});
26 26
@@ -78,16 +78,12 @@ class ConnectionLostBanner extends Component {
78 reload: PropTypes.func.isRequired, 78 reload: PropTypes.func.isRequired,
79 }; 79 };
80 80
81 static contextTypes = {
82 intl: intlShape,
83 };
84
85 inputRef = React.createRef(); 81 inputRef = React.createRef();
86 82
87 render() { 83 render() {
88 const { classes, name, reload } = this.props; 84 const { classes, name, reload } = this.props;
89 85
90 const { intl } = this.context; 86 const { intl } = this.props;
91 87
92 return ( 88 return (
93 <div className={classes.root}> 89 <div className={classes.root}>
@@ -110,4 +106,4 @@ class ConnectionLostBanner extends Component {
110 } 106 }
111} 107}
112 108
113export default ConnectionLostBanner; 109export default injectIntl(ConnectionLostBanner);
diff --git a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js
index 36e0ac418..b00db8c3f 100644
--- a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js
+++ b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.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 injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
6 6
7import Button from '../../../ui/Button'; 7import Button from '../../../ui/Button';
@@ -11,27 +11,29 @@ import styles from './styles';
11const messages = defineMessages({ 11const messages = defineMessages({
12 headline: { 12 headline: {
13 id: 'service.errorHandler.headline', 13 id: 'service.errorHandler.headline',
14 defaultMessage: '!!!Oh no!', 14 defaultMessage: 'Oh no!',
15 }, 15 },
16 text: { 16 text: {
17 id: 'service.errorHandler.text', 17 id: 'service.errorHandler.text',
18 defaultMessage: '!!!{name} has failed to load.', 18 defaultMessage: '{name} has failed to load.',
19 }, 19 },
20 action: { 20 action: {
21 id: 'service.errorHandler.action', 21 id: 'service.errorHandler.action',
22 defaultMessage: '!!!Reload {name}', 22 defaultMessage: 'Reload {name}',
23 }, 23 },
24 editAction: { 24 editAction: {
25 id: 'service.errorHandler.editAction', 25 id: 'service.errorHandler.editAction',
26 defaultMessage: '!!!Edit {name}', 26 defaultMessage: 'Edit {name}',
27 }, 27 },
28 errorMessage: { 28 errorMessage: {
29 id: 'service.errorHandler.message', 29 id: 'service.errorHandler.message',
30 defaultMessage: '!!!Error:', 30 defaultMessage: 'Error',
31 }, 31 },
32}); 32});
33 33
34export default @injectSheet(styles) @observer class WebviewErrorHandler extends Component { 34@injectSheet(styles)
35@observer
36class WebviewErrorHandler extends Component {
35 static propTypes = { 37 static propTypes = {
36 name: PropTypes.string.isRequired, 38 name: PropTypes.string.isRequired,
37 reload: PropTypes.func.isRequired, 39 reload: PropTypes.func.isRequired,
@@ -40,30 +42,16 @@ export default @injectSheet(styles) @observer class WebviewErrorHandler extends
40 classes: PropTypes.object.isRequired, 42 classes: PropTypes.object.isRequired,
41 }; 43 };
42 44
43 static contextTypes = {
44 intl: intlShape,
45 };
46
47 render() { 45 render() {
48 const { 46 const { name, reload, edit, errorMessage, classes } = this.props;
49 name, 47 const { intl } = this.props;
50 reload,
51 edit,
52 errorMessage,
53 classes,
54 } = this.props;
55 const { intl } = this.context;
56 48
57 return ( 49 return (
58 <div className={classes.component}> 50 <div className={classes.component}>
59 <h1>{intl.formatMessage(messages.headline)}</h1> 51 <h1>{intl.formatMessage(messages.headline)}</h1>
60 <p>{intl.formatMessage(messages.text, { name })}</p> 52 <p>{intl.formatMessage(messages.text, { name })}</p>
61 <p> 53 <p>
62 <strong> 54 <strong>{intl.formatMessage(messages.errorMessage)}:</strong>{' '}
63 {intl.formatMessage(messages.errorMessage)}
64 :
65 </strong>
66 {' '}
67 {errorMessage} 55 {errorMessage}
68 </p> 56 </p>
69 <div className={classes.buttonContainer}> 57 <div className={classes.buttonContainer}>
@@ -82,3 +70,5 @@ export default @injectSheet(styles) @observer class WebviewErrorHandler extends
82 ); 70 );
83 } 71 }
84} 72}
73
74export default injectIntl(WebviewErrorHandler);
diff --git a/src/components/services/content/ServiceDisabled.js b/src/components/services/content/ServiceDisabled.js
index d0f12256e..e59ed58bd 100644
--- a/src/components/services/content/ServiceDisabled.js
+++ b/src/components/services/content/ServiceDisabled.js
@@ -1,38 +1,35 @@
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';
5 5
6import Button from '../../ui/Button'; 6import Button from '../../ui/Button';
7 7
8const messages = defineMessages({ 8const messages = defineMessages({
9 headline: { 9 headline: {
10 id: 'service.disabledHandler.headline', 10 id: 'service.disabledHandler.headline',
11 defaultMessage: '!!!{name} is disabled', 11 defaultMessage: '{name} is disabled',
12 }, 12 },
13 action: { 13 action: {
14 id: 'service.disabledHandler.action', 14 id: 'service.disabledHandler.action',
15 defaultMessage: '!!!Enable {name}', 15 defaultMessage: 'Enable {name}',
16 }, 16 },
17}); 17});
18 18
19export default @observer class ServiceDisabled extends Component { 19@observer
20class ServiceDisabled extends Component {
20 static propTypes = { 21 static propTypes = {
21 name: PropTypes.string.isRequired, 22 name: PropTypes.string.isRequired,
22 enable: PropTypes.func.isRequired, 23 enable: PropTypes.func.isRequired,
23 }; 24 };
24 25
25 static contextTypes = {
26 intl: intlShape,
27 };
28
29 countdownInterval = null; 26 countdownInterval = null;
30 27
31 countdownIntervalTimeout = 1000; 28 countdownIntervalTimeout = 1000;
32 29
33 render() { 30 render() {
34 const { name, enable } = this.props; 31 const { name, enable } = this.props;
35 const { intl } = this.context; 32 const { intl } = this.props;
36 33
37 return ( 34 return (
38 <div className="services__info-layer"> 35 <div className="services__info-layer">
@@ -46,3 +43,5 @@ export default @observer class ServiceDisabled extends Component {
46 ); 43 );
47 } 44 }
48} 45}
46
47export default injectIntl(ServiceDisabled);
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 3fc084ff0..81401b1d2 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -15,7 +15,9 @@ import SettingsStore from '../../../stores/SettingsStore';
15import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; 15import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen';
16import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config'; 16import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config';
17 17
18export default @inject('stores', 'actions') @observer class ServiceView extends Component { 18@inject('stores', 'actions')
19@observer
20class ServiceView extends Component {
19 static propTypes = { 21 static propTypes = {
20 service: PropTypes.instanceOf(ServiceModel).isRequired, 22 service: PropTypes.instanceOf(ServiceModel).isRequired,
21 setWebviewReference: PropTypes.func.isRequired, 23 setWebviewReference: PropTypes.func.isRequired,
@@ -63,7 +65,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
63 clearTimeout(this.hibernationTimer); 65 clearTimeout(this.hibernationTimer);
64 } 66 }
65 67
66 updateTargetUrl = (event) => { 68 updateTargetUrl = event => {
67 let visible = true; 69 let visible = true;
68 if (event.url === '' || event.url === '#') { 70 if (event.url === '' || event.url === '#') {
69 visible = false; 71 visible = false;
@@ -86,11 +88,12 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
86 isSpellcheckerEnabled, 88 isSpellcheckerEnabled,
87 } = this.props; 89 } = this.props;
88 90
89 const { 91 const { navigationBarBehaviour } = stores.settings.app;
90 navigationBarBehaviour,
91 } = stores.settings.app;
92 92
93 const showNavBar = navigationBarBehaviour === 'always' || (navigationBarBehaviour === 'custom' && service.recipe.id === CUSTOM_WEBSITE_RECIPE_ID); 93 const showNavBar =
94 navigationBarBehaviour === 'always' ||
95 (navigationBarBehaviour === 'custom' &&
96 service.recipe.id === CUSTOM_WEBSITE_RECIPE_ID);
94 97
95 const webviewClasses = classnames({ 98 const webviewClasses = classnames({
96 services__webview: true, 99 services__webview: true,
@@ -101,13 +104,11 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
101 104
102 let statusBar = null; 105 let statusBar = null;
103 if (this.state.statusBarVisible) { 106 if (this.state.statusBarVisible) {
104 statusBar = ( 107 statusBar = <StatusBarTargetUrl text={this.state.targetUrl} />;
105 <StatusBarTargetUrl text={this.state.targetUrl} />
106 );
107 } 108 }
108 109
109 return ( 110 return (
110 <div className={webviewClasses}> 111 <div className={webviewClasses} data-name={service.name}>
111 {service.isActive && service.isEnabled && ( 112 {service.isActive && service.isEnabled && (
112 <> 113 <>
113 {service.hasCrashed && ( 114 {service.hasCrashed && (
@@ -117,11 +118,11 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
117 reload={reload} 118 reload={reload}
118 /> 119 />
119 )} 120 )}
120 {service.isEnabled && service.isLoading && service.isFirstLoad && !service.isServiceAccessRestricted && ( 121 {service.isEnabled &&
121 <WebviewLoader 122 service.isLoading &&
122 loaded={false} 123 service.isFirstLoad &&
123 name={service.name} 124 !service.isServiceAccessRestricted && (
124 /> 125 <WebviewLoader loaded={false} name={service.name} />
125 )} 126 )}
126 {service.isError && ( 127 {service.isError && (
127 <WebviewErrorHandler 128 <WebviewErrorHandler
@@ -147,9 +148,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
147 <> 148 <>
148 {!service.isHibernating ? ( 149 {!service.isHibernating ? (
149 <> 150 <>
150 {showNavBar && ( 151 {showNavBar && <WebControlsScreen service={service} />}
151 <WebControlsScreen service={service} />
152 )}
153 <ServiceWebview 152 <ServiceWebview
154 service={service} 153 service={service}
155 setWebviewReference={setWebviewReference} 154 setWebviewReference={setWebviewReference}
@@ -159,9 +158,11 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
159 </> 158 </>
160 ) : ( 159 ) : (
161 <div> 160 <div>
162 <span role="img" aria-label="Sleeping Emoji">😴</span> 161 <span role="img" aria-label="Sleeping Emoji">
163 {' '} 162 😴
164 This service is currently hibernating. If this page doesn&#x27;t close soon, please try reloading Ferdi. 163 </span>{' '}
164 This service is currently hibernating. If this page doesn&#x27;t
165 close soon, please try reloading Ferdi.
165 </div> 166 </div>
166 )} 167 )}
167 </> 168 </>
@@ -171,3 +172,5 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
171 ); 172 );
172 } 173 }
173} 174}
175
176export default ServiceView;
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index c0f48793a..d3170be53 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -85,7 +85,8 @@ class ServiceWebview extends Component {
85 useragent={service.userAgent} 85 useragent={service.userAgent}
86 disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined} 86 disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined}
87 allowpopups 87 allowpopups
88 webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}`} 88 nodeintegration
89 webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}, contextIsolation=1, enableRemoteModule=1`}
89 /> 90 />
90 ); 91 );
91 } 92 }
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index bb93ff7d4..fb43fb816 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -2,7 +2,7 @@ import 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 { Link } from 'react-router'; 4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6import Confetti from 'react-confetti'; 6import Confetti from 'react-confetti';
7import ms from 'ms'; 7import ms from 'ms';
8import injectSheet from 'react-jss'; 8import injectSheet from 'react-jss';
@@ -14,23 +14,24 @@ import serverlessLogin from '../../../helpers/serverless-helpers';
14const messages = defineMessages({ 14const messages = defineMessages({
15 welcome: { 15 welcome: {
16 id: 'services.welcome', 16 id: 'services.welcome',
17 defaultMessage: '!!!Welcome to Ferdi', 17 defaultMessage: 'Welcome to Ferdi',
18 }, 18 },
19 getStarted: { 19 getStarted: {
20 id: 'services.getStarted', 20 id: 'services.getStarted',
21 defaultMessage: '!!!Get started', 21 defaultMessage: 'Get started',
22 }, 22 },
23 login: { 23 login: {
24 id: 'services.login', 24 id: 'services.login',
25 defaultMessage: '!!!Please login to use Ferdi.', 25 defaultMessage: 'Please login to use Ferdi.',
26 }, 26 },
27 serverless: { 27 serverless: {
28 id: 'services.serverless', 28 id: 'services.serverless',
29 defaultMessage: '!!!Use Ferdi without an Account', 29 defaultMessage: 'Use Ferdi without an Account',
30 }, 30 },
31 serverInfo: { 31 serverInfo: {
32 id: 'services.serverInfo', 32 id: 'services.serverInfo',
33 defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner. If you are switching over (from one of the hosted servers) to using Ferdi without an account, please be informed that you can export your data from that server and subsequently import it using the Help menu to resurrect all your workspaces and configured services!', 33 defaultMessage:
34 'Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner. If you are switching over (from one of the hosted servers) to using Ferdi without an account, please be informed that you can export your data from that server and subsequently import it using the Help menu to resurrect all your workspaces and configured services!',
34 }, 35 },
35}); 36});
36 37
@@ -43,7 +44,10 @@ const styles = {
43 }, 44 },
44}; 45};
45 46
46export default @injectSheet(styles) @inject('actions') @observer class Services extends Component { 47@injectSheet(styles)
48@inject('actions')
49@observer
50class Services extends Component {
47 static propTypes = { 51 static propTypes = {
48 services: MobxPropTypes.arrayOrObservableArray, 52 services: MobxPropTypes.arrayOrObservableArray,
49 setWebviewReference: PropTypes.func.isRequired, 53 setWebviewReference: PropTypes.func.isRequired,
@@ -63,10 +67,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
63 services: [], 67 services: [],
64 }; 68 };
65 69
66 static contextTypes = {
67 intl: intlShape,
68 };
69
70 state = { 70 state = {
71 showConfetti: true, 71 showConfetti: true,
72 }; 72 };
@@ -112,11 +112,9 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
112 isSpellcheckerEnabled, 112 isSpellcheckerEnabled,
113 } = this.props; 113 } = this.props;
114 114
115 const { 115 const { showConfetti } = this.state;
116 showConfetti,
117 } = this.state;
118 116
119 const { intl } = this.context; 117 const { intl } = this.props;
120 const isLoggedIn = Boolean(localStorage.getItem('authToken')); 118 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
121 119
122 return ( 120 return (
@@ -131,25 +129,28 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
131 </div> 129 </div>
132 )} 130 )}
133 {services.length === 0 && ( 131 {services.length === 0 && (
134 <Appear 132 <Appear timeout={1500} transitionName="slideUp">
135 timeout={1500}
136 transitionName="slideUp"
137 >
138 <div className="services__no-service"> 133 <div className="services__no-service">
139 <img src="./assets/images/logo.svg" alt="Logo" style={{ maxHeight: '50vh' }} /> 134 <img
135 src="./assets/images/logo.svg"
136 alt="Logo"
137 style={{ maxHeight: '50vh' }}
138 />
140 <h1>{intl.formatMessage(messages.welcome)}</h1> 139 <h1>{intl.formatMessage(messages.welcome)}</h1>
141 { !isLoggedIn && ( 140 {!isLoggedIn && (
142 <> 141 <>
143 <p>{intl.formatMessage(messages.login)}</p> 142 <p>{intl.formatMessage(messages.login)}</p>
144 <p>{intl.formatMessage(messages.serverInfo)}</p> 143 <p>{intl.formatMessage(messages.serverInfo)}</p>
145 </> 144 </>
146 ) } 145 )}
147 <Appear 146 <Appear timeout={300} transitionName="slideUp">
148 timeout={300} 147 <Link
149 transitionName="slideUp" 148 to={isLoggedIn ? '/settings/recipes' : '/auth/welcome'}
150 > 149 className="button"
151 <Link to={isLoggedIn ? '/settings/recipes' : '/auth/welcome'} className="button"> 150 >
152 { isLoggedIn ? intl.formatMessage(messages.getStarted) : 'Login' } 151 {isLoggedIn
152 ? intl.formatMessage(messages.getStarted)
153 : 'Login'}
153 </Link> 154 </Link>
154 {!isLoggedIn && ( 155 {!isLoggedIn && (
155 <button 156 <button
@@ -167,27 +168,33 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
167 </div> 168 </div>
168 </Appear> 169 </Appear>
169 )} 170 )}
170 {services.filter((service) => !service.isTodosService).map((service) => ( 171 {services
171 <ServiceView 172 .filter(service => !service.isTodosService)
172 key={service.id} 173 .map(service => (
173 service={service} 174 <ServiceView
174 handleIPCMessage={handleIPCMessage} 175 key={service.id}
175 setWebviewReference={setWebviewReference} 176 service={service}
176 detachService={detachService} 177 handleIPCMessage={handleIPCMessage}
177 openWindow={openWindow} 178 setWebviewReference={setWebviewReference}
178 reload={() => reload({ serviceId: service.id })} 179 detachService={detachService}
179 edit={() => openSettings({ path: `services/edit/${service.id}` })} 180 openWindow={openWindow}
180 enable={() => update({ 181 reload={() => reload({ serviceId: service.id })}
181 serviceId: service.id, 182 edit={() => openSettings({ path: `services/edit/${service.id}` })}
182 serviceData: { 183 enable={() =>
183 isEnabled: true, 184 update({
184 }, 185 serviceId: service.id,
185 redirect: false, 186 serviceData: {
186 })} 187 isEnabled: true,
187 isSpellcheckerEnabled={isSpellcheckerEnabled} 188 },
188 /> 189 redirect: false,
189 ))} 190 })
191 }
192 isSpellcheckerEnabled={isSpellcheckerEnabled}
193 />
194 ))}
190 </div> 195 </div>
191 ); 196 );
192 } 197 }
193} 198}
199
200export default injectIntl(Services);
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js
index 10ff0bbbb..a332602be 100644
--- a/src/components/services/content/WebviewCrashHandler.js
+++ b/src/components/services/content/WebviewCrashHandler.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 ms from 'ms'; 5import ms from 'ms';
6 6
7import Button from '../../ui/Button'; 7import Button from '../../ui/Button';
@@ -9,35 +9,33 @@ import Button from '../../ui/Button';
9const messages = defineMessages({ 9const messages = defineMessages({
10 headline: { 10 headline: {
11 id: 'service.crashHandler.headline', 11 id: 'service.crashHandler.headline',
12 defaultMessage: '!!!Oh no!', 12 defaultMessage: 'Oh no!',
13 }, 13 },
14 text: { 14 text: {
15 id: 'service.crashHandler.text', 15 id: 'service.crashHandler.text',
16 defaultMessage: '!!!{name} has caused an error.', 16 defaultMessage: '{name} has caused an error.',
17 }, 17 },
18 action: { 18 action: {
19 id: 'service.crashHandler.action', 19 id: 'service.crashHandler.action',
20 defaultMessage: '!!!Reload {name}', 20 defaultMessage: 'Reload {name}',
21 }, 21 },
22 autoReload: { 22 autoReload: {
23 id: 'service.crashHandler.autoReload', 23 id: 'service.crashHandler.autoReload',
24 defaultMessage: '!!!Trying to automatically restore {name} in {seconds} seconds', 24 defaultMessage:
25 'Trying to automatically restore {name} in {seconds} seconds',
25 }, 26 },
26}); 27});
27 28
28export default @observer class WebviewCrashHandler extends Component { 29@observer
30class WebviewCrashHandler extends Component {
29 static propTypes = { 31 static propTypes = {
30 name: PropTypes.string.isRequired, 32 name: PropTypes.string.isRequired,
31 reload: PropTypes.func.isRequired, 33 reload: PropTypes.func.isRequired,
32 }; 34 };
33 35
34 static contextTypes = {
35 intl: intlShape,
36 };
37
38 state = { 36 state = {
39 countdown: ms('10s'), 37 countdown: ms('10s'),
40 } 38 };
41 39
42 countdownInterval = null; 40 countdownInterval = null;
43 41
@@ -47,7 +45,7 @@ export default @observer class WebviewCrashHandler extends Component {
47 const { reload } = this.props; 45 const { reload } = this.props;
48 46
49 this.countdownInterval = setInterval(() => { 47 this.countdownInterval = setInterval(() => {
50 this.setState((prevState) => ({ 48 this.setState(prevState => ({
51 countdown: prevState.countdown - this.countdownIntervalTimeout, 49 countdown: prevState.countdown - this.countdownIntervalTimeout,
52 })); 50 }));
53 51
@@ -60,7 +58,7 @@ export default @observer class WebviewCrashHandler extends Component {
60 58
61 render() { 59 render() {
62 const { name, reload } = this.props; 60 const { name, reload } = this.props;
63 const { intl } = this.context; 61 const { intl } = this.props;
64 62
65 return ( 63 return (
66 <div className="services__info-layer"> 64 <div className="services__info-layer">
@@ -82,3 +80,5 @@ export default @observer class WebviewCrashHandler extends Component {
82 ); 80 );
83 } 81 }
84} 82}
83
84export default injectIntl(WebviewCrashHandler);
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
index e5892be5d..2474682df 100644
--- a/src/components/services/tabs/TabItem.js
+++ b/src/components/services/tabs/TabItem.js
@@ -1,6 +1,6 @@
1import { Menu, dialog, app, getCurrentWindow } from '@electron/remote'; 1import { Menu, dialog, app } from '@electron/remote';
2import React, { Component } from 'react'; 2import React, { Component } from 'react';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, injectIntl } from 'react-intl';
4import PropTypes from 'prop-types'; 4import PropTypes from 'prop-types';
5import { observer } from 'mobx-react'; 5import { observer } from 'mobx-react';
6import classnames from 'classnames'; 6import classnames from 'classnames';
@@ -20,56 +20,55 @@ const IS_SERVICE_DEBUGGING_ENABLED = (
20const messages = defineMessages({ 20const messages = defineMessages({
21 reload: { 21 reload: {
22 id: 'tabs.item.reload', 22 id: 'tabs.item.reload',
23 defaultMessage: '!!!Reload', 23 defaultMessage: 'Reload',
24 }, 24 },
25 disableNotifications: { 25 disableNotifications: {
26 id: 'tabs.item.disableNotifications', 26 id: 'tabs.item.disableNotifications',
27 defaultMessage: '!!!Disable notifications', 27 defaultMessage: 'Disable notifications',
28 }, 28 },
29 enableNotifications: { 29 enableNotifications: {
30 id: 'tabs.item.enableNotification', 30 id: 'tabs.item.enableNotification',
31 defaultMessage: '!!!Enable notifications', 31 defaultMessage: 'Enable notifications',
32 }, 32 },
33 disableAudio: { 33 disableAudio: {
34 id: 'tabs.item.disableAudio', 34 id: 'tabs.item.disableAudio',
35 defaultMessage: '!!!Disable audio', 35 defaultMessage: 'Disable audio',
36 }, 36 },
37 enableAudio: { 37 enableAudio: {
38 id: 'tabs.item.enableAudio', 38 id: 'tabs.item.enableAudio',
39 defaultMessage: '!!!Enable audio', 39 defaultMessage: 'Enable audio',
40 }, 40 },
41 enableDarkMode: { 41 enableDarkMode: {
42 id: 'tabs.item.enableDarkMode', 42 id: 'tabs.item.enableDarkMode',
43 defaultMessage: '!!!Enable Dark mode', 43 defaultMessage: 'Enable Dark mode',
44 }, 44 },
45 disableDarkMode: { 45 disableDarkMode: {
46 id: 'tabs.item.disableDarkMode', 46 id: 'tabs.item.disableDarkMode',
47 defaultMessage: '!!!Disable Dark mode', 47 defaultMessage: 'Disable Dark mode',
48 }, 48 },
49 disableService: { 49 disableService: {
50 id: 'tabs.item.disableService', 50 id: 'tabs.item.disableService',
51 defaultMessage: '!!!Disable Service', 51 defaultMessage: 'Disable service',
52 }, 52 },
53 enableService: { 53 enableService: {
54 id: 'tabs.item.enableService', 54 id: 'tabs.item.enableService',
55 defaultMessage: '!!!Enable Service', 55 defaultMessage: 'Enable service',
56 }, 56 },
57 hibernateService: { 57 hibernateService: {
58 id: 'tabs.item.hibernateService', 58 id: 'tabs.item.hibernateService',
59 defaultMessage: '!!!Hibernate Service', 59 defaultMessage: 'Hibernate service',
60 }, 60 },
61 wakeUpService: { 61 wakeUpService: {
62 id: 'tabs.item.wakeUpService', 62 id: 'tabs.item.wakeUpService',
63 defaultMessage: '!!!Wake Up Service', 63 defaultMessage: 'Wake up service',
64 }, 64 },
65 deleteService: { 65 deleteService: {
66 id: 'tabs.item.deleteService', 66 id: 'tabs.item.deleteService',
67 defaultMessage: '!!!Delete Service', 67 defaultMessage: 'Delete service',
68 }, 68 },
69 confirmDeleteService: { 69 confirmDeleteService: {
70 id: 'tabs.item.confirmDeleteService', 70 id: 'tabs.item.confirmDeleteService',
71 defaultMessage: 71 defaultMessage: 'Do you really want to delete the {serviceName} service?',
72 '!!!Do you really want to delete the {serviceName} service?',
73 }, 72 },
74}); 73});
75 74
@@ -134,14 +133,44 @@ class TabItem extends Component {
134 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, 133 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired,
135 }; 134 };
136 135
137 static contextTypes = {
138 intl: intlShape,
139 };
140
141 @observable isPolled = false; 136 @observable isPolled = false;
142 137
143 @observable isPollAnswered = false; 138 @observable isPollAnswered = false;
144 139
140 constructor(props) {
141 super(props);
142 this.state = {
143 showShortcutIndex: false,
144 };
145 }
146
147 handleShowShortcutIndex = () => {
148 this.setState({ showShortcutIndex: true });
149 };
150
151 checkForLongPress = () => {
152 let longpressDelay = null;
153 const longpressDelayDuration = 1000;
154
155 document.addEventListener(
156 'keydown',
157 e => {
158 if (e.ctrlKey || e.metaKey) {
159 longpressDelay = setTimeout(
160 this.handleShowShortcutIndex,
161 longpressDelayDuration,
162 );
163 }
164 },
165 { capture: true },
166 );
167
168 document.addEventListener('keyup', () => {
169 clearTimeout(longpressDelay);
170 this.setState({ showShortcutIndex: false });
171 });
172 };
173
145 componentDidMount() { 174 componentDidMount() {
146 const { service } = this.props; 175 const { service } = this.props;
147 176
@@ -164,6 +193,8 @@ class TabItem extends Component {
164 } 193 }
165 }); 194 });
166 } 195 }
196
197 this.checkForLongPress();
167 } 198 }
168 199
169 render() { 200 render() {
@@ -185,7 +216,7 @@ class TabItem extends Component {
185 showMessageBadgeWhenMutedSetting, 216 showMessageBadgeWhenMutedSetting,
186 showMessageBadgesEvenWhenMuted, 217 showMessageBadgesEvenWhenMuted,
187 } = this.props; 218 } = this.props;
188 const { intl } = this.context; 219 const { intl } = this.props;
189 220
190 const menuTemplate = [ 221 const menuTemplate = [
191 { 222 {
@@ -240,8 +271,9 @@ class TabItem extends Component {
240 ? messages.wakeUpService 271 ? messages.wakeUpService
241 : messages.hibernateService, 272 : messages.hibernateService,
242 ), 273 ),
274 // eslint-disable-next-line no-confusing-arrow
243 click: () => 275 click: () =>
244 (service.isHibernating ? wakeUpService() : hibernateService()), 276 service.isHibernating ? wakeUpService() : hibernateService(),
245 enabled: service.canHibernate, 277 enabled: service.canHibernate,
246 }, 278 },
247 { 279 {
@@ -256,7 +288,10 @@ class TabItem extends Component {
256 detail: intl.formatMessage(messages.confirmDeleteService, { 288 detail: intl.formatMessage(messages.confirmDeleteService, {
257 serviceName: service.name || service.recipe.name, 289 serviceName: service.name || service.recipe.name,
258 }), 290 }),
259 buttons: [intl.formatMessage(globalMessages.yes), intl.formatMessage(globalMessages.no)], 291 buttons: [
292 intl.formatMessage(globalMessages.yes),
293 intl.formatMessage(globalMessages.no),
294 ],
260 }); 295 });
261 if (selection === 0) { 296 if (selection === 0) {
262 deleteService(); 297 deleteService();
@@ -283,7 +318,7 @@ class TabItem extends Component {
283 service.unreadDirectMessageCount === 0 && 318 service.unreadDirectMessageCount === 0 &&
284 service.isIndirectMessageBadgeEnabled && ( 319 service.isIndirectMessageBadgeEnabled && (
285 <span className="tab-item__message-count is-indirect">•</span> 320 <span className="tab-item__message-count is-indirect">•</span>
286 )} 321 )}
287 {service.isHibernating && ( 322 {service.isHibernating && (
288 <span className="tab-item__message-count hibernating">•</span> 323 <span className="tab-item__message-count hibernating">•</span>
289 )} 324 )}
@@ -302,9 +337,11 @@ class TabItem extends Component {
302 'is-disabled': !service.isEnabled, 337 'is-disabled': !service.isEnabled,
303 })} 338 })}
304 onClick={clickHandler} 339 onClick={clickHandler}
305 onContextMenu={() => menu.popup(getCurrentWindow())} 340 onContextMenu={() => menu.popup()}
306 data-tip={`${service.name} ${ 341 data-tip={`${service.name} ${
307 shortcutIndex <= 9 ? `(${cmdOrCtrlShortcutKey(false)}+${shortcutIndex})` : '' 342 shortcutIndex <= 9
343 ? `(${cmdOrCtrlShortcutKey(false)}+${shortcutIndex})`
344 : ''
308 }`} 345 }`}
309 > 346 >
310 <img src={service.icon} className="tab-item__icon" alt="" /> 347 <img src={service.icon} className="tab-item__icon" alt="" />
@@ -327,9 +364,12 @@ class TabItem extends Component {
327 /> 364 />
328 </> 365 </>
329 )} 366 )}
367 {shortcutIndex && this.state.showShortcutIndex && (
368 <span className="tab-item__shortcut-index">{shortcutIndex}</span>
369 )}
330 </li> 370 </li>
331 ); 371 );
332 } 372 }
333} 373}
334 374
335export default SortableElement(TabItem); 375export default injectIntl(SortableElement(TabItem));
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js
index c1421a2b1..a77799819 100644
--- a/src/components/services/tabs/Tabbar.js
+++ b/src/components/services/tabs/Tabbar.js
@@ -4,7 +4,8 @@ import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4 4
5import TabBarSortableList from './TabBarSortableList'; 5import TabBarSortableList from './TabBarSortableList';
6 6
7export default @observer class TabBar extends Component { 7@observer
8class TabBar extends Component {
8 static propTypes = { 9 static propTypes = {
9 services: MobxPropTypes.arrayOrObservableArray.isRequired, 10 services: MobxPropTypes.arrayOrObservableArray.isRequired,
10 setActive: PropTypes.func.isRequired, 11 setActive: PropTypes.func.isRequired,
@@ -26,16 +27,13 @@ export default @observer class TabBar extends Component {
26 }; 27 };
27 28
28 onSortEnd = ({ oldIndex, newIndex }) => { 29 onSortEnd = ({ oldIndex, newIndex }) => {
29 const { 30 const { enableToolTip, reorder } = this.props;
30 enableToolTip,
31 reorder,
32 } = this.props;
33 31
34 enableToolTip(); 32 enableToolTip();
35 reorder({ oldIndex, newIndex }); 33 reorder({ oldIndex, newIndex });
36 }; 34 };
37 35
38 shouldPreventSorting = (event) => event.target.tagName !== 'LI'; 36 shouldPreventSorting = event => event.target.tagName !== 'LI';
39 37
40 toggleService = ({ serviceId, isEnabled }) => { 38 toggleService = ({ serviceId, isEnabled }) => {
41 const { updateService } = this.props; 39 const { updateService } = this.props;
@@ -102,10 +100,10 @@ export default @observer class TabBar extends Component {
102 toggleAudio={toggleAudio} 100 toggleAudio={toggleAudio}
103 toggleDarkMode={toggleDarkMode} 101 toggleDarkMode={toggleDarkMode}
104 deleteService={deleteService} 102 deleteService={deleteService}
105 disableService={(args) => this.disableService(args)} 103 disableService={args => this.disableService(args)}
106 enableService={(args) => this.enableService(args)} 104 enableService={args => this.enableService(args)}
107 hibernateService={(args) => this.hibernateService(args)} 105 hibernateService={args => this.hibernateService(args)}
108 wakeUpService={(args) => this.wakeUpService(args)} 106 wakeUpService={args => this.wakeUpService(args)}
109 openSettings={openSettings} 107 openSettings={openSettings}
110 distance={20} 108 distance={20}
111 axis={axis} 109 axis={axis}
@@ -118,3 +116,5 @@ export default @observer class TabBar extends Component {
118 ); 116 );
119 } 117 }
120} 118}
119
120export default TabBar;