diff options
author | 2019-03-08 15:38:17 +0100 | |
---|---|---|
committer | 2019-03-08 15:38:17 +0100 | |
commit | e1a9125e1f783894b1f712565a59b6bf6ef46a29 (patch) | |
tree | 3c35ca77560c2b011e64f9ef662d59d38198f01e | |
parent | fix merge conflicts with latest develop (diff) | |
parent | disable no-param-reassign eslint rule (diff) | |
download | ferdium-app-e1a9125e1f783894b1f712565a59b6bf6ef46a29.tar.gz ferdium-app-e1a9125e1f783894b1f712565a59b6bf6ef46a29.tar.zst ferdium-app-e1a9125e1f783894b1f712565a59b6bf6ef46a29.zip |
merge-in webview unmounting fix
-rw-r--r-- | .eslintrc | 1 | ||||
-rw-r--r-- | src/actions/service.js | 4 | ||||
-rw-r--r-- | src/components/services/content/ServiceView.js | 136 | ||||
-rw-r--r-- | src/components/services/content/ServiceWebview.js | 145 | ||||
-rw-r--r-- | src/components/services/content/Services.js | 7 | ||||
-rw-r--r-- | src/containers/layout/AppLayoutContainer.js | 3 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 6 |
7 files changed, 180 insertions, 122 deletions
@@ -3,6 +3,7 @@ | |||
3 | "extends": "eslint-config-airbnb", | 3 | "extends": "eslint-config-airbnb", |
4 | "rules": { | 4 | "rules": { |
5 | "consistent-return": 0, | 5 | "consistent-return": 0, |
6 | "no-param-reassign": 0, | ||
6 | "import/extensions": 0, | 7 | "import/extensions": 0, |
7 | "import/no-extraneous-dependencies": 0, | 8 | "import/no-extraneous-dependencies": 0, |
8 | "import/no-unresolved": [2, { | 9 | "import/no-unresolved": [2, { |
diff --git a/src/actions/service.js b/src/actions/service.js index 5d483b12a..ceaabc31e 100644 --- a/src/actions/service.js +++ b/src/actions/service.js | |||
@@ -1,4 +1,5 @@ | |||
1 | import PropTypes from 'prop-types'; | 1 | import PropTypes from 'prop-types'; |
2 | import ServiceModel from '../models/Service'; | ||
2 | 3 | ||
3 | export default { | 4 | export default { |
4 | setActive: { | 5 | setActive: { |
@@ -36,6 +37,9 @@ export default { | |||
36 | serviceId: PropTypes.string.isRequired, | 37 | serviceId: PropTypes.string.isRequired, |
37 | webview: PropTypes.object.isRequired, | 38 | webview: PropTypes.object.isRequired, |
38 | }, | 39 | }, |
40 | detachService: { | ||
41 | service: PropTypes.instanceOf(ServiceModel).isRequired, | ||
42 | }, | ||
39 | focusService: { | 43 | focusService: { |
40 | serviceId: PropTypes.string.isRequired, | 44 | serviceId: PropTypes.string.isRequired, |
41 | }, | 45 | }, |
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js new file mode 100644 index 000000000..5afc54f9d --- /dev/null +++ b/src/components/services/content/ServiceView.js | |||
@@ -0,0 +1,136 @@ | |||
1 | import React, { Component, Fragment } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { autorun } from 'mobx'; | ||
4 | import { observer } from 'mobx-react'; | ||
5 | import classnames from 'classnames'; | ||
6 | |||
7 | import ServiceModel from '../../../models/Service'; | ||
8 | import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; | ||
9 | import WebviewLoader from '../../ui/WebviewLoader'; | ||
10 | import WebviewCrashHandler from './WebviewCrashHandler'; | ||
11 | import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; | ||
12 | import ServiceDisabled from './ServiceDisabled'; | ||
13 | import ServiceWebview from './ServiceWebview'; | ||
14 | |||
15 | export default @observer class ServiceView extends Component { | ||
16 | static propTypes = { | ||
17 | service: PropTypes.instanceOf(ServiceModel).isRequired, | ||
18 | setWebviewReference: PropTypes.func.isRequired, | ||
19 | detachService: PropTypes.func.isRequired, | ||
20 | reload: PropTypes.func.isRequired, | ||
21 | edit: PropTypes.func.isRequired, | ||
22 | enable: PropTypes.func.isRequired, | ||
23 | isActive: PropTypes.bool, | ||
24 | }; | ||
25 | |||
26 | static defaultProps = { | ||
27 | isActive: false, | ||
28 | }; | ||
29 | |||
30 | state = { | ||
31 | forceRepaint: false, | ||
32 | targetUrl: '', | ||
33 | statusBarVisible: false, | ||
34 | }; | ||
35 | |||
36 | autorunDisposer = null; | ||
37 | |||
38 | componentDidMount() { | ||
39 | this.autorunDisposer = autorun(() => { | ||
40 | if (this.props.service.isActive) { | ||
41 | this.setState({ forceRepaint: true }); | ||
42 | setTimeout(() => { | ||
43 | this.setState({ forceRepaint: false }); | ||
44 | }, 100); | ||
45 | } | ||
46 | }); | ||
47 | } | ||
48 | |||
49 | componentWillUnmount() { | ||
50 | this.autorunDisposer(); | ||
51 | } | ||
52 | |||
53 | updateTargetUrl = (event) => { | ||
54 | let visible = true; | ||
55 | if (event.url === '' || event.url === '#') { | ||
56 | visible = false; | ||
57 | } | ||
58 | this.setState({ | ||
59 | targetUrl: event.url, | ||
60 | statusBarVisible: visible, | ||
61 | }); | ||
62 | }; | ||
63 | |||
64 | render() { | ||
65 | const { | ||
66 | detachService, | ||
67 | service, | ||
68 | setWebviewReference, | ||
69 | reload, | ||
70 | edit, | ||
71 | enable, | ||
72 | } = this.props; | ||
73 | |||
74 | const webviewClasses = classnames({ | ||
75 | services__webview: true, | ||
76 | 'services__webview-wrapper': true, | ||
77 | 'is-active': service.isActive, | ||
78 | 'services__webview--force-repaint': this.state.forceRepaint, | ||
79 | }); | ||
80 | |||
81 | let statusBar = null; | ||
82 | if (this.state.statusBarVisible) { | ||
83 | statusBar = ( | ||
84 | <StatusBarTargetUrl text={this.state.targetUrl} /> | ||
85 | ); | ||
86 | } | ||
87 | |||
88 | return ( | ||
89 | <div className={webviewClasses}> | ||
90 | {service.isActive && service.isEnabled && ( | ||
91 | <Fragment> | ||
92 | {service.hasCrashed && ( | ||
93 | <WebviewCrashHandler | ||
94 | name={service.recipe.name} | ||
95 | webview={service.webview} | ||
96 | reload={reload} | ||
97 | /> | ||
98 | )} | ||
99 | {service.isEnabled && service.isLoading && service.isFirstLoad && ( | ||
100 | <WebviewLoader | ||
101 | loaded={false} | ||
102 | name={service.name} | ||
103 | /> | ||
104 | )} | ||
105 | {service.isError && ( | ||
106 | <WebviewErrorHandler | ||
107 | name={service.recipe.name} | ||
108 | errorMessage={service.errorMessage} | ||
109 | reload={reload} | ||
110 | edit={edit} | ||
111 | /> | ||
112 | )} | ||
113 | </Fragment> | ||
114 | )} | ||
115 | {!service.isEnabled ? ( | ||
116 | <Fragment> | ||
117 | {service.isActive && ( | ||
118 | <ServiceDisabled | ||
119 | name={service.recipe.name} | ||
120 | webview={service.webview} | ||
121 | enable={enable} | ||
122 | /> | ||
123 | )} | ||
124 | </Fragment> | ||
125 | ) : ( | ||
126 | <ServiceWebview | ||
127 | service={service} | ||
128 | setWebviewReference={setWebviewReference} | ||
129 | detachService={detachService} | ||
130 | /> | ||
131 | )} | ||
132 | {statusBar} | ||
133 | </div> | ||
134 | ); | ||
135 | } | ||
136 | } | ||
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index bb577e4cc..7252c695f 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -1,145 +1,50 @@ | |||
1 | import React, { Component, Fragment } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { autorun } from 'mobx'; | ||
4 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
5 | import Webview from 'react-electron-web-view'; | 4 | import ElectronWebView from 'react-electron-web-view'; |
6 | import classnames from 'classnames'; | ||
7 | 5 | ||
8 | import ServiceModel from '../../../models/Service'; | 6 | import ServiceModel from '../../../models/Service'; |
9 | import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; | ||
10 | import WebviewLoader from '../../ui/WebviewLoader'; | ||
11 | import WebviewCrashHandler from './WebviewCrashHandler'; | ||
12 | import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; | ||
13 | import ServiceDisabled from './ServiceDisabled'; | ||
14 | 7 | ||
15 | export default @observer class ServiceWebview extends Component { | 8 | @observer |
9 | class ServiceWebview extends Component { | ||
16 | static propTypes = { | 10 | static propTypes = { |
17 | service: PropTypes.instanceOf(ServiceModel).isRequired, | 11 | service: PropTypes.instanceOf(ServiceModel).isRequired, |
18 | setWebviewReference: PropTypes.func.isRequired, | 12 | setWebviewReference: PropTypes.func.isRequired, |
19 | reload: PropTypes.func.isRequired, | 13 | detachService: PropTypes.func.isRequired, |
20 | edit: PropTypes.func.isRequired, | ||
21 | enable: PropTypes.func.isRequired, | ||
22 | isActive: PropTypes.bool, | ||
23 | }; | 14 | }; |
24 | 15 | ||
25 | static defaultProps = { | ||
26 | isActive: false, | ||
27 | }; | ||
28 | |||
29 | state = { | ||
30 | forceRepaint: false, | ||
31 | targetUrl: '', | ||
32 | statusBarVisible: false, | ||
33 | }; | ||
34 | |||
35 | autorunDisposer = null; | ||
36 | |||
37 | webview = null; | 16 | webview = null; |
38 | 17 | ||
39 | componentDidMount() { | ||
40 | this.autorunDisposer = autorun(() => { | ||
41 | if (this.props.service.isActive) { | ||
42 | this.setState({ forceRepaint: true }); | ||
43 | setTimeout(() => { | ||
44 | this.setState({ forceRepaint: false }); | ||
45 | }, 100); | ||
46 | } | ||
47 | }); | ||
48 | } | ||
49 | |||
50 | componentWillUnmount() { | 18 | componentWillUnmount() { |
51 | this.autorunDisposer(); | 19 | const { service, detachService } = this.props; |
52 | } | 20 | detachService({ service }); |
53 | |||
54 | updateTargetUrl = (event) => { | ||
55 | let visible = true; | ||
56 | if (event.url === '' || event.url === '#') { | ||
57 | visible = false; | ||
58 | } | ||
59 | this.setState({ | ||
60 | targetUrl: event.url, | ||
61 | statusBarVisible: visible, | ||
62 | }); | ||
63 | } | 21 | } |
64 | 22 | ||
65 | render() { | 23 | render() { |
66 | const { | 24 | const { |
67 | service, | 25 | service, |
68 | setWebviewReference, | 26 | setWebviewReference, |
69 | reload, | ||
70 | edit, | ||
71 | enable, | ||
72 | } = this.props; | 27 | } = this.props; |
73 | 28 | ||
74 | const webviewClasses = classnames({ | ||
75 | services__webview: true, | ||
76 | 'services__webview-wrapper': true, | ||
77 | 'is-active': service.isActive, | ||
78 | 'services__webview--force-repaint': this.state.forceRepaint, | ||
79 | }); | ||
80 | |||
81 | let statusBar = null; | ||
82 | if (this.state.statusBarVisible) { | ||
83 | statusBar = ( | ||
84 | <StatusBarTargetUrl text={this.state.targetUrl} /> | ||
85 | ); | ||
86 | } | ||
87 | |||
88 | return ( | 29 | return ( |
89 | <div className={webviewClasses}> | 30 | <ElectronWebView |
90 | {service.isActive && service.isEnabled && ( | 31 | ref={(webview) => { this.webview = webview; }} |
91 | <Fragment> | 32 | autosize |
92 | {service.hasCrashed && ( | 33 | src={service.url} |
93 | <WebviewCrashHandler | 34 | preload="./webview/recipe.js" |
94 | name={service.recipe.name} | 35 | partition={`persist:service-${service.id}`} |
95 | webview={service.webview} | 36 | onDidAttach={() => { |
96 | reload={reload} | 37 | setWebviewReference({ |
97 | /> | 38 | serviceId: service.id, |
98 | )} | 39 | webview: this.webview.view, |
99 | {service.isEnabled && service.isLoading && service.isFirstLoad && ( | 40 | }); |
100 | <WebviewLoader | 41 | }} |
101 | loaded={false} | 42 | onUpdateTargetUrl={this.updateTargetUrl} |
102 | name={service.name} | 43 | useragent={service.userAgent} |
103 | /> | 44 | allowpopups |
104 | )} | 45 | /> |
105 | {service.isError && ( | ||
106 | <WebviewErrorHandler | ||
107 | name={service.recipe.name} | ||
108 | errorMessage={service.errorMessage} | ||
109 | reload={reload} | ||
110 | edit={edit} | ||
111 | /> | ||
112 | )} | ||
113 | </Fragment> | ||
114 | )} | ||
115 | {!service.isEnabled ? ( | ||
116 | <Fragment> | ||
117 | {service.isActive && ( | ||
118 | <ServiceDisabled | ||
119 | name={service.recipe.name} | ||
120 | webview={service.webview} | ||
121 | enable={enable} | ||
122 | /> | ||
123 | )} | ||
124 | </Fragment> | ||
125 | ) : ( | ||
126 | <Webview | ||
127 | ref={(element) => { this.webview = element; }} | ||
128 | autosize | ||
129 | src={service.url} | ||
130 | preload="./webview/recipe.js" | ||
131 | partition={`persist:service-${service.id}`} | ||
132 | onDidAttach={() => setWebviewReference({ | ||
133 | serviceId: service.id, | ||
134 | webview: this.webview.view, | ||
135 | })} | ||
136 | onUpdateTargetUrl={this.updateTargetUrl} | ||
137 | useragent={service.userAgent} | ||
138 | allowpopups | ||
139 | /> | ||
140 | )} | ||
141 | {statusBar} | ||
142 | </div> | ||
143 | ); | 46 | ); |
144 | } | 47 | } |
145 | } | 48 | } |
49 | |||
50 | export default ServiceWebview; | ||
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 54f16ba12..8f8c38a11 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -4,7 +4,7 @@ import { observer, PropTypes as MobxPropTypes } 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, intlShape } from 'react-intl'; |
6 | 6 | ||
7 | import Webview from './ServiceWebview'; | 7 | import ServiceView from './ServiceView'; |
8 | import Appear from '../../ui/effects/Appear'; | 8 | import Appear from '../../ui/effects/Appear'; |
9 | 9 | ||
10 | const messages = defineMessages({ | 10 | const messages = defineMessages({ |
@@ -22,6 +22,7 @@ export default @observer class Services extends Component { | |||
22 | static propTypes = { | 22 | static propTypes = { |
23 | services: MobxPropTypes.arrayOrObservableArray, | 23 | services: MobxPropTypes.arrayOrObservableArray, |
24 | setWebviewReference: PropTypes.func.isRequired, | 24 | setWebviewReference: PropTypes.func.isRequired, |
25 | detachService: PropTypes.func.isRequired, | ||
25 | handleIPCMessage: PropTypes.func.isRequired, | 26 | handleIPCMessage: PropTypes.func.isRequired, |
26 | openWindow: PropTypes.func.isRequired, | 27 | openWindow: PropTypes.func.isRequired, |
27 | reload: PropTypes.func.isRequired, | 28 | reload: PropTypes.func.isRequired, |
@@ -42,6 +43,7 @@ export default @observer class Services extends Component { | |||
42 | services, | 43 | services, |
43 | handleIPCMessage, | 44 | handleIPCMessage, |
44 | setWebviewReference, | 45 | setWebviewReference, |
46 | detachService, | ||
45 | openWindow, | 47 | openWindow, |
46 | reload, | 48 | reload, |
47 | openSettings, | 49 | openSettings, |
@@ -71,11 +73,12 @@ export default @observer class Services extends Component { | |||
71 | </Appear> | 73 | </Appear> |
72 | )} | 74 | )} |
73 | {services.map(service => ( | 75 | {services.map(service => ( |
74 | <Webview | 76 | <ServiceView |
75 | key={service.id} | 77 | key={service.id} |
76 | service={service} | 78 | service={service} |
77 | handleIPCMessage={handleIPCMessage} | 79 | handleIPCMessage={handleIPCMessage} |
78 | setWebviewReference={setWebviewReference} | 80 | setWebviewReference={setWebviewReference} |
81 | detachService={detachService} | ||
79 | openWindow={openWindow} | 82 | openWindow={openWindow} |
80 | reload={() => reload({ serviceId: service.id })} | 83 | reload={() => reload({ serviceId: service.id })} |
81 | edit={() => openSettings({ path: `services/edit/${service.id}` })} | 84 | edit={() => openSettings({ path: `services/edit/${service.id}` })} |
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 749912c59..5a05ce431 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js | |||
@@ -42,6 +42,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
42 | setActive, | 42 | setActive, |
43 | handleIPCMessage, | 43 | handleIPCMessage, |
44 | setWebviewReference, | 44 | setWebviewReference, |
45 | detachService, | ||
45 | openWindow, | 46 | openWindow, |
46 | reorder, | 47 | reorder, |
47 | reload, | 48 | reload, |
@@ -105,6 +106,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
105 | services={services.allDisplayedUnordered} | 106 | services={services.allDisplayedUnordered} |
106 | handleIPCMessage={handleIPCMessage} | 107 | handleIPCMessage={handleIPCMessage} |
107 | setWebviewReference={setWebviewReference} | 108 | setWebviewReference={setWebviewReference} |
109 | detachService={detachService} | ||
108 | openWindow={openWindow} | 110 | openWindow={openWindow} |
109 | reload={reload} | 111 | reload={reload} |
110 | openSettings={openSettings} | 112 | openSettings={openSettings} |
@@ -160,6 +162,7 @@ AppLayoutContainer.wrappedComponent.propTypes = { | |||
160 | toggleAudio: PropTypes.func.isRequired, | 162 | toggleAudio: PropTypes.func.isRequired, |
161 | handleIPCMessage: PropTypes.func.isRequired, | 163 | handleIPCMessage: PropTypes.func.isRequired, |
162 | setWebviewReference: PropTypes.func.isRequired, | 164 | setWebviewReference: PropTypes.func.isRequired, |
165 | detachService: PropTypes.func.isRequired, | ||
163 | openWindow: PropTypes.func.isRequired, | 166 | openWindow: PropTypes.func.isRequired, |
164 | reloadUpdatedServices: PropTypes.func.isRequired, | 167 | reloadUpdatedServices: PropTypes.func.isRequired, |
165 | updateService: PropTypes.func.isRequired, | 168 | updateService: PropTypes.func.isRequired, |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index da4b19c0d..cc8eed65b 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -45,6 +45,7 @@ export default class ServicesStore extends Store { | |||
45 | this.actions.service.deleteService.listen(this._deleteService.bind(this)); | 45 | this.actions.service.deleteService.listen(this._deleteService.bind(this)); |
46 | this.actions.service.clearCache.listen(this._clearCache.bind(this)); | 46 | this.actions.service.clearCache.listen(this._clearCache.bind(this)); |
47 | this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); | 47 | this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); |
48 | this.actions.service.detachService.listen(this._detachService.bind(this)); | ||
48 | this.actions.service.focusService.listen(this._focusService.bind(this)); | 49 | this.actions.service.focusService.listen(this._focusService.bind(this)); |
49 | this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); | 50 | this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); |
50 | this.actions.service.toggleService.listen(this._toggleService.bind(this)); | 51 | this.actions.service.toggleService.listen(this._toggleService.bind(this)); |
@@ -344,6 +345,11 @@ export default class ServicesStore extends Store { | |||
344 | service.isAttached = true; | 345 | service.isAttached = true; |
345 | } | 346 | } |
346 | 347 | ||
348 | @action _detachService({ service }) { | ||
349 | service.webview = null; | ||
350 | service.isAttached = false; | ||
351 | } | ||
352 | |||
347 | @action _focusService({ serviceId }) { | 353 | @action _focusService({ serviceId }) { |
348 | const service = this.one(serviceId); | 354 | const service = this.one(serviceId); |
349 | 355 | ||