aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2017-11-07 15:29:31 +0100
committerLibravatar Stefan Malzner <stefan@adlk.io>2017-11-07 15:29:31 +0100
commitdd307e3deb14f1738029ea38f7c1de7893455283 (patch)
tree9e1148d98476f864903900e819b509dfe12a7f14
parentRemove cumbersome style nesting (diff)
downloadferdium-app-dd307e3deb14f1738029ea38f7c1de7893455283.tar.gz
ferdium-app-dd307e3deb14f1738029ea38f7c1de7893455283.tar.zst
ferdium-app-dd307e3deb14f1738029ea38f7c1de7893455283.zip
feature(Service): Add webview crash handler to display a user friendly message
-rw-r--r--.vscode/launch.json13
-rw-r--r--.vscode/tasks.json18
-rw-r--r--src/components/services/content/ServiceWebview.js10
-rw-r--r--src/components/services/content/Services.js3
-rw-r--r--src/components/services/content/WebviewCrashHandler.js81
-rw-r--r--src/containers/layout/AppLayoutContainer.js2
-rw-r--r--src/i18n/locales/en-US.json6
-rw-r--r--src/models/Service.js15
-rw-r--r--src/styles/services.scss19
-rw-r--r--src/styles/type.scss5
10 files changed, 156 insertions, 16 deletions
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 @@
1{
2 "version": "0.2.0",
3 "configurations": [
4 {
5 "type": "node",
6 "request": "launch",
7 "name": "Electron Main",
8 "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
9 "program": "${workspaceFolder}/build/index.js",
10 "protocol": "inspector"
11 }
12 ]
13} \ 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 @@
1{
2 "version": "2.0.0",
3 "tasks": [
4 {
5 "type": "npm",
6 "script": "dev",
7 "group": {
8 "kind": "build",
9 "isDefault": true
10 }
11 },
12 {
13 "type": "npm",
14 "script": "lint",
15 "group": "test"
16 }
17 ]
18} \ 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';
7 7
8import ServiceModel from '../../../models/Service'; 8import ServiceModel from '../../../models/Service';
9import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; 9import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl';
10import WebviewCrashHandler from './WebviewCrashHandler';
10 11
11@observer 12@observer
12export default class ServiceWebview extends Component { 13export default class ServiceWebview extends Component {
13 static propTypes = { 14 static propTypes = {
14 service: PropTypes.instanceOf(ServiceModel).isRequired, 15 service: PropTypes.instanceOf(ServiceModel).isRequired,
15 setWebviewReference: PropTypes.func.isRequired, 16 setWebviewReference: PropTypes.func.isRequired,
17 reload: PropTypes.func.isRequired,
16 }; 18 };
17 19
18 static defaultProps = { 20 static defaultProps = {
@@ -53,6 +55,7 @@ export default class ServiceWebview extends Component {
53 const { 55 const {
54 service, 56 service,
55 setWebviewReference, 57 setWebviewReference,
58 reload,
56 } = this.props; 59 } = this.props;
57 60
58 const webviewClasses = classnames({ 61 const webviewClasses = classnames({
@@ -70,6 +73,13 @@ export default class ServiceWebview extends Component {
70 73
71 return ( 74 return (
72 <div className={webviewClasses}> 75 <div className={webviewClasses}>
76 {service.hasCrashed && (
77 <WebviewCrashHandler
78 name={service.recipe.name}
79 webview={service.webview}
80 reload={reload}
81 />
82 )}
73 <Webview 83 <Webview
74 ref={(element) => { this.webview = element; }} 84 ref={(element) => { this.webview = element; }}
75 85
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 {
25 setWebviewReference: PropTypes.func.isRequired, 25 setWebviewReference: PropTypes.func.isRequired,
26 handleIPCMessage: PropTypes.func.isRequired, 26 handleIPCMessage: PropTypes.func.isRequired,
27 openWindow: PropTypes.func.isRequired, 27 openWindow: PropTypes.func.isRequired,
28 reload: PropTypes.func.isRequired,
28 }; 29 };
29 30
30 static defaultProps = { 31 static defaultProps = {
@@ -42,6 +43,7 @@ export default class Services extends Component {
42 handleIPCMessage, 43 handleIPCMessage,
43 setWebviewReference, 44 setWebviewReference,
44 openWindow, 45 openWindow,
46 reload,
45 } = this.props; 47 } = this.props;
46 const { intl } = this.context; 48 const { intl } = this.context;
47 49
@@ -73,6 +75,7 @@ export default class Services extends Component {
73 handleIPCMessage={handleIPCMessage} 75 handleIPCMessage={handleIPCMessage}
74 setWebviewReference={setWebviewReference} 76 setWebviewReference={setWebviewReference}
75 openWindow={openWindow} 77 openWindow={openWindow}
78 reload={() => reload({ serviceId: service.id })}
76 /> 79 />
77 ))} 80 ))}
78 </div> 81 </div>
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 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import Button from '../../ui/Button';
7
8const messages = defineMessages({
9 headline: {
10 id: 'service.crashHandler.headline',
11 defaultMessage: '!!!Oh no!',
12 },
13 text: {
14 id: 'service.crashHandler.text',
15 defaultMessage: '!!!{name} has caused an error.',
16 },
17 action: {
18 id: 'service.crashHandler.action',
19 defaultMessage: '!!!Reload {name}',
20 },
21 autoReload: {
22 id: 'service.crashHandler.autoReload',
23 defaultMessage: '!!!Trying to automatically restore {name} in {seconds} seconds',
24 },
25});
26
27@observer
28export default class ServiceWebview extends Component {
29 static propTypes = {
30 name: PropTypes.string.isRequired,
31 reload: PropTypes.func.isRequired,
32 };
33
34 static contextTypes = {
35 intl: intlShape,
36 };
37
38 state = {
39 countdown: 10000,
40 }
41
42 componentDidMount() {
43 const { reload } = this.props;
44
45 this.countdownInterval = setInterval(() => {
46 this.setState({
47 countdown: this.state.countdown - this.countdownIntervalTimeout,
48 });
49
50 if (this.state.countdown <= 0) {
51 reload();
52 clearInterval(this.countdownInterval);
53 }
54 }, this.countdownIntervalTimeout);
55 }
56
57 countdownInterval = null;
58 countdownIntervalTimeout = 1000;
59
60 render() {
61 const { name, reload } = this.props;
62 const { intl } = this.context;
63
64 return (
65 <div className="services__crash-handler">
66 <h1>{intl.formatMessage(messages.headline)}</h1>
67 <p>{intl.formatMessage(messages.text, { name })}</p>
68 <Button
69 // label={`Reload ${name}`}
70 label={intl.formatMessage(messages.action, { name })}
71 buttonType="inverted"
72 onClick={() => reload()}
73 />
74 <p className="footnote">{intl.formatMessage(messages.autoReload, {
75 name,
76 seconds: this.state.countdown / 1000,
77 })}</p>
78 </div>
79 );
80 }
81}
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 {
92 92
93 const servicesContainer = ( 93 const servicesContainer = (
94 <Services 94 <Services
95 // settings={allSettings}
96 services={allServices} 95 services={allServices}
97 handleIPCMessage={handleIPCMessage} 96 handleIPCMessage={handleIPCMessage}
98 setWebviewReference={setWebviewReference} 97 setWebviewReference={setWebviewReference}
99 openWindow={openWindow} 98 openWindow={openWindow}
99 reload={reload}
100 /> 100 />
101 ); 101 );
102 102
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 @@
165 "tabs.item.disableNotifications": "Disable notifications", 165 "tabs.item.disableNotifications": "Disable notifications",
166 "tabs.item.enableNotification": "Enable notifications", 166 "tabs.item.enableNotification": "Enable notifications",
167 "tabs.item.disableService": "Disable service", 167 "tabs.item.disableService": "Disable service",
168 "tabs.item.deleteService": "Delete service" 168 "tabs.item.deleteService": "Delete service",
169 "service.crashHandler.headline": "Oh no!",
170 "service.crashHandler.text": "{name} has caused an error.",
171 "service.crashHandler.action": "Reload {name}",
172 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds"
169} 173}
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 {
23 @observable isNotificationEnabled = true; 23 @observable isNotificationEnabled = true;
24 @observable isIndirectMessageBadgeEnabled = true; 24 @observable isIndirectMessageBadgeEnabled = true;
25 @observable customIconUrl = ''; 25 @observable customIconUrl = '';
26 @observable hasCrashed = false;
26 27
27 constructor(data, recipe) { 28 constructor(data, recipe) {
28 if (!data) { 29 if (!data) {
@@ -118,14 +119,12 @@ export default class Service {
118 options, 119 options,
119 })); 120 }));
120 121
121 this.webview.addEventListener('crashed', (e) => { 122 this.webview.addEventListener('did-start-loading', () => {
122 console.log(e); 123 this.hasCrashed = false;
123 const reload = window.confirm('Service crashed. Reload?'); // eslint-disable-line no-alert 124 });
124 if (reload) { 125
125 store.actions.service.reload({ 126 this.webview.addEventListener('crashed', () => {
126 serviceId: this.id, 127 this.hasCrashed = true;
127 });
128 }
129 }); 128 });
130 } 129 }
131 130
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 @@
8 background: #FFF; 8 background: #FFF;
9 order: 5; 9 order: 5;
10 10
11 .services__webview { 11 .services__webview,
12 .services__crash-handler {
12 position: absolute; 13 position: absolute;
13 width: 100%; 14 width: 100%;
14 top: 0; 15 top: 0;
@@ -38,7 +39,8 @@
38 } 39 }
39 } 40 }
40 41
41 .services__no-service { 42 .services__no-service,
43 .services__crash-handler {
42 display: flex; 44 display: flex;
43 flex-direction: column; 45 flex-direction: column;
44 justify-content: center; 46 justify-content: center;
@@ -51,10 +53,15 @@
51 color: $theme-gray-dark; 53 color: $theme-gray-dark;
52 } 54 }
53 55
54 a.button { 56 a.button,
55 margin-top: 40px; 57 button {
56 // color: #FFF; 58 margin: 40px 0 20px;
57 // border-color: #FFF;
58 } 59 }
59 } 60 }
61
62 .services__crash-handler {
63 position: absolut;
64 z-index: 110;
65 }
66
60} 67}
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 {
71.label { 71.label {
72 @include formLabel(); 72 @include formLabel();
73} 73}
74
75.footnote {
76 font-size: 12px;
77 color: $theme-gray-light;
78} \ No newline at end of file