diff options
Diffstat (limited to 'src/components/services')
-rw-r--r-- | src/components/services/content/ConnectionLostBanner.js | 16 | ||||
-rw-r--r-- | src/components/services/content/ErrorHandlers/WebviewErrorHandler.js | 38 | ||||
-rw-r--r-- | src/components/services/content/ServiceDisabled.js | 17 | ||||
-rw-r--r-- | src/components/services/content/ServiceView.js | 45 | ||||
-rw-r--r-- | src/components/services/content/ServiceWebview.js | 3 | ||||
-rw-r--r-- | src/components/services/content/Services.js | 103 | ||||
-rw-r--r-- | src/components/services/content/WebviewCrashHandler.js | 26 | ||||
-rw-r--r-- | src/components/services/tabs/TabItem.js | 94 | ||||
-rw-r--r-- | src/components/services/tabs/Tabbar.js | 20 |
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'; | |||
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import injectSheet from 'react-jss'; | 4 | import injectSheet from 'react-jss'; |
5 | import { Icon } from '@meetfranz/ui'; | 5 | import { Icon } from '@meetfranz/ui'; |
6 | import { intlShape, defineMessages } from 'react-intl'; | 6 | import { defineMessages, injectIntl } from 'react-intl'; |
7 | 7 | ||
8 | import { mdiAlert } from '@mdi/js'; | 8 | import { mdiAlert } from '@mdi/js'; |
9 | import { LIVE_API_FERDI_WEBSITE } from '../../../config'; | 9 | import { LIVE_API_FERDI_WEBSITE } from '../../../config'; |
@@ -12,15 +12,15 @@ import { LIVE_API_FERDI_WEBSITE } from '../../../config'; | |||
12 | const messages = defineMessages({ | 12 | const 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 | ||
113 | export default ConnectionLostBanner; | 109 | export 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 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, injectIntl } from 'react-intl'; |
5 | import injectSheet from 'react-jss'; | 5 | import injectSheet from 'react-jss'; |
6 | 6 | ||
7 | import Button from '../../../ui/Button'; | 7 | import Button from '../../../ui/Button'; |
@@ -11,27 +11,29 @@ import styles from './styles'; | |||
11 | const messages = defineMessages({ | 11 | const 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 | ||
34 | export default @injectSheet(styles) @observer class WebviewErrorHandler extends Component { | 34 | @injectSheet(styles) |
35 | @observer | ||
36 | class 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 | |||
74 | export 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 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, injectIntl } from 'react-intl'; |
5 | 5 | ||
6 | import Button from '../../ui/Button'; | 6 | import Button from '../../ui/Button'; |
7 | 7 | ||
8 | const messages = defineMessages({ | 8 | const 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 | ||
19 | export default @observer class ServiceDisabled extends Component { | 19 | @observer |
20 | class 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 | |||
47 | export 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'; | |||
15 | import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; | 15 | import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; |
16 | import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config'; | 16 | import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config'; |
17 | 17 | ||
18 | export default @inject('stores', 'actions') @observer class ServiceView extends Component { | 18 | @inject('stores', 'actions') |
19 | @observer | ||
20 | class 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't close soon, please try reloading Ferdi. | 163 | </span>{' '} |
164 | This service is currently hibernating. If this page doesn'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 | |||
176 | export 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'; | |||
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react'; | 3 | import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react'; |
4 | import { Link } from 'react-router'; | 4 | import { Link } from 'react-router'; |
5 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, injectIntl } from 'react-intl'; |
6 | import Confetti from 'react-confetti'; | 6 | import Confetti from 'react-confetti'; |
7 | import ms from 'ms'; | 7 | import ms from 'ms'; |
8 | import injectSheet from 'react-jss'; | 8 | import injectSheet from 'react-jss'; |
@@ -14,23 +14,24 @@ import serverlessLogin from '../../../helpers/serverless-helpers'; | |||
14 | const messages = defineMessages({ | 14 | const 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 | ||
46 | export default @injectSheet(styles) @inject('actions') @observer class Services extends Component { | 47 | @injectSheet(styles) |
48 | @inject('actions') | ||
49 | @observer | ||
50 | class 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 | |||
200 | export 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 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, injectIntl } from 'react-intl'; |
5 | import ms from 'ms'; | 5 | import ms from 'ms'; |
6 | 6 | ||
7 | import Button from '../../ui/Button'; | 7 | import Button from '../../ui/Button'; |
@@ -9,35 +9,33 @@ import Button from '../../ui/Button'; | |||
9 | const messages = defineMessages({ | 9 | const 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 | ||
28 | export default @observer class WebviewCrashHandler extends Component { | 29 | @observer |
30 | class 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 | |||
84 | export 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 @@ | |||
1 | import { Menu, dialog, app, getCurrentWindow } from '@electron/remote'; | 1 | import { Menu, dialog, app } from '@electron/remote'; |
2 | import React, { Component } from 'react'; | 2 | import React, { Component } from 'react'; |
3 | import { defineMessages, intlShape } from 'react-intl'; | 3 | import { defineMessages, injectIntl } from 'react-intl'; |
4 | import PropTypes from 'prop-types'; | 4 | import PropTypes from 'prop-types'; |
5 | import { observer } from 'mobx-react'; | 5 | import { observer } from 'mobx-react'; |
6 | import classnames from 'classnames'; | 6 | import classnames from 'classnames'; |
@@ -20,56 +20,55 @@ const IS_SERVICE_DEBUGGING_ENABLED = ( | |||
20 | const messages = defineMessages({ | 20 | const 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 | ||
335 | export default SortableElement(TabItem); | 375 | export 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 | ||
5 | import TabBarSortableList from './TabBarSortableList'; | 5 | import TabBarSortableList from './TabBarSortableList'; |
6 | 6 | ||
7 | export default @observer class TabBar extends Component { | 7 | @observer |
8 | class 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 | |||
120 | export default TabBar; | ||