diff options
author | 2019-03-12 14:04:13 +0100 | |
---|---|---|
committer | 2019-03-12 14:04:13 +0100 | |
commit | 9af01849f895230ae60a635c9756a5e206aa0fa1 (patch) | |
tree | b0b99f324c1a2e8c52e1fadf1152efac6d1028d2 /src | |
parent | Bump version to 5.0.1-beta.1 (diff) | |
parent | Merge branch 'sergiughf-hotfix/update-electron' into develop (diff) | |
download | ferdium-app-9af01849f895230ae60a635c9756a5e206aa0fa1.tar.gz ferdium-app-9af01849f895230ae60a635c9756a5e206aa0fa1.tar.zst ferdium-app-9af01849f895230ae60a635c9756a5e206aa0fa1.zip |
Merge branch 'develop' into release/5.0.1-beta.1
Diffstat (limited to 'src')
-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/i18n/locales/defaultMessages.json | 52 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 2 | ||||
-rw-r--r-- | src/i18n/messages/src/lib/Menu.json | 52 | ||||
-rw-r--r-- | src/index.js | 5 | ||||
-rw-r--r-- | src/lib/Menu.js | 91 | ||||
-rw-r--r-- | src/lib/Tray.js | 8 | ||||
-rw-r--r-- | src/stores/AppStore.js | 56 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 6 | ||||
-rw-r--r-- | src/webview/contextMenu.js | 21 |
14 files changed, 391 insertions, 197 deletions
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/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 065398dc6..0641c510c 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json | |||
@@ -3875,6 +3875,58 @@ | |||
3875 | "column": 17, | 3875 | "column": 17, |
3876 | "line": 178 | 3876 | "line": 178 |
3877 | } | 3877 | } |
3878 | }, | ||
3879 | { | ||
3880 | "defaultMessage": "!!!Activate next service...", | ||
3881 | "end": { | ||
3882 | "column": 3, | ||
3883 | "line": 185 | ||
3884 | }, | ||
3885 | "file": "src/lib/Menu.js", | ||
3886 | "id": "menu.services.setNextServiceActive", | ||
3887 | "start": { | ||
3888 | "column": 23, | ||
3889 | "line": 182 | ||
3890 | } | ||
3891 | }, | ||
3892 | { | ||
3893 | "defaultMessage": "!!!Activate previous service...", | ||
3894 | "end": { | ||
3895 | "column": 3, | ||
3896 | "line": 189 | ||
3897 | }, | ||
3898 | "file": "src/lib/Menu.js", | ||
3899 | "id": "menu.services.activatePreviousService", | ||
3900 | "start": { | ||
3901 | "column": 27, | ||
3902 | "line": 186 | ||
3903 | } | ||
3904 | }, | ||
3905 | { | ||
3906 | "defaultMessage": "!!!Disable notifications & audio", | ||
3907 | "end": { | ||
3908 | "column": 3, | ||
3909 | "line": 193 | ||
3910 | }, | ||
3911 | "file": "src/lib/Menu.js", | ||
3912 | "id": "sidebar.muteApp", | ||
3913 | "start": { | ||
3914 | "column": 11, | ||
3915 | "line": 190 | ||
3916 | } | ||
3917 | }, | ||
3918 | { | ||
3919 | "defaultMessage": "!!!Enable notifications & audio", | ||
3920 | "end": { | ||
3921 | "column": 3, | ||
3922 | "line": 197 | ||
3923 | }, | ||
3924 | "file": "src/lib/Menu.js", | ||
3925 | "id": "sidebar.unmuteApp", | ||
3926 | "start": { | ||
3927 | "column": 13, | ||
3928 | "line": 194 | ||
3929 | } | ||
3878 | } | 3930 | } |
3879 | ], | 3931 | ], |
3880 | "path": "src/lib/Menu.json" | 3932 | "path": "src/lib/Menu.json" |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index af6edd2df..7543d38bd 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -70,7 +70,9 @@ | |||
70 | "menu.help.support": "Support", | 70 | "menu.help.support": "Support", |
71 | "menu.help.tos": "Terms of Service", | 71 | "menu.help.tos": "Terms of Service", |
72 | "menu.services": "Services", | 72 | "menu.services": "Services", |
73 | "menu.services.activatePreviousService": "Activate previous service", | ||
73 | "menu.services.addNewService": "Add New Service...", | 74 | "menu.services.addNewService": "Add New Service...", |
75 | "menu.services.setNextServiceActive": "Activate next service", | ||
74 | "menu.view": "View", | 76 | "menu.view": "View", |
75 | "menu.view.enterFullScreen": "Enter Full Screen", | 77 | "menu.view.enterFullScreen": "Enter Full Screen", |
76 | "menu.view.exitFullScreen": "Exit Full Screen", | 78 | "menu.view.exitFullScreen": "Exit Full Screen", |
diff --git a/src/i18n/messages/src/lib/Menu.json b/src/i18n/messages/src/lib/Menu.json index 6958f0219..9314f5cce 100644 --- a/src/i18n/messages/src/lib/Menu.json +++ b/src/i18n/messages/src/lib/Menu.json | |||
@@ -557,5 +557,57 @@ | |||
557 | "line": 181, | 557 | "line": 181, |
558 | "column": 3 | 558 | "column": 3 |
559 | } | 559 | } |
560 | }, | ||
561 | { | ||
562 | "id": "menu.services.setNextServiceActive", | ||
563 | "defaultMessage": "!!!Activate next service...", | ||
564 | "file": "src/lib/Menu.js", | ||
565 | "start": { | ||
566 | "line": 182, | ||
567 | "column": 23 | ||
568 | }, | ||
569 | "end": { | ||
570 | "line": 185, | ||
571 | "column": 3 | ||
572 | } | ||
573 | }, | ||
574 | { | ||
575 | "id": "menu.services.activatePreviousService", | ||
576 | "defaultMessage": "!!!Activate previous service...", | ||
577 | "file": "src/lib/Menu.js", | ||
578 | "start": { | ||
579 | "line": 186, | ||
580 | "column": 27 | ||
581 | }, | ||
582 | "end": { | ||
583 | "line": 189, | ||
584 | "column": 3 | ||
585 | } | ||
586 | }, | ||
587 | { | ||
588 | "id": "sidebar.muteApp", | ||
589 | "defaultMessage": "!!!Disable notifications & audio", | ||
590 | "file": "src/lib/Menu.js", | ||
591 | "start": { | ||
592 | "line": 190, | ||
593 | "column": 11 | ||
594 | }, | ||
595 | "end": { | ||
596 | "line": 193, | ||
597 | "column": 3 | ||
598 | } | ||
599 | }, | ||
600 | { | ||
601 | "id": "sidebar.unmuteApp", | ||
602 | "defaultMessage": "!!!Enable notifications & audio", | ||
603 | "file": "src/lib/Menu.js", | ||
604 | "start": { | ||
605 | "line": 194, | ||
606 | "column": 13 | ||
607 | }, | ||
608 | "end": { | ||
609 | "line": 197, | ||
610 | "column": 3 | ||
611 | } | ||
560 | } | 612 | } |
561 | ] \ No newline at end of file | 613 | ] \ No newline at end of file |
diff --git a/src/index.js b/src/index.js index 0e222c3d6..05c793d98 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -72,7 +72,10 @@ if (!gotTheLock) { | |||
72 | app.on('second-instance', (event, argv) => { | 72 | app.on('second-instance', (event, argv) => { |
73 | // Someone tried to run a second instance, we should focus our window. | 73 | // Someone tried to run a second instance, we should focus our window. |
74 | if (mainWindow) { | 74 | if (mainWindow) { |
75 | if (mainWindow.isMinimized()) mainWindow.restore(); | 75 | mainWindow.show(); |
76 | if (mainWindow.isMinimized()) { | ||
77 | mainWindow.restore(); | ||
78 | } | ||
76 | mainWindow.focus(); | 79 | mainWindow.focus(); |
77 | 80 | ||
78 | if (isWindows) { | 81 | if (isWindows) { |
diff --git a/src/lib/Menu.js b/src/lib/Menu.js index dce8ab969..7a60c448f 100644 --- a/src/lib/Menu.js +++ b/src/lib/Menu.js | |||
@@ -1,5 +1,5 @@ | |||
1 | import { remote, shell } from 'electron'; | 1 | import { remote, shell } from 'electron'; |
2 | import { observable, autorun, computed } from 'mobx'; | 2 | import { observable, autorun } from 'mobx'; |
3 | import { defineMessages } from 'react-intl'; | 3 | import { defineMessages } from 'react-intl'; |
4 | 4 | ||
5 | import { isMac, ctrlKey, cmdKey } from '../environment'; | 5 | import { isMac, ctrlKey, cmdKey } from '../environment'; |
@@ -179,6 +179,22 @@ const menuItems = defineMessages({ | |||
179 | id: 'menu.services.addNewService', | 179 | id: 'menu.services.addNewService', |
180 | defaultMessage: '!!!Add New Service...', | 180 | defaultMessage: '!!!Add New Service...', |
181 | }, | 181 | }, |
182 | activateNextService: { | ||
183 | id: 'menu.services.setNextServiceActive', | ||
184 | defaultMessage: '!!!Activate next service...', | ||
185 | }, | ||
186 | activatePreviousService: { | ||
187 | id: 'menu.services.activatePreviousService', | ||
188 | defaultMessage: '!!!Activate previous service...', | ||
189 | }, | ||
190 | muteApp: { | ||
191 | id: 'sidebar.muteApp', | ||
192 | defaultMessage: '!!!Disable notifications & audio', | ||
193 | }, | ||
194 | unmuteApp: { | ||
195 | id: 'sidebar.unmuteApp', | ||
196 | defaultMessage: '!!!Enable notifications & audio', | ||
197 | }, | ||
182 | }); | 198 | }); |
183 | 199 | ||
184 | function getActiveWebview() { | 200 | function getActiveWebview() { |
@@ -408,7 +424,7 @@ const _titleBarTemplateFactory = intl => [ | |||
408 | }, | 424 | }, |
409 | { | 425 | { |
410 | label: intl.formatMessage(menuItems.zoomIn), | 426 | label: intl.formatMessage(menuItems.zoomIn), |
411 | accelerator: `${ctrlKey}+Plus`, | 427 | accelerator: `${ctrlKey}+=`, |
412 | click() { | 428 | click() { |
413 | const activeService = getActiveWebview(); | 429 | const activeService = getActiveWebview(); |
414 | activeService.getZoomLevel((level) => { | 430 | activeService.getZoomLevel((level) => { |
@@ -519,13 +535,14 @@ export default class FranzMenu { | |||
519 | } | 535 | } |
520 | 536 | ||
521 | _build() { | 537 | _build() { |
522 | const serviceTpl = Object.assign([], this.serviceTpl); // need to clone object so we don't modify computed (cached) object | 538 | // need to clone object so we don't modify computed (cached) object |
539 | const serviceTpl = Object.assign([], this.serviceTpl()); | ||
523 | 540 | ||
524 | if (window.franz === undefined) { | 541 | if (window.franz === undefined) { |
525 | return; | 542 | return; |
526 | } | 543 | } |
527 | 544 | ||
528 | const intl = window.franz.intl; | 545 | const { intl } = window.franz; |
529 | const tpl = isMac ? _templateFactory(intl) : _titleBarTemplateFactory(intl); | 546 | const tpl = isMac ? _templateFactory(intl) : _titleBarTemplateFactory(intl); |
530 | 547 | ||
531 | tpl[1].submenu.push({ | 548 | tpl[1].submenu.push({ |
@@ -683,17 +700,6 @@ export default class FranzMenu { | |||
683 | }, about); | 700 | }, about); |
684 | } | 701 | } |
685 | 702 | ||
686 | serviceTpl.unshift({ | ||
687 | label: intl.formatMessage(menuItems.addNewService), | ||
688 | accelerator: `${cmdKey}+N`, | ||
689 | click: () => { | ||
690 | this.actions.ui.openSettings({ path: 'recipes' }); | ||
691 | }, | ||
692 | enabled: this.stores.user.isLoggedIn, | ||
693 | }, { | ||
694 | type: 'separator', | ||
695 | }); | ||
696 | |||
697 | if (serviceTpl.length > 0) { | 703 | if (serviceTpl.length > 0) { |
698 | tpl[3].submenu = serviceTpl; | 704 | tpl[3].submenu = serviceTpl; |
699 | } | 705 | } |
@@ -703,22 +709,49 @@ export default class FranzMenu { | |||
703 | Menu.setApplicationMenu(menu); | 709 | Menu.setApplicationMenu(menu); |
704 | } | 710 | } |
705 | 711 | ||
706 | @computed get serviceTpl() { | 712 | serviceTpl() { |
707 | const services = this.stores.services.allDisplayed; | 713 | const { intl } = window.franz; |
714 | const { user, services, settings } = this.stores; | ||
715 | if (!user.isLoggedIn) return []; | ||
716 | const menu = []; | ||
708 | 717 | ||
709 | if (this.stores.user.isLoggedIn) { | 718 | menu.push({ |
710 | return services.map((service, i) => ({ | 719 | label: intl.formatMessage(menuItems.addNewService), |
711 | label: this._getServiceName(service), | 720 | accelerator: `${cmdKey}+N`, |
712 | accelerator: i < 9 ? `${cmdKey}+${i + 1}` : null, | 721 | click: () => { |
713 | type: 'radio', | 722 | this.actions.ui.openSettings({ path: 'recipes' }); |
714 | checked: service.isActive, | 723 | }, |
715 | click: () => { | 724 | }, { |
716 | this.actions.service.setActive({ serviceId: service.id }); | 725 | type: 'separator', |
717 | }, | 726 | }, { |
718 | })); | 727 | label: intl.formatMessage(menuItems.activateNextService), |
719 | } | 728 | accelerator: `${cmdKey}+alt+right`, |
729 | click: () => this.actions.service.setActiveNext(), | ||
730 | }, { | ||
731 | label: intl.formatMessage(menuItems.activatePreviousService), | ||
732 | accelerator: `${cmdKey}+alt+left`, | ||
733 | click: () => this.actions.service.setActivePrev(), | ||
734 | }, { | ||
735 | label: intl.formatMessage( | ||
736 | settings.all.app.isAppMuted ? menuItems.unmuteApp : menuItems.muteApp, | ||
737 | ).replace('&', '&&'), | ||
738 | accelerator: `${cmdKey}+shift+m`, | ||
739 | click: () => this.actions.app.toggleMuteApp(), | ||
740 | }, { | ||
741 | type: 'separator', | ||
742 | }); | ||
743 | |||
744 | services.allDisplayed.forEach((service, i) => (menu.push({ | ||
745 | label: this._getServiceName(service), | ||
746 | accelerator: i < 9 ? `${cmdKey}+${i + 1}` : null, | ||
747 | type: 'radio', | ||
748 | checked: service.isActive, | ||
749 | click: () => { | ||
750 | this.actions.service.setActive({ serviceId: service.id }); | ||
751 | }, | ||
752 | }))); | ||
720 | 753 | ||
721 | return []; | 754 | return menu; |
722 | } | 755 | } |
723 | 756 | ||
724 | _getServiceName(service) { | 757 | _getServiceName(service) { |
diff --git a/src/lib/Tray.js b/src/lib/Tray.js index 669b02709..192e24796 100644 --- a/src/lib/Tray.js +++ b/src/lib/Tray.js | |||
@@ -22,7 +22,11 @@ export default class TrayIcon { | |||
22 | { | 22 | { |
23 | label: 'Show Franz', | 23 | label: 'Show Franz', |
24 | click() { | 24 | click() { |
25 | if (app.mainWindow.isMinimized()) { | ||
26 | app.mainWindow.restore(); | ||
27 | } | ||
25 | app.mainWindow.show(); | 28 | app.mainWindow.show(); |
29 | app.mainWindow.focus(); | ||
26 | }, | 30 | }, |
27 | }, { | 31 | }, { |
28 | label: 'Quit Franz', | 32 | label: 'Quit Franz', |
@@ -36,7 +40,11 @@ export default class TrayIcon { | |||
36 | this.trayIcon.setContextMenu(trayMenu); | 40 | this.trayIcon.setContextMenu(trayMenu); |
37 | 41 | ||
38 | this.trayIcon.on('click', () => { | 42 | this.trayIcon.on('click', () => { |
43 | if (app.mainWindow.isMinimized()) { | ||
44 | app.mainWindow.restore(); | ||
45 | } | ||
39 | app.mainWindow.show(); | 46 | app.mainWindow.show(); |
47 | app.mainWindow.focus(); | ||
40 | }); | 48 | }); |
41 | 49 | ||
42 | if (process.platform === 'darwin') { | 50 | if (process.platform === 'darwin') { |
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index d933ca407..351ad6422 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -3,7 +3,6 @@ import { | |||
3 | action, computed, observable, reaction, | 3 | action, computed, observable, reaction, |
4 | } from 'mobx'; | 4 | } from 'mobx'; |
5 | import moment from 'moment'; | 5 | import moment from 'moment'; |
6 | import key from 'keymaster'; | ||
7 | import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; | 6 | import { getDoNotDisturb } from '@meetfranz/electron-notification-state'; |
8 | import AutoLaunch from 'auto-launch'; | 7 | import AutoLaunch from 'auto-launch'; |
9 | import prettyBytes from 'pretty-bytes'; | 8 | import prettyBytes from 'pretty-bytes'; |
@@ -13,7 +12,7 @@ import { URL } from 'url'; | |||
13 | import Store from './lib/Store'; | 12 | import Store from './lib/Store'; |
14 | import Request from './lib/Request'; | 13 | import Request from './lib/Request'; |
15 | import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; | 14 | import { CHECK_INTERVAL, DEFAULT_APP_SETTINGS } from '../config'; |
16 | import { isMac, isLinux, isWindows } from '../environment'; | 15 | import { isMac } from '../environment'; |
17 | import locales from '../i18n/translations'; | 16 | import locales from '../i18n/translations'; |
18 | import { gaEvent, gaPage } from '../lib/analytics'; | 17 | import { gaEvent, gaPage } from '../lib/analytics'; |
19 | import { onVisibilityChange } from '../helpers/visibility-helper'; | 18 | import { onVisibilityChange } from '../helpers/visibility-helper'; |
@@ -157,41 +156,6 @@ export default class AppStore extends Store { | |||
157 | this.stores.router.push(url); | 156 | this.stores.router.push(url); |
158 | }); | 157 | }); |
159 | 158 | ||
160 | // Set active the next service | ||
161 | key( | ||
162 | '⌘+pagedown, ctrl+pagedown, ⌘+alt+right, ctrl+tab', () => { | ||
163 | this.actions.service.setActiveNext(); | ||
164 | }, | ||
165 | ); | ||
166 | |||
167 | // Set active the prev service | ||
168 | key( | ||
169 | '⌘+pageup, ctrl+pageup, ⌘+alt+left, ctrl+shift+tab', () => { | ||
170 | this.actions.service.setActivePrev(); | ||
171 | }, | ||
172 | ); | ||
173 | |||
174 | // Global Mute | ||
175 | key( | ||
176 | '⌘+shift+m ctrl+shift+m', () => { | ||
177 | this.actions.app.toggleMuteApp(); | ||
178 | }, | ||
179 | ); | ||
180 | |||
181 | // We need to add an additional key listener for ctrl+ on windows. Otherwise only ctrl+shift+ would work | ||
182 | if (isWindows) { | ||
183 | key( | ||
184 | 'ctrl+=', () => { | ||
185 | debug('Windows: zoom in via ctrl+'); | ||
186 | const { webview } = this.stores.services.active; | ||
187 | webview.getZoomLevel((level) => { | ||
188 | // level 9 =~ +300% and setZoomLevel wouldnt zoom in further | ||
189 | if (level < 9) webview.setZoomLevel(level + 1); | ||
190 | }); | ||
191 | }, | ||
192 | ); | ||
193 | } | ||
194 | |||
195 | this.locale = this._getDefaultLocale(); | 159 | this.locale = this._getDefaultLocale(); |
196 | 160 | ||
197 | this._healthCheck(); | 161 | this._healthCheck(); |
@@ -221,7 +185,15 @@ export default class AppStore extends Store { | |||
221 | }) { | 185 | }) { |
222 | if (this.stores.settings.all.app.isAppMuted) return; | 186 | if (this.stores.settings.all.app.isAppMuted) return; |
223 | 187 | ||
188 | // TODO: is there a simple way to use blobs for notifications without storing them on disk? | ||
189 | if (options.icon.startsWith('blob:')) { | ||
190 | delete options.icon; | ||
191 | } | ||
192 | |||
224 | const notification = new window.Notification(title, options); | 193 | const notification = new window.Notification(title, options); |
194 | |||
195 | debug('New notification', title, options); | ||
196 | |||
225 | notification.onclick = (e) => { | 197 | notification.onclick = (e) => { |
226 | if (serviceId) { | 198 | if (serviceId) { |
227 | this.actions.service.sendIPCMessage({ | 199 | this.actions.service.sendIPCMessage({ |
@@ -231,12 +203,13 @@ export default class AppStore extends Store { | |||
231 | }); | 203 | }); |
232 | 204 | ||
233 | this.actions.service.setActive({ serviceId }); | 205 | this.actions.service.setActive({ serviceId }); |
234 | 206 | mainWindow.show(); | |
235 | if (isWindows) { | 207 | if (app.mainWindow.isMinimized()) { |
236 | mainWindow.restore(); | 208 | mainWindow.restore(); |
237 | } else if (isLinux) { | ||
238 | mainWindow.show(); | ||
239 | } | 209 | } |
210 | mainWindow.focus(); | ||
211 | |||
212 | debug('Notification click handler'); | ||
240 | } | 213 | } |
241 | }; | 214 | }; |
242 | } | 215 | } |
@@ -303,7 +276,6 @@ export default class AppStore extends Store { | |||
303 | 276 | ||
304 | @action _muteApp({ isMuted, overrideSystemMute = true }) { | 277 | @action _muteApp({ isMuted, overrideSystemMute = true }) { |
305 | this.isSystemMuteOverridden = overrideSystemMute; | 278 | this.isSystemMuteOverridden = overrideSystemMute; |
306 | |||
307 | this.actions.settings.update({ | 279 | this.actions.settings.update({ |
308 | type: 'app', | 280 | type: 'app', |
309 | data: { | 281 | data: { |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index c63bef196..69e616f0c 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -44,6 +44,7 @@ export default class ServicesStore extends Store { | |||
44 | this.actions.service.deleteService.listen(this._deleteService.bind(this)); | 44 | this.actions.service.deleteService.listen(this._deleteService.bind(this)); |
45 | this.actions.service.clearCache.listen(this._clearCache.bind(this)); | 45 | this.actions.service.clearCache.listen(this._clearCache.bind(this)); |
46 | this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); | 46 | this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); |
47 | this.actions.service.detachService.listen(this._detachService.bind(this)); | ||
47 | this.actions.service.focusService.listen(this._focusService.bind(this)); | 48 | this.actions.service.focusService.listen(this._focusService.bind(this)); |
48 | this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); | 49 | this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); |
49 | this.actions.service.toggleService.listen(this._toggleService.bind(this)); | 50 | this.actions.service.toggleService.listen(this._toggleService.bind(this)); |
@@ -341,6 +342,11 @@ export default class ServicesStore extends Store { | |||
341 | service.isAttached = true; | 342 | service.isAttached = true; |
342 | } | 343 | } |
343 | 344 | ||
345 | @action _detachService({ service }) { | ||
346 | service.webview = null; | ||
347 | service.isAttached = false; | ||
348 | } | ||
349 | |||
344 | @action _focusService({ serviceId }) { | 350 | @action _focusService({ serviceId }) { |
345 | const service = this.one(serviceId); | 351 | const service = this.one(serviceId); |
346 | 352 | ||
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index afb1d8912..a4a6ab899 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js | |||
@@ -33,6 +33,8 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
33 | const canGoBack = webContents.canGoBack(); | 33 | const canGoBack = webContents.canGoBack(); |
34 | const canGoForward = webContents.canGoForward(); | 34 | const canGoForward = webContents.canGoForward(); |
35 | 35 | ||
36 | // @adlk: we can't use roles here due to a bug with electron where electron.remote.webContents.getFocusedWebContents() returns the first webview in DOM instead of the focused one | ||
37 | // Github issue creation is pending | ||
36 | let menuTpl = [ | 38 | let menuTpl = [ |
37 | { | 39 | { |
38 | type: 'separator', | 40 | type: 'separator', |
@@ -48,19 +50,32 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck | |||
48 | type: 'separator', | 50 | type: 'separator', |
49 | }, { | 51 | }, { |
50 | id: 'cut', | 52 | id: 'cut', |
51 | role: can('Cut') ? 'cut' : '', | 53 | label: 'Cut', |
54 | click() { | ||
55 | if (can('Cut')) { | ||
56 | webContents.cut(); | ||
57 | } | ||
58 | }, | ||
52 | enabled: can('Cut'), | 59 | enabled: can('Cut'), |
53 | visible: hasText && props.isEditable, | 60 | visible: hasText && props.isEditable, |
54 | }, { | 61 | }, { |
55 | id: 'copy', | 62 | id: 'copy', |
56 | label: 'Copy', | 63 | label: 'Copy', |
57 | role: can('Copy') ? 'copy' : '', | 64 | click() { |
65 | if (can('Copy')) { | ||
66 | webContents.copy(); | ||
67 | } | ||
68 | }, | ||
58 | enabled: can('Copy'), | 69 | enabled: can('Copy'), |
59 | visible: props.isEditable || hasText, | 70 | visible: props.isEditable || hasText, |
60 | }, { | 71 | }, { |
61 | id: 'paste', | 72 | id: 'paste', |
62 | label: 'Paste', | 73 | label: 'Paste', |
63 | role: editFlags.canPaste ? 'paste' : '', | 74 | click() { |
75 | if (editFlags.canPaste) { | ||
76 | webContents.paste(); | ||
77 | } | ||
78 | }, | ||
64 | enabled: editFlags.canPaste, | 79 | enabled: editFlags.canPaste, |
65 | visible: props.isEditable, | 80 | visible: props.isEditable, |
66 | }, { | 81 | }, { |