From dd307e3deb14f1738029ea38f7c1de7893455283 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Tue, 7 Nov 2017 15:29:31 +0100 Subject: feature(Service): Add webview crash handler to display a user friendly message --- .vscode/launch.json | 13 ++++ .vscode/tasks.json | 18 +++++ src/components/services/content/ServiceWebview.js | 10 +++ src/components/services/content/Services.js | 3 + .../services/content/WebviewCrashHandler.js | 81 ++++++++++++++++++++++ src/containers/layout/AppLayoutContainer.js | 2 +- src/i18n/locales/en-US.json | 6 +- src/models/Service.js | 15 ++-- src/styles/services.scss | 19 +++-- src/styles/type.scss | 5 ++ 10 files changed, 156 insertions(+), 16 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 src/components/services/content/WebviewCrashHandler.js diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..7d14571f5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Electron Main", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", + "program": "${workspaceFolder}/build/index.js", + "protocol": "inspector" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..a9fb57b06 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "dev", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "type": "npm", + "script": "lint", + "group": "test" + } + ] +} \ No newline at end of file diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index 3ee3155be..cd59e0a8a 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js @@ -7,12 +7,14 @@ import classnames from 'classnames'; import ServiceModel from '../../../models/Service'; import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; +import WebviewCrashHandler from './WebviewCrashHandler'; @observer export default class ServiceWebview extends Component { static propTypes = { service: PropTypes.instanceOf(ServiceModel).isRequired, setWebviewReference: PropTypes.func.isRequired, + reload: PropTypes.func.isRequired, }; static defaultProps = { @@ -53,6 +55,7 @@ export default class ServiceWebview extends Component { const { service, setWebviewReference, + reload, } = this.props; const webviewClasses = classnames({ @@ -70,6 +73,13 @@ export default class ServiceWebview extends Component { return (
+ {service.hasCrashed && ( + + )} { this.webview = element; }} diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 03c68b06f..bad525d22 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js @@ -25,6 +25,7 @@ export default class Services extends Component { setWebviewReference: PropTypes.func.isRequired, handleIPCMessage: PropTypes.func.isRequired, openWindow: PropTypes.func.isRequired, + reload: PropTypes.func.isRequired, }; static defaultProps = { @@ -42,6 +43,7 @@ export default class Services extends Component { handleIPCMessage, setWebviewReference, openWindow, + reload, } = this.props; const { intl } = this.context; @@ -73,6 +75,7 @@ export default class Services extends Component { handleIPCMessage={handleIPCMessage} setWebviewReference={setWebviewReference} openWindow={openWindow} + reload={() => reload({ serviceId: service.id })} /> ))}
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js new file mode 100644 index 000000000..24903f3c5 --- /dev/null +++ b/src/components/services/content/WebviewCrashHandler.js @@ -0,0 +1,81 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; + +import Button from '../../ui/Button'; + +const messages = defineMessages({ + headline: { + id: 'service.crashHandler.headline', + defaultMessage: '!!!Oh no!', + }, + text: { + id: 'service.crashHandler.text', + defaultMessage: '!!!{name} has caused an error.', + }, + action: { + id: 'service.crashHandler.action', + defaultMessage: '!!!Reload {name}', + }, + autoReload: { + id: 'service.crashHandler.autoReload', + defaultMessage: '!!!Trying to automatically restore {name} in {seconds} seconds', + }, +}); + +@observer +export default class ServiceWebview extends Component { + static propTypes = { + name: PropTypes.string.isRequired, + reload: PropTypes.func.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + state = { + countdown: 10000, + } + + componentDidMount() { + const { reload } = this.props; + + this.countdownInterval = setInterval(() => { + this.setState({ + countdown: this.state.countdown - this.countdownIntervalTimeout, + }); + + if (this.state.countdown <= 0) { + reload(); + clearInterval(this.countdownInterval); + } + }, this.countdownIntervalTimeout); + } + + countdownInterval = null; + countdownIntervalTimeout = 1000; + + render() { + const { name, reload } = this.props; + const { intl } = this.context; + + return ( +
+

{intl.formatMessage(messages.headline)}

+

{intl.formatMessage(messages.text, { name })}

+
+ ); + } +} diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index aa7f7952a..68ad1039e 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -92,11 +92,11 @@ export default class AppLayoutContainer extends Component { const servicesContainer = ( ); diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index b1d260f0a..0493b547f 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -165,5 +165,9 @@ "tabs.item.disableNotifications": "Disable notifications", "tabs.item.enableNotification": "Enable notifications", "tabs.item.disableService": "Disable service", - "tabs.item.deleteService": "Delete service" + "tabs.item.deleteService": "Delete service", + "service.crashHandler.headline": "Oh no!", + "service.crashHandler.text": "{name} has caused an error.", + "service.crashHandler.action": "Reload {name}", + "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds" } diff --git a/src/models/Service.js b/src/models/Service.js index c7276821a..dc53807f7 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -23,6 +23,7 @@ export default class Service { @observable isNotificationEnabled = true; @observable isIndirectMessageBadgeEnabled = true; @observable customIconUrl = ''; + @observable hasCrashed = false; constructor(data, recipe) { if (!data) { @@ -118,14 +119,12 @@ export default class Service { options, })); - this.webview.addEventListener('crashed', (e) => { - console.log(e); - const reload = window.confirm('Service crashed. Reload?'); // eslint-disable-line no-alert - if (reload) { - store.actions.service.reload({ - serviceId: this.id, - }); - } + this.webview.addEventListener('did-start-loading', () => { + this.hasCrashed = false; + }); + + this.webview.addEventListener('crashed', () => { + this.hasCrashed = true; }); } diff --git a/src/styles/services.scss b/src/styles/services.scss index 3347ea9d7..95738f123 100644 --- a/src/styles/services.scss +++ b/src/styles/services.scss @@ -8,7 +8,8 @@ background: #FFF; order: 5; - .services__webview { + .services__webview, + .services__crash-handler { position: absolute; width: 100%; top: 0; @@ -38,7 +39,8 @@ } } - .services__no-service { + .services__no-service, + .services__crash-handler { display: flex; flex-direction: column; justify-content: center; @@ -51,10 +53,15 @@ color: $theme-gray-dark; } - a.button { - margin-top: 40px; - // color: #FFF; - // border-color: #FFF; + a.button, + button { + margin: 40px 0 20px; } } + + .services__crash-handler { + position: absolut; + z-index: 110; + } + } diff --git a/src/styles/type.scss b/src/styles/type.scss index 935a36f4b..cacbec482 100644 --- a/src/styles/type.scss +++ b/src/styles/type.scss @@ -71,3 +71,8 @@ a { .label { @include formLabel(); } + +.footnote { + font-size: 12px; + color: $theme-gray-light; +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf