diff options
author | 2018-12-08 17:08:58 +0100 | |
---|---|---|
committer | 2018-12-08 17:08:58 +0100 | |
commit | a5e7171402eb27a4527a238d186c88ac03f8ffd7 (patch) | |
tree | 897708a2cfc88569c04f4896bacf2c9d390c7b39 | |
parent | Add service spellchecker language strings (diff) | |
download | ferdium-app-a5e7171402eb27a4527a238d186c88ac03f8ffd7.tar.gz ferdium-app-a5e7171402eb27a4527a238d186c88ac03f8ffd7.tar.zst ferdium-app-a5e7171402eb27a4527a238d186c88ac03f8ffd7.zip |
feat(Service): Add error screen for services that failed to load
-rw-r--r-- | src/components/services/content/ErrorHandlers/WebviewErrorHandler.js | 79 | ||||
-rw-r--r-- | src/components/services/content/ErrorHandlers/styles.js | 25 | ||||
-rw-r--r-- | src/components/services/content/ServiceWebview.js | 18 | ||||
-rw-r--r-- | src/components/services/content/Services.js | 3 | ||||
-rw-r--r-- | src/containers/layout/AppLayoutContainer.js | 1 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 5 | ||||
-rw-r--r-- | src/models/Service.js | 23 |
7 files changed, 154 insertions, 0 deletions
diff --git a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js new file mode 100644 index 000000000..05a64209b --- /dev/null +++ b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js | |||
@@ -0,0 +1,79 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | |||
7 | import Button from '../../../ui/Button'; | ||
8 | |||
9 | import styles from './styles'; | ||
10 | |||
11 | const messages = defineMessages({ | ||
12 | headline: { | ||
13 | id: 'service.errorHandler.headline', | ||
14 | defaultMessage: '!!!Oh no!', | ||
15 | }, | ||
16 | text: { | ||
17 | id: 'service.errorHandler.text', | ||
18 | defaultMessage: '!!!{name} has failed to load.', | ||
19 | }, | ||
20 | action: { | ||
21 | id: 'service.errorHandler.action', | ||
22 | defaultMessage: '!!!Reload {name}', | ||
23 | }, | ||
24 | editAction: { | ||
25 | id: 'service.errorHandler.editAction', | ||
26 | defaultMessage: '!!!Edit {name}', | ||
27 | }, | ||
28 | errorMessage: { | ||
29 | id: 'service.errorHandler.message', | ||
30 | defaultMessage: '!!!Error: {error}', | ||
31 | }, | ||
32 | }); | ||
33 | |||
34 | export default @injectSheet(styles) @observer class WebviewCrashHandler extends Component { | ||
35 | static propTypes = { | ||
36 | name: PropTypes.string.isRequired, | ||
37 | reload: PropTypes.func.isRequired, | ||
38 | edit: PropTypes.func.isRequired, | ||
39 | errorMessage: PropTypes.string.isRequired, | ||
40 | classes: PropTypes.object.isRequired, | ||
41 | }; | ||
42 | |||
43 | static contextTypes = { | ||
44 | intl: intlShape, | ||
45 | }; | ||
46 | |||
47 | render() { | ||
48 | const { | ||
49 | name, | ||
50 | reload, | ||
51 | edit, | ||
52 | errorMessage, | ||
53 | classes, | ||
54 | } = this.props; | ||
55 | const { intl } = this.context; | ||
56 | |||
57 | return ( | ||
58 | <div className={classes.component}> | ||
59 | <h1>{intl.formatMessage(messages.headline)}</h1> | ||
60 | <p>{intl.formatMessage(messages.text, { name })}</p> | ||
61 | <p>{intl.formatMessage(messages.errorMessage, { | ||
62 | error: errorMessage, | ||
63 | })}</p> | ||
64 | <div className={classes.buttonContainer}> | ||
65 | <Button | ||
66 | label={intl.formatMessage(messages.editAction, { name })} | ||
67 | buttonType="inverted" | ||
68 | onClick={() => edit()} | ||
69 | /> | ||
70 | <Button | ||
71 | label={intl.formatMessage(messages.action, { name })} | ||
72 | buttonType="inverted" | ||
73 | onClick={() => reload()} | ||
74 | /> | ||
75 | </div> | ||
76 | </div> | ||
77 | ); | ||
78 | } | ||
79 | } | ||
diff --git a/src/components/services/content/ErrorHandlers/styles.js b/src/components/services/content/ErrorHandlers/styles.js new file mode 100644 index 000000000..f11386798 --- /dev/null +++ b/src/components/services/content/ErrorHandlers/styles.js | |||
@@ -0,0 +1,25 @@ | |||
1 | export default { | ||
2 | component: { | ||
3 | left: 0, | ||
4 | position: 'absolute', | ||
5 | top: 0, | ||
6 | width: '100%', | ||
7 | zIndex: 0, | ||
8 | alignItems: 'center', | ||
9 | // background: $theme-gray-lighter; | ||
10 | display: 'flex', | ||
11 | flexDirection: 'column', | ||
12 | justifyContent: 'center', | ||
13 | textAlign: 'center', | ||
14 | }, | ||
15 | buttonContainer: { | ||
16 | display: 'flex', | ||
17 | flexDirection: 'row', | ||
18 | height: 'auto', | ||
19 | margin: [40, 0, 20], | ||
20 | |||
21 | '& button': { | ||
22 | margin: [0, 10, 0, 10], | ||
23 | }, | ||
24 | }, | ||
25 | }; | ||
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 6e56de92f..b3def3fa5 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -7,7 +7,9 @@ import classnames from 'classnames'; | |||
7 | 7 | ||
8 | import ServiceModel from '../../../models/Service'; | 8 | import ServiceModel from '../../../models/Service'; |
9 | import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; | 9 | import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; |
10 | import WebviewLoader from '../../ui/WebviewLoader'; | ||
10 | import WebviewCrashHandler from './WebviewCrashHandler'; | 11 | import WebviewCrashHandler from './WebviewCrashHandler'; |
12 | import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; | ||
11 | import ServiceDisabled from './ServiceDisabled'; | 13 | import ServiceDisabled from './ServiceDisabled'; |
12 | 14 | ||
13 | export default @observer class ServiceWebview extends Component { | 15 | export default @observer class ServiceWebview extends Component { |
@@ -15,6 +17,7 @@ export default @observer class ServiceWebview extends Component { | |||
15 | service: PropTypes.instanceOf(ServiceModel).isRequired, | 17 | service: PropTypes.instanceOf(ServiceModel).isRequired, |
16 | setWebviewReference: PropTypes.func.isRequired, | 18 | setWebviewReference: PropTypes.func.isRequired, |
17 | reload: PropTypes.func.isRequired, | 19 | reload: PropTypes.func.isRequired, |
20 | edit: PropTypes.func.isRequired, | ||
18 | isAppMuted: PropTypes.bool.isRequired, | 21 | isAppMuted: PropTypes.bool.isRequired, |
19 | enable: PropTypes.func.isRequired, | 22 | enable: PropTypes.func.isRequired, |
20 | }; | 23 | }; |
@@ -58,6 +61,7 @@ export default @observer class ServiceWebview extends Component { | |||
58 | service, | 61 | service, |
59 | setWebviewReference, | 62 | setWebviewReference, |
60 | reload, | 63 | reload, |
64 | edit, | ||
61 | isAppMuted, | 65 | isAppMuted, |
62 | enable, | 66 | enable, |
63 | } = this.props; | 67 | } = this.props; |
@@ -85,6 +89,20 @@ export default @observer class ServiceWebview extends Component { | |||
85 | reload={reload} | 89 | reload={reload} |
86 | /> | 90 | /> |
87 | )} | 91 | )} |
92 | {service.isLoading && ( | ||
93 | <WebviewLoader | ||
94 | loaded={!service.isLoading} | ||
95 | name={service.name} | ||
96 | /> | ||
97 | )} | ||
98 | {service.isError && ( | ||
99 | <WebviewErrorHandler | ||
100 | name={service.recipe.name} | ||
101 | errorMessage={service.errorMessage} | ||
102 | reload={reload} | ||
103 | edit={edit} | ||
104 | /> | ||
105 | )} | ||
88 | {!service.isEnabled ? ( | 106 | {!service.isEnabled ? ( |
89 | <ServiceDisabled | 107 | <ServiceDisabled |
90 | name={service.recipe.name} | 108 | name={service.recipe.name} |
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 4cbd51043..0d4d778cd 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -25,6 +25,7 @@ export default @observer class Services extends Component { | |||
25 | handleIPCMessage: PropTypes.func.isRequired, | 25 | handleIPCMessage: PropTypes.func.isRequired, |
26 | openWindow: PropTypes.func.isRequired, | 26 | openWindow: PropTypes.func.isRequired, |
27 | reload: PropTypes.func.isRequired, | 27 | reload: PropTypes.func.isRequired, |
28 | openSettings: PropTypes.func.isRequired, | ||
28 | isAppMuted: PropTypes.bool.isRequired, | 29 | isAppMuted: PropTypes.bool.isRequired, |
29 | update: PropTypes.func.isRequired, | 30 | update: PropTypes.func.isRequired, |
30 | }; | 31 | }; |
@@ -45,6 +46,7 @@ export default @observer class Services extends Component { | |||
45 | setWebviewReference, | 46 | setWebviewReference, |
46 | openWindow, | 47 | openWindow, |
47 | reload, | 48 | reload, |
49 | openSettings, | ||
48 | isAppMuted, | 50 | isAppMuted, |
49 | update, | 51 | update, |
50 | } = this.props; | 52 | } = this.props; |
@@ -79,6 +81,7 @@ export default @observer class Services extends Component { | |||
79 | setWebviewReference={setWebviewReference} | 81 | setWebviewReference={setWebviewReference} |
80 | openWindow={openWindow} | 82 | openWindow={openWindow} |
81 | reload={() => reload({ serviceId: service.id })} | 83 | reload={() => reload({ serviceId: service.id })} |
84 | edit={() => openSettings({ path: `services/edit/${service.id}` })} | ||
82 | isAppMuted={isAppMuted} | 85 | isAppMuted={isAppMuted} |
83 | enable={() => update({ | 86 | enable={() => update({ |
84 | serviceId: service.id, | 87 | serviceId: service.id, |
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index e1423bdaa..455df10d7 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js | |||
@@ -105,6 +105,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
105 | setWebviewReference={setWebviewReference} | 105 | setWebviewReference={setWebviewReference} |
106 | openWindow={openWindow} | 106 | openWindow={openWindow} |
107 | reload={reload} | 107 | reload={reload} |
108 | openSettings={openSettings} | ||
108 | isAppMuted={settings.all.app.isAppMuted} | 109 | isAppMuted={settings.all.app.isAppMuted} |
109 | update={updateService} | 110 | update={updateService} |
110 | /> | 111 | /> |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 7ccf94a87..17af2b128 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -212,6 +212,11 @@ | |||
212 | "service.crashHandler.text": "{name} has caused an error.", | 212 | "service.crashHandler.text": "{name} has caused an error.", |
213 | "service.crashHandler.action": "Reload {name}", | 213 | "service.crashHandler.action": "Reload {name}", |
214 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", | 214 | "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", |
215 | "service.errorHandler.headline": "Oh no!", | ||
216 | "service.errorHandler.text": "{name} has failed to load.", | ||
217 | "service.errorHandler.message": "Error: {error}", | ||
218 | "service.errorHandler.action": "Reload {name}", | ||
219 | "service.errorHandler.editAction": "Edit {name}", | ||
215 | "service.disabledHandler.headline": "{name} is disabled", | 220 | "service.disabledHandler.headline": "{name} is disabled", |
216 | "service.disabledHandler.action": "Enable {name}", | 221 | "service.disabledHandler.action": "Enable {name}", |
217 | "menu.edit": "Edit", | 222 | "menu.edit": "Edit", |
diff --git a/src/models/Service.js b/src/models/Service.js index deeb544d1..7ba9aa396 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -2,6 +2,8 @@ import { computed, observable, autorun } from 'mobx'; | |||
2 | import path from 'path'; | 2 | import path from 'path'; |
3 | import normalizeUrl from 'normalize-url'; | 3 | import normalizeUrl from 'normalize-url'; |
4 | 4 | ||
5 | const debug = require('debug')('Franz:Service'); | ||
6 | |||
5 | export default class Service { | 7 | export default class Service { |
6 | id = ''; | 8 | id = ''; |
7 | recipe = ''; | 9 | recipe = ''; |
@@ -31,6 +33,11 @@ export default class Service { | |||
31 | @observable isDarkModeEnabled = false; | 33 | @observable isDarkModeEnabled = false; |
32 | @observable spellcheckerLanguage = null; | 34 | @observable spellcheckerLanguage = null; |
33 | 35 | ||
36 | // @observable isFirstNavigation = true; | ||
37 | @observable isLoading = true; | ||
38 | @observable isError = false; | ||
39 | @observable errorMessage = ''; | ||
40 | |||
34 | constructor(data, recipe) { | 41 | constructor(data, recipe) { |
35 | if (!data) { | 42 | if (!data) { |
36 | console.error('Service config not valid'); | 43 | console.error('Service config not valid'); |
@@ -150,9 +157,25 @@ export default class Service { | |||
150 | 157 | ||
151 | this.webview.addEventListener('did-start-loading', () => { | 158 | this.webview.addEventListener('did-start-loading', () => { |
152 | this.hasCrashed = false; | 159 | this.hasCrashed = false; |
160 | this.isLoading = true; | ||
161 | this.isError = false; | ||
162 | }); | ||
163 | |||
164 | this.webview.addEventListener('did-stop-loading', () => { | ||
165 | this.isLoading = false; | ||
166 | }); | ||
167 | |||
168 | this.webview.addEventListener('did-fail-load', (event) => { | ||
169 | debug('Service failed to load', this.name, event); | ||
170 | if (event.isMainFrame) { | ||
171 | this.isError = true; | ||
172 | this.errorMessage = event.errorDescription; | ||
173 | this.isLoading = false; | ||
174 | } | ||
153 | }); | 175 | }); |
154 | 176 | ||
155 | this.webview.addEventListener('crashed', () => { | 177 | this.webview.addEventListener('crashed', () => { |
178 | debug('Service crashed', this.name); | ||
156 | this.hasCrashed = true; | 179 | this.hasCrashed = true; |
157 | }); | 180 | }); |
158 | } | 181 | } |