diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/services/content/ServiceWebview.js | 10 | ||||
-rw-r--r-- | src/components/services/content/Services.js | 3 | ||||
-rw-r--r-- | src/components/services/content/WebviewCrashHandler.js | 81 | ||||
-rw-r--r-- | src/containers/layout/AppLayoutContainer.js | 2 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 6 | ||||
-rw-r--r-- | src/models/Service.js | 9 | ||||
-rw-r--r-- | src/styles/button.scss | 108 | ||||
-rw-r--r-- | src/styles/services.scss | 19 | ||||
-rw-r--r-- | src/styles/type.scss | 5 |
9 files changed, 180 insertions, 63 deletions
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 | ||
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 WebviewCrashHandler from './WebviewCrashHandler'; | ||
10 | 11 | ||
11 | @observer | 12 | @observer |
12 | export default class ServiceWebview extends Component { | 13 | export 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 @@ | |||
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 | |||
6 | import Button from '../../ui/Button'; | ||
7 | |||
8 | const 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 | ||
28 | export 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 484252e7c..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) { |
@@ -117,6 +118,14 @@ export default class Service { | |||
117 | frameName, | 118 | frameName, |
118 | options, | 119 | options, |
119 | })); | 120 | })); |
121 | |||
122 | this.webview.addEventListener('did-start-loading', () => { | ||
123 | this.hasCrashed = false; | ||
124 | }); | ||
125 | |||
126 | this.webview.addEventListener('crashed', () => { | ||
127 | this.hasCrashed = true; | ||
128 | }); | ||
120 | } | 129 | } |
121 | 130 | ||
122 | initializeWebViewListener() { | 131 | initializeWebViewListener() { |
diff --git a/src/styles/button.scss b/src/styles/button.scss index c2dd91293..75d2cb1d4 100644 --- a/src/styles/button.scss +++ b/src/styles/button.scss | |||
@@ -1,74 +1,72 @@ | |||
1 | @import './config.scss'; | 1 | @import './config.scss'; |
2 | 2 | ||
3 | .franz-form { | 3 | .franz-form__button { |
4 | .franz-form__button { | 4 | position: relative; |
5 | position: relative; | 5 | background: $theme-brand-primary; |
6 | background: $theme-brand-primary; | 6 | display: block; |
7 | display: block; | 7 | padding: 10px 20px; |
8 | padding: 10px 20px; | 8 | color: #FFF; |
9 | color: #FFF; | 9 | border-radius: 3px; |
10 | border-radius: 3px; | 10 | transition: background 0.5s; |
11 | transition: background 0.5s; | 11 | text-align: center; |
12 | text-align: center; | ||
13 | 12 | ||
14 | &:hover { | 13 | &:hover { |
15 | background: darken($theme-brand-primary, 5%); | 14 | background: darken($theme-brand-primary, 5%); |
16 | } | 15 | } |
17 | 16 | ||
18 | &:active { | 17 | &:active { |
19 | transition: none; | 18 | transition: none; |
20 | background: lighten($theme-brand-primary, 5%); | 19 | background: lighten($theme-brand-primary, 5%); |
21 | } | 20 | } |
22 | 21 | ||
23 | &:disabled { | 22 | &:disabled { |
24 | opacity: 0.2; | 23 | opacity: 0.2; |
25 | } | 24 | } |
26 | 25 | ||
27 | &.franz-form__button--secondary { | 26 | &.franz-form__button--secondary { |
28 | background: $theme-gray-lighter; | 27 | background: $theme-gray-lighter; |
29 | color: $theme-gray; | 28 | color: $theme-gray; |
30 | 29 | ||
31 | &:hover { | 30 | &:hover { |
32 | background: darken($theme-gray-lighter, 5%); | 31 | background: darken($theme-gray-lighter, 5%); |
33 | } | 32 | } |
34 | 33 | ||
35 | &:active { | 34 | &:active { |
36 | background: lighten($theme-gray-lighter, 5%); | 35 | background: lighten($theme-gray-lighter, 5%); |
37 | } | ||
38 | } | 36 | } |
37 | } | ||
39 | 38 | ||
40 | &.franz-form__button--danger { | 39 | &.franz-form__button--danger { |
41 | background: $theme-brand-danger; | 40 | background: $theme-brand-danger; |
42 | 41 | ||
43 | &:hover { | 42 | &:hover { |
44 | background: darken($theme-brand-danger, 5%); | 43 | background: darken($theme-brand-danger, 5%); |
45 | } | 44 | } |
46 | 45 | ||
47 | &:active { | 46 | &:active { |
48 | background: lighten($theme-brand-danger, 5%); | 47 | background: lighten($theme-brand-danger, 5%); |
49 | } | ||
50 | } | 48 | } |
49 | } | ||
51 | 50 | ||
52 | &.franz-form__button--inverted { | 51 | &.franz-form__button--inverted { |
53 | background: none; | 52 | background: none; |
54 | padding: 10px 20px; | 53 | padding: 10px 20px; |
55 | border: 2px solid $theme-brand-primary; | 54 | border: 2px solid $theme-brand-primary; |
56 | color: $theme-brand-primary; | 55 | color: $theme-brand-primary; |
57 | transition: background 0.5s, color 0.5s; | 56 | transition: background 0.5s, color 0.5s; |
58 | 57 | ||
59 | &:hover { | 58 | &:hover { |
60 | background: darken($theme-brand-primary, 5%); | 59 | background: darken($theme-brand-primary, 5%); |
61 | color: #FFF; | 60 | color: #FFF; |
62 | } | ||
63 | } | 61 | } |
62 | } | ||
64 | 63 | ||
65 | .loader { | 64 | .loader { |
66 | position: relative; | 65 | position: relative; |
67 | width: 20px; | 66 | width: 20px; |
68 | height: 12px; | 67 | height: 12px; |
69 | z-index: 9999; | 68 | z-index: 9999; |
70 | display: inline-block; | 69 | display: inline-block; |
71 | margin-right: 5px; | 70 | margin-right: 5px; |
72 | } | ||
73 | } | 71 | } |
74 | } | 72 | } |
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 | ||