diff options
Diffstat (limited to 'src')
32 files changed, 525 insertions, 89 deletions
diff --git a/src/app.js b/src/app.js index 831dd93ce..6660feb46 100644 --- a/src/app.js +++ b/src/app.js | |||
@@ -62,6 +62,7 @@ window.addEventListener('load', () => { | |||
62 | menu, | 62 | menu, |
63 | touchBar, | 63 | touchBar, |
64 | analytics, | 64 | analytics, |
65 | features: {}, | ||
65 | render() { | 66 | render() { |
66 | const preparedApp = ( | 67 | const preparedApp = ( |
67 | <Provider stores={stores} actions={actions}> | 68 | <Provider stores={stores} actions={actions}> |
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index dbe0bb4b6..bce792e56 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js | |||
@@ -6,9 +6,10 @@ import { TitleBar } from 'electron-react-titlebar'; | |||
6 | 6 | ||
7 | import InfoBar from '../ui/InfoBar'; | 7 | import InfoBar from '../ui/InfoBar'; |
8 | import { Component as DelayApp } from '../../features/delayApp'; | 8 | import { Component as DelayApp } from '../../features/delayApp'; |
9 | import { Component as BasicAuth } from '../../features/basicAuth'; | ||
9 | import ErrorBoundary from '../util/ErrorBoundary'; | 10 | import ErrorBoundary from '../util/ErrorBoundary'; |
10 | 11 | ||
11 | import globalMessages from '../../i18n/globalMessages'; | 12 | // import globalMessages from '../../i18n/globalMessages'; |
12 | 13 | ||
13 | import { isWindows } from '../../environment'; | 14 | import { isWindows } from '../../environment'; |
14 | 15 | ||
@@ -50,7 +51,7 @@ export default @observer class AppLayout extends Component { | |||
50 | services: PropTypes.element.isRequired, | 51 | services: PropTypes.element.isRequired, |
51 | children: PropTypes.element, | 52 | children: PropTypes.element, |
52 | news: MobxPropTypes.arrayOrObservableArray.isRequired, | 53 | news: MobxPropTypes.arrayOrObservableArray.isRequired, |
53 | isOnline: PropTypes.bool.isRequired, | 54 | // isOnline: PropTypes.bool.isRequired, |
54 | showServicesUpdatedInfoBar: PropTypes.bool.isRequired, | 55 | showServicesUpdatedInfoBar: PropTypes.bool.isRequired, |
55 | appUpdateIsDownloaded: PropTypes.bool.isRequired, | 56 | appUpdateIsDownloaded: PropTypes.bool.isRequired, |
56 | removeNewsItem: PropTypes.func.isRequired, | 57 | removeNewsItem: PropTypes.func.isRequired, |
@@ -78,7 +79,7 @@ export default @observer class AppLayout extends Component { | |||
78 | sidebar, | 79 | sidebar, |
79 | services, | 80 | services, |
80 | children, | 81 | children, |
81 | isOnline, | 82 | // isOnline, |
82 | news, | 83 | news, |
83 | showServicesUpdatedInfoBar, | 84 | showServicesUpdatedInfoBar, |
84 | appUpdateIsDownloaded, | 85 | appUpdateIsDownloaded, |
@@ -114,14 +115,15 @@ export default @observer class AppLayout extends Component { | |||
114 | <span dangerouslySetInnerHTML={createMarkup(item.message)} /> | 115 | <span dangerouslySetInnerHTML={createMarkup(item.message)} /> |
115 | </InfoBar> | 116 | </InfoBar> |
116 | ))} | 117 | ))} |
117 | {!isOnline && ( | 118 | {/* {!isOnline && ( |
118 | <InfoBar | 119 | <InfoBar |
119 | type="danger" | 120 | type="danger" |
121 | sticky | ||
120 | > | 122 | > |
121 | <span className="mdi mdi-flash" /> | 123 | <span className="mdi mdi-flash" /> |
122 | {intl.formatMessage(globalMessages.notConnectedToTheInternet)} | 124 | {intl.formatMessage(globalMessages.notConnectedToTheInternet)} |
123 | </InfoBar> | 125 | </InfoBar> |
124 | )} | 126 | )} */} |
125 | {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( | 127 | {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( |
126 | <InfoBar | 128 | <InfoBar |
127 | type="danger" | 129 | type="danger" |
@@ -161,6 +163,7 @@ export default @observer class AppLayout extends Component { | |||
161 | </InfoBar> | 163 | </InfoBar> |
162 | )} | 164 | )} |
163 | {isDelayAppScreenVisible && (<DelayApp />)} | 165 | {isDelayAppScreenVisible && (<DelayApp />)} |
166 | <BasicAuth /> | ||
164 | {services} | 167 | {services} |
165 | </div> | 168 | </div> |
166 | </div> | 169 | </div> |
diff --git a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js index 415a8d1b5..37ccc8e51 100644 --- a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js +++ b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js | |||
@@ -31,7 +31,7 @@ const messages = defineMessages({ | |||
31 | }, | 31 | }, |
32 | }); | 32 | }); |
33 | 33 | ||
34 | export default @injectSheet(styles) @observer class WebviewCrashHandler extends Component { | 34 | export default @injectSheet(styles) @observer class WebviewErrorHandler extends Component { |
35 | static propTypes = { | 35 | static propTypes = { |
36 | name: PropTypes.string.isRequired, | 36 | name: PropTypes.string.isRequired, |
37 | reload: PropTypes.func.isRequired, | 37 | reload: PropTypes.func.isRequired, |
diff --git a/src/components/services/content/ErrorHandlers/styles.js b/src/components/services/content/ErrorHandlers/styles.js index f11386798..9e2509ee5 100644 --- a/src/components/services/content/ErrorHandlers/styles.js +++ b/src/components/services/content/ErrorHandlers/styles.js | |||
@@ -1,4 +1,4 @@ | |||
1 | export default { | 1 | export default theme => ({ |
2 | component: { | 2 | component: { |
3 | left: 0, | 3 | left: 0, |
4 | position: 'absolute', | 4 | position: 'absolute', |
@@ -6,7 +6,7 @@ export default { | |||
6 | width: '100%', | 6 | width: '100%', |
7 | zIndex: 0, | 7 | zIndex: 0, |
8 | alignItems: 'center', | 8 | alignItems: 'center', |
9 | // background: $theme-gray-lighter; | 9 | background: theme.colorWebviewErrorHandlerBackground, |
10 | display: 'flex', | 10 | display: 'flex', |
11 | flexDirection: 'column', | 11 | flexDirection: 'column', |
12 | justifyContent: 'center', | 12 | justifyContent: 'center', |
@@ -22,4 +22,4 @@ export default { | |||
22 | margin: [0, 10, 0, 10], | 22 | margin: [0, 10, 0, 10], |
23 | }, | 23 | }, |
24 | }, | 24 | }, |
25 | }; | 25 | }); |
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index b1a2c0207..bb577e4cc 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js | |||
@@ -18,7 +18,6 @@ export default @observer class ServiceWebview extends Component { | |||
18 | setWebviewReference: PropTypes.func.isRequired, | 18 | setWebviewReference: PropTypes.func.isRequired, |
19 | reload: PropTypes.func.isRequired, | 19 | reload: PropTypes.func.isRequired, |
20 | edit: PropTypes.func.isRequired, | 20 | edit: PropTypes.func.isRequired, |
21 | isAppMuted: PropTypes.bool.isRequired, | ||
22 | enable: PropTypes.func.isRequired, | 21 | enable: PropTypes.func.isRequired, |
23 | isActive: PropTypes.bool, | 22 | isActive: PropTypes.bool, |
24 | }; | 23 | }; |
@@ -69,7 +68,6 @@ export default @observer class ServiceWebview extends Component { | |||
69 | setWebviewReference, | 68 | setWebviewReference, |
70 | reload, | 69 | reload, |
71 | edit, | 70 | edit, |
72 | isAppMuted, | ||
73 | enable, | 71 | enable, |
74 | } = this.props; | 72 | } = this.props; |
75 | 73 | ||
@@ -137,7 +135,6 @@ export default @observer class ServiceWebview extends Component { | |||
137 | })} | 135 | })} |
138 | onUpdateTargetUrl={this.updateTargetUrl} | 136 | onUpdateTargetUrl={this.updateTargetUrl} |
139 | useragent={service.userAgent} | 137 | useragent={service.userAgent} |
140 | muted={isAppMuted || service.isMuted} | ||
141 | allowpopups | 138 | allowpopups |
142 | /> | 139 | /> |
143 | )} | 140 | )} |
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 1aeb17e03..54f16ba12 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js | |||
@@ -26,7 +26,6 @@ export default @observer class Services extends Component { | |||
26 | openWindow: PropTypes.func.isRequired, | 26 | openWindow: PropTypes.func.isRequired, |
27 | reload: PropTypes.func.isRequired, | 27 | reload: PropTypes.func.isRequired, |
28 | openSettings: PropTypes.func.isRequired, | 28 | openSettings: PropTypes.func.isRequired, |
29 | isAppMuted: PropTypes.bool.isRequired, | ||
30 | update: PropTypes.func.isRequired, | 29 | update: PropTypes.func.isRequired, |
31 | }; | 30 | }; |
32 | 31 | ||
@@ -46,7 +45,6 @@ export default @observer class Services extends Component { | |||
46 | openWindow, | 45 | openWindow, |
47 | reload, | 46 | reload, |
48 | openSettings, | 47 | openSettings, |
49 | isAppMuted, | ||
50 | update, | 48 | update, |
51 | } = this.props; | 49 | } = this.props; |
52 | const { intl } = this.context; | 50 | const { intl } = this.context; |
@@ -81,7 +79,6 @@ export default @observer class Services extends Component { | |||
81 | openWindow={openWindow} | 79 | openWindow={openWindow} |
82 | reload={() => reload({ serviceId: service.id })} | 80 | reload={() => reload({ serviceId: service.id })} |
83 | edit={() => openSettings({ path: `services/edit/${service.id}` })} | 81 | edit={() => openSettings({ path: `services/edit/${service.id}` })} |
84 | isAppMuted={isAppMuted} | ||
85 | enable={() => update({ | 82 | enable={() => update({ |
86 | serviceId: service.id, | 83 | serviceId: service.id, |
87 | serviceData: { | 84 | serviceData: { |
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js new file mode 100644 index 000000000..d84e4c713 --- /dev/null +++ b/src/components/ui/Modal/index.js | |||
@@ -0,0 +1,59 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import ReactModal from 'react-modal'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | import classnames from 'classnames'; | ||
5 | import injectCSS from 'react-jss'; | ||
6 | |||
7 | import styles from './styles'; | ||
8 | |||
9 | export default @injectCSS(styles) class Modal extends Component { | ||
10 | static propTypes = { | ||
11 | children: PropTypes.node.isRequired, | ||
12 | className: PropTypes.string, | ||
13 | classes: PropTypes.object.isRequired, | ||
14 | isOpen: PropTypes.bool.isRequired, | ||
15 | portal: PropTypes.string, | ||
16 | close: PropTypes.func.isRequired, | ||
17 | } | ||
18 | |||
19 | static defaultProps = { | ||
20 | className: null, | ||
21 | portal: 'modal-portal', | ||
22 | } | ||
23 | |||
24 | render() { | ||
25 | const { | ||
26 | children, | ||
27 | className, | ||
28 | classes, | ||
29 | isOpen, | ||
30 | portal, | ||
31 | close, | ||
32 | } = this.props; | ||
33 | |||
34 | return ( | ||
35 | <ReactModal | ||
36 | isOpen={isOpen} | ||
37 | className={classnames({ | ||
38 | [`${classes.modal}`]: true, | ||
39 | [`${className}`]: className, | ||
40 | })} | ||
41 | portalClassName={classes.component} | ||
42 | overlayClassName={classes.overlay} | ||
43 | portal={portal} | ||
44 | onRequestClose={close} | ||
45 | > | ||
46 | {/* <button | ||
47 | type="button" | ||
48 | className={classnames({ | ||
49 | [`${classes.close}`]: true, | ||
50 | 'mdi mdi-close': true, | ||
51 | })} | ||
52 | /> */} | ||
53 | <div className={classes.content}> | ||
54 | {children} | ||
55 | </div> | ||
56 | </ReactModal> | ||
57 | ); | ||
58 | } | ||
59 | } | ||
diff --git a/src/components/ui/Modal/styles.js b/src/components/ui/Modal/styles.js new file mode 100644 index 000000000..56fecbf55 --- /dev/null +++ b/src/components/ui/Modal/styles.js | |||
@@ -0,0 +1,32 @@ | |||
1 | export default theme => ({ | ||
2 | component: { | ||
3 | zIndex: 500, | ||
4 | position: 'absolute', | ||
5 | }, | ||
6 | overlay: { | ||
7 | background: theme.colorModalOverlayBackground, | ||
8 | position: 'fixed', | ||
9 | top: 0, | ||
10 | left: 0, | ||
11 | right: 0, | ||
12 | bottom: 0, | ||
13 | display: 'flex', | ||
14 | }, | ||
15 | modal: { | ||
16 | background: '#FFF', | ||
17 | maxWidth: '90%', | ||
18 | height: 'auto', | ||
19 | margin: 'auto auto', | ||
20 | borderRadius: 6, | ||
21 | boxShadow: '0px 13px 40px 0px rgba(0,0,0,0.2)', | ||
22 | position: 'relative', | ||
23 | }, | ||
24 | content: { | ||
25 | padding: 20, | ||
26 | }, | ||
27 | close: { | ||
28 | position: 'absolute', | ||
29 | top: 0, | ||
30 | right: 0, | ||
31 | }, | ||
32 | }); | ||
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index 430b49b55..749912c59 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js | |||
@@ -108,7 +108,6 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e | |||
108 | openWindow={openWindow} | 108 | openWindow={openWindow} |
109 | reload={reload} | 109 | reload={reload} |
110 | openSettings={openSettings} | 110 | openSettings={openSettings} |
111 | isAppMuted={settings.all.app.isAppMuted} | ||
112 | update={updateService} | 111 | update={updateService} |
113 | /> | 112 | /> |
114 | ); | 113 | ); |
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index 019b3d7d6..d681d5226 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js | |||
@@ -14,6 +14,14 @@ import ErrorBoundary from '../../components/util/ErrorBoundary'; | |||
14 | const { BrowserWindow } = remote; | 14 | const { BrowserWindow } = remote; |
15 | 15 | ||
16 | export default @inject('stores', 'actions') @observer class AccountScreen extends Component { | 16 | export default @inject('stores', 'actions') @observer class AccountScreen extends Component { |
17 | componentWillMount() { | ||
18 | const { | ||
19 | user, | ||
20 | } = this.props.stores; | ||
21 | |||
22 | user.getUserInfoRequest.invalidate({ immediately: true }); | ||
23 | } | ||
24 | |||
17 | componentDidMount() { | 25 | componentDidMount() { |
18 | gaPage('Settings/Account Dashboard'); | 26 | gaPage('Settings/Account Dashboard'); |
19 | } | 27 | } |
diff --git a/src/electron/ipc-api/settings.js b/src/electron/ipc-api/settings.js index ce006bb92..b651db306 100644 --- a/src/electron/ipc-api/settings.js +++ b/src/electron/ipc-api/settings.js | |||
@@ -2,7 +2,6 @@ import { ipcMain } from 'electron'; | |||
2 | 2 | ||
3 | export default (params) => { | 3 | export default (params) => { |
4 | ipcMain.on('getAppSettings', (event, type) => { | 4 | ipcMain.on('getAppSettings', (event, type) => { |
5 | console.log('getAppSettings', type, params.settings[type].all); | ||
6 | params.mainWindow.webContents.send('appSettings', { | 5 | params.mainWindow.webContents.send('appSettings', { |
7 | type, | 6 | type, |
8 | data: params.settings[type].all, | 7 | data: params.settings[type].all, |
diff --git a/src/environment.js b/src/environment.js index e1762129b..73b1c7ab2 100644 --- a/src/environment.js +++ b/src/environment.js | |||
@@ -1,10 +1,12 @@ | |||
1 | import isDev from 'electron-is-dev'; | ||
2 | |||
1 | import { LIVE_API, DEV_API, LOCAL_API } from './config'; | 3 | import { LIVE_API, DEV_API, LOCAL_API } from './config'; |
2 | 4 | ||
3 | export const isDevMode = Boolean(process.execPath.match(/[\\/]electron/)); | 5 | export const isDevMode = isDev; |
4 | export const useLiveAPI = process.env.LIVE_API; | 6 | export const useLiveAPI = process.env.LIVE_API; |
5 | export const useLocalAPI = process.env.LOCAL_API; | 7 | export const useLocalAPI = process.env.LOCAL_API; |
6 | 8 | ||
7 | let platform = process.platform; | 9 | let { platform } = process; |
8 | if (process.env.OS_PLATFORM) { | 10 | if (process.env.OS_PLATFORM) { |
9 | platform = process.env.OS_PLATFORM; | 11 | platform = process.env.OS_PLATFORM; |
10 | } | 12 | } |
diff --git a/src/features/basicAuth/Component.js b/src/features/basicAuth/Component.js new file mode 100644 index 000000000..13395fb40 --- /dev/null +++ b/src/features/basicAuth/Component.js | |||
@@ -0,0 +1,102 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import injectSheet from 'react-jss'; | ||
4 | import { observer } from 'mobx-react'; | ||
5 | import classnames from 'classnames'; | ||
6 | |||
7 | import Modal from '../../components/ui/Modal'; | ||
8 | import Input from '../../components/ui/Input'; | ||
9 | import Button from '../../components/ui/Button'; | ||
10 | |||
11 | import { | ||
12 | state, | ||
13 | resetState, | ||
14 | sendCredentials, | ||
15 | cancelLogin, | ||
16 | } from '.'; | ||
17 | import Form from './Form'; | ||
18 | |||
19 | import styles from './styles'; | ||
20 | |||
21 | export default @injectSheet(styles) @observer class BasicAuthModal extends Component { | ||
22 | static propTypes = { | ||
23 | classes: PropTypes.object.isRequired, | ||
24 | } | ||
25 | |||
26 | submit(e) { | ||
27 | e.preventDefault(); | ||
28 | |||
29 | const values = Form.values(); | ||
30 | console.log('form submit', values); | ||
31 | |||
32 | sendCredentials(values.user, values.password); | ||
33 | resetState(); | ||
34 | } | ||
35 | |||
36 | cancel() { | ||
37 | cancelLogin(); | ||
38 | this.close(); | ||
39 | } | ||
40 | |||
41 | close() { | ||
42 | resetState(); | ||
43 | state.isModalVisible = false; | ||
44 | } | ||
45 | |||
46 | render() { | ||
47 | const { | ||
48 | classes, | ||
49 | } = this.props; | ||
50 | |||
51 | const { | ||
52 | isModalVisible, | ||
53 | authInfo, | ||
54 | } = state; | ||
55 | |||
56 | if (!authInfo) { | ||
57 | return null; | ||
58 | } | ||
59 | |||
60 | return ( | ||
61 | <Modal | ||
62 | isOpen={isModalVisible} | ||
63 | className={classes.modal} | ||
64 | close={this.cancel.bind(this)} | ||
65 | > | ||
66 | <h1>Sign in</h1> | ||
67 | <p> | ||
68 | http | ||
69 | {authInfo.port === 443 && 's'} | ||
70 | :// | ||
71 | {authInfo.host} | ||
72 | </p> | ||
73 | <form | ||
74 | onSubmit={this.submit.bind(this)} | ||
75 | className={classnames('franz-form', classes.form)} | ||
76 | > | ||
77 | <Input | ||
78 | field={Form.$('user')} | ||
79 | showLabel={false} | ||
80 | /> | ||
81 | <Input | ||
82 | field={Form.$('password')} | ||
83 | showLabel={false} | ||
84 | showPasswordToggle | ||
85 | /> | ||
86 | <div className={classes.buttons}> | ||
87 | <Button | ||
88 | type="button" | ||
89 | label="Cancel" | ||
90 | buttonType="secondary" | ||
91 | onClick={this.cancel.bind(this)} | ||
92 | /> | ||
93 | <Button | ||
94 | type="submit" | ||
95 | label="Sign In" | ||
96 | /> | ||
97 | </div> | ||
98 | </form> | ||
99 | </Modal> | ||
100 | ); | ||
101 | } | ||
102 | } | ||
diff --git a/src/features/basicAuth/Form.js b/src/features/basicAuth/Form.js new file mode 100644 index 000000000..95721d0e9 --- /dev/null +++ b/src/features/basicAuth/Form.js | |||
@@ -0,0 +1,17 @@ | |||
1 | import Form from '../../lib/Form'; | ||
2 | |||
3 | export default new Form({ | ||
4 | fields: { | ||
5 | user: { | ||
6 | label: 'user', | ||
7 | placeholder: 'Username', | ||
8 | value: '', | ||
9 | }, | ||
10 | password: { | ||
11 | label: 'Password', | ||
12 | placeholder: 'Password', | ||
13 | value: '', | ||
14 | type: 'password', | ||
15 | }, | ||
16 | }, | ||
17 | }); | ||
diff --git a/src/features/basicAuth/index.js b/src/features/basicAuth/index.js new file mode 100644 index 000000000..03269582c --- /dev/null +++ b/src/features/basicAuth/index.js | |||
@@ -0,0 +1,68 @@ | |||
1 | import { ipcRenderer } from 'electron'; | ||
2 | import { observable } from 'mobx'; | ||
3 | |||
4 | import BasicAuthComponent from './Component'; | ||
5 | |||
6 | const debug = require('debug')('Franz:feature:basicAuth'); | ||
7 | |||
8 | const defaultState = { | ||
9 | isModalVisible: false, | ||
10 | service: null, | ||
11 | authInfo: null, | ||
12 | }; | ||
13 | |||
14 | export const state = observable(defaultState); | ||
15 | |||
16 | export function resetState() { | ||
17 | Object.assign(state, defaultState); | ||
18 | console.log('reset state', state); | ||
19 | } | ||
20 | |||
21 | export default function initialize() { | ||
22 | debug('Initialize basicAuth feature'); | ||
23 | |||
24 | window.franz.features.basicAuth = { | ||
25 | state, | ||
26 | }; | ||
27 | |||
28 | ipcRenderer.on('feature:basic-auth-request', (e, data) => { | ||
29 | debug(e, data); | ||
30 | // state.serviceId = data.serviceId; | ||
31 | state.authInfo = data.authInfo; | ||
32 | state.isModalVisible = true; | ||
33 | }); | ||
34 | |||
35 | // autorun(() => { | ||
36 | // // if (state.serviceId) { | ||
37 | // // const service = stores.services.one(state.serviceId); | ||
38 | // // if (service) { | ||
39 | // // state.service = service; | ||
40 | // // } | ||
41 | // // } | ||
42 | // }); | ||
43 | } | ||
44 | |||
45 | export function mainIpcHandler(mainWindow, authInfo) { | ||
46 | debug('Sending basic auth call', authInfo); | ||
47 | |||
48 | mainWindow.webContents.send('feature:basic-auth-request', { | ||
49 | authInfo, | ||
50 | }); | ||
51 | } | ||
52 | |||
53 | export function sendCredentials(user, password) { | ||
54 | debug('Sending credentials to main', user, password); | ||
55 | |||
56 | ipcRenderer.send('feature-basic-auth-credentials', { | ||
57 | user, | ||
58 | password, | ||
59 | }); | ||
60 | } | ||
61 | |||
62 | export function cancelLogin() { | ||
63 | debug('Cancel basic auth event'); | ||
64 | |||
65 | ipcRenderer.send('feature-basic-auth-cancel'); | ||
66 | } | ||
67 | |||
68 | export const Component = BasicAuthComponent; | ||
diff --git a/src/features/basicAuth/mainIpcHandler.js b/src/features/basicAuth/mainIpcHandler.js new file mode 100644 index 000000000..87ac0b6df --- /dev/null +++ b/src/features/basicAuth/mainIpcHandler.js | |||
@@ -0,0 +1,9 @@ | |||
1 | const debug = require('debug')('Franz:feature:basicAuth:main'); | ||
2 | |||
3 | export default function mainIpcHandler(mainWindow, authInfo) { | ||
4 | debug('Sending basic auth call', authInfo); | ||
5 | |||
6 | mainWindow.webContents.send('feature:basic-auth', { | ||
7 | authInfo, | ||
8 | }); | ||
9 | } | ||
diff --git a/src/features/basicAuth/styles.js b/src/features/basicAuth/styles.js new file mode 100644 index 000000000..6bdaf9a6e --- /dev/null +++ b/src/features/basicAuth/styles.js | |||
@@ -0,0 +1,12 @@ | |||
1 | export default { | ||
2 | modal: { | ||
3 | width: 300, | ||
4 | }, | ||
5 | buttons: { | ||
6 | display: 'flex', | ||
7 | justifyContent: 'space-between', | ||
8 | }, | ||
9 | form: { | ||
10 | marginTop: 15, | ||
11 | }, | ||
12 | }; | ||
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js index 6e0532c9a..ff84510e8 100644 --- a/src/features/delayApp/Component.js +++ b/src/features/delayApp/Component.js | |||
@@ -4,6 +4,8 @@ import { inject, observer } from 'mobx-react'; | |||
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | import injectSheet from 'react-jss'; | 5 | import injectSheet from 'react-jss'; |
6 | 6 | ||
7 | import { gaEvent } from '../../lib/analytics'; | ||
8 | |||
7 | import Button from '../../components/ui/Button'; | 9 | import Button from '../../components/ui/Button'; |
8 | 10 | ||
9 | import { config } from '.'; | 11 | import { config } from '.'; |
@@ -59,8 +61,16 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp | |||
59 | clearInterval(this.countdownInterval); | 61 | clearInterval(this.countdownInterval); |
60 | } | 62 | } |
61 | 63 | ||
64 | handleCTAClick() { | ||
65 | const { actions } = this.props; | ||
66 | |||
67 | actions.ui.openSettings({ path: 'user' }); | ||
68 | |||
69 | gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature'); | ||
70 | } | ||
71 | |||
62 | render() { | 72 | render() { |
63 | const { classes, actions } = this.props; | 73 | const { classes } = this.props; |
64 | const { intl } = this.context; | 74 | const { intl } = this.context; |
65 | 75 | ||
66 | return ( | 76 | return ( |
@@ -70,7 +80,7 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp | |||
70 | label={intl.formatMessage(messages.action)} | 80 | label={intl.formatMessage(messages.action)} |
71 | className={classes.button} | 81 | className={classes.button} |
72 | buttonType="inverted" | 82 | buttonType="inverted" |
73 | onClick={() => actions.ui.openSettings({ path: 'user' })} | 83 | onClick={this.handleCTAClick.bind(this)} |
74 | /> | 84 | /> |
75 | <p className="footnote"> | 85 | <p className="footnote"> |
76 | {intl.formatMessage(messages.text, { | 86 | {intl.formatMessage(messages.text, { |
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js index d5c544b78..28aa50eb2 100644 --- a/src/features/delayApp/index.js +++ b/src/features/delayApp/index.js | |||
@@ -3,6 +3,7 @@ import moment from 'moment'; | |||
3 | import DelayAppComponent from './Component'; | 3 | import DelayAppComponent from './Component'; |
4 | 4 | ||
5 | import { DEFAULT_FEATURES_CONFIG } from '../../config'; | 5 | import { DEFAULT_FEATURES_CONFIG } from '../../config'; |
6 | import { gaEvent } from '../../lib/analytics'; | ||
6 | 7 | ||
7 | const debug = require('debug')('Franz:feature:delayApp'); | 8 | const debug = require('debug')('Franz:feature:delayApp'); |
8 | 9 | ||
@@ -22,19 +23,18 @@ function setVisibility(value) { | |||
22 | } | 23 | } |
23 | 24 | ||
24 | export default function init(stores) { | 25 | export default function init(stores) { |
25 | reaction( | 26 | debug('Initializing `delayApp` feature'); |
26 | () => stores.features.features.needToWaitToProceed, | ||
27 | (enabled, r) => { | ||
28 | if (enabled) { | ||
29 | debug('Initializing `delayApp` feature'); | ||
30 | 27 | ||
31 | // Dispose the reaction to run this only once | 28 | let shownAfterLaunch = false; |
32 | r.dispose(); | 29 | let timeLastDelay = moment(); |
33 | 30 | ||
34 | const { needToWaitToProceedConfig: globalConfig } = stores.features.features; | 31 | reaction( |
32 | () => stores.features.features.needToWaitToProceed && !stores.user.data.isPremium, | ||
33 | (isEnabled) => { | ||
34 | if (isEnabled) { | ||
35 | debug('Enabling `delayApp` feature'); | ||
35 | 36 | ||
36 | let shownAfterLaunch = false; | 37 | const { needToWaitToProceedConfig: globalConfig } = stores.features.features; |
37 | let timeLastDelay = moment(); | ||
38 | 38 | ||
39 | config.delayOffset = globalConfig.delayOffset !== undefined ? globalConfig.delayOffset : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.delayOffset; | 39 | config.delayOffset = globalConfig.delayOffset !== undefined ? globalConfig.delayOffset : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.delayOffset; |
40 | config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait; | 40 | config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait; |
@@ -50,6 +50,7 @@ export default function init(stores) { | |||
50 | debug(`App will be delayed for ${config.delayDuration / 1000}s`); | 50 | debug(`App will be delayed for ${config.delayDuration / 1000}s`); |
51 | 51 | ||
52 | setVisibility(true); | 52 | setVisibility(true); |
53 | gaEvent('delayApp', 'show', 'Delay App Feature'); | ||
53 | 54 | ||
54 | timeLastDelay = moment(); | 55 | timeLastDelay = moment(); |
55 | shownAfterLaunch = true; | 56 | shownAfterLaunch = true; |
@@ -61,6 +62,8 @@ export default function init(stores) { | |||
61 | }, DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait + 1000); // timer needs to be able to hit 0 | 62 | }, DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait + 1000); // timer needs to be able to hit 0 |
62 | } | 63 | } |
63 | }); | 64 | }); |
65 | } else { | ||
66 | setVisibility(false); | ||
64 | } | 67 | } |
65 | }, | 68 | }, |
66 | ); | 69 | ); |
diff --git a/src/index.js b/src/index.js index 75da4ff88..a3c770797 100644 --- a/src/index.js +++ b/src/index.js | |||
@@ -1,20 +1,26 @@ | |||
1 | import { | 1 | import { |
2 | app, BrowserWindow, shell, ipcMain, | 2 | app, |
3 | BrowserWindow, | ||
4 | shell, | ||
5 | ipcMain, | ||
3 | } from 'electron'; | 6 | } from 'electron'; |
4 | 7 | import isDevMode from 'electron-is-dev'; | |
5 | import fs from 'fs-extra'; | 8 | import fs from 'fs-extra'; |
6 | import path from 'path'; | 9 | import path from 'path'; |
7 | import windowStateKeeper from 'electron-window-state'; | 10 | import windowStateKeeper from 'electron-window-state'; |
8 | 11 | ||
9 | import { | 12 | // Set app directory before loading user modules |
10 | isDevMode, isMac, isWindows, isLinux, | ||
11 | } from './environment'; | ||
12 | |||
13 | // DEV MODE: Save user data into FranzDev | ||
14 | if (isDevMode) { | 13 | if (isDevMode) { |
15 | app.setPath('userData', path.join(app.getPath('appData'), 'FranzDev')); | 14 | app.setPath('userData', path.join(app.getPath('appData'), 'FranzDev')); |
16 | } | 15 | } |
16 | |||
17 | /* eslint-disable import/first */ | 17 | /* eslint-disable import/first */ |
18 | import { | ||
19 | isMac, | ||
20 | isWindows, | ||
21 | isLinux, | ||
22 | } from './environment'; | ||
23 | import { mainIpcHandler as basicAuthHandler } from './features/basicAuth'; | ||
18 | import ipcApi from './electron/ipc-api'; | 24 | import ipcApi from './electron/ipc-api'; |
19 | import Tray from './lib/Tray'; | 25 | import Tray from './lib/Tray'; |
20 | import Settings from './electron/Settings'; | 26 | import Settings from './electron/Settings'; |
@@ -36,6 +42,17 @@ const debug = require('debug')('Franz:App'); | |||
36 | let mainWindow; | 42 | let mainWindow; |
37 | let willQuitApp = false; | 43 | let willQuitApp = false; |
38 | 44 | ||
45 | // Register methods to be called once the window has been loaded. | ||
46 | let onDidLoadFns = []; | ||
47 | |||
48 | function onDidLoad(fn) { | ||
49 | if (onDidLoadFns) { | ||
50 | onDidLoadFns.push(fn); | ||
51 | } else if (mainWindow) { | ||
52 | fn(mainWindow); | ||
53 | } | ||
54 | } | ||
55 | |||
39 | // Ensure that the recipe directory exists | 56 | // Ensure that the recipe directory exists |
40 | fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp')); | 57 | fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp')); |
41 | fs.ensureFileSync(path.join(app.getPath('userData'), 'window-state.json')); | 58 | fs.ensureFileSync(path.join(app.getPath('userData'), 'window-state.json')); |
@@ -57,28 +74,25 @@ if (!gotTheLock) { | |||
57 | mainWindow.focus(); | 74 | mainWindow.focus(); |
58 | 75 | ||
59 | if (isWindows) { | 76 | if (isWindows) { |
60 | // Keep only command line / deep linked arguments | 77 | onDidLoad((window) => { |
61 | const url = argv.slice(1); | 78 | // Keep only command line / deep linked arguments |
62 | 79 | const url = argv.slice(1); | |
63 | if (url) { | 80 | if (url) { |
64 | handleDeepLink(mainWindow, url.toString()); | 81 | handleDeepLink(window, url.toString()); |
65 | } | 82 | } |
66 | } | 83 | |
67 | 84 | if (argv.includes('--reset-window')) { | |
68 | if (argv.includes('--reset-window')) { | 85 | // Needs to be delayed to not interfere with mainWindow.restore(); |
69 | // Needs to be delayed to not interfere with mainWindow.restore(); | 86 | setTimeout(() => { |
70 | setTimeout(() => { | 87 | debug('Resetting windows via Task'); |
71 | debug('Resetting windows via Task'); | 88 | window.setPosition(DEFAULT_WINDOW_OPTIONS.x + 100, DEFAULT_WINDOW_OPTIONS.y + 100); |
72 | mainWindow.setPosition(DEFAULT_WINDOW_OPTIONS.x + 100, DEFAULT_WINDOW_OPTIONS.y + 100); | 89 | window.setSize(DEFAULT_WINDOW_OPTIONS.width, DEFAULT_WINDOW_OPTIONS.height); |
73 | mainWindow.setSize(DEFAULT_WINDOW_OPTIONS.width, DEFAULT_WINDOW_OPTIONS.height); | 90 | }, 1); |
74 | }, 1); | 91 | } |
92 | }); | ||
75 | } | 93 | } |
76 | } | 94 | } |
77 | }); | 95 | }); |
78 | |||
79 | // Create myWindow, load the rest of the app, etc... | ||
80 | app.on('ready', () => { | ||
81 | }); | ||
82 | } | 96 | } |
83 | // const isSecondInstance = app.makeSingleInstance((argv) => { | 97 | // const isSecondInstance = app.makeSingleInstance((argv) => { |
84 | // if (mainWindow) { | 98 | // if (mainWindow) { |
@@ -153,6 +167,17 @@ const createWindow = () => { | |||
153 | titleBarStyle: isMac ? 'hidden' : '', | 167 | titleBarStyle: isMac ? 'hidden' : '', |
154 | frame: isLinux, | 168 | frame: isLinux, |
155 | backgroundColor: !settings.get('darkMode') ? '#3498db' : '#1E1E1E', | 169 | backgroundColor: !settings.get('darkMode') ? '#3498db' : '#1E1E1E', |
170 | webPreferences: { | ||
171 | nodeIntegration: true, | ||
172 | }, | ||
173 | }); | ||
174 | |||
175 | mainWindow.webContents.on('did-finish-load', () => { | ||
176 | const fns = onDidLoadFns; | ||
177 | onDidLoadFns = null; | ||
178 | for (const fn of fns) { | ||
179 | fn(mainWindow); | ||
180 | } | ||
156 | }); | 181 | }); |
157 | 182 | ||
158 | // Initialize System Tray | 183 | // Initialize System Tray |
@@ -179,6 +204,16 @@ const createWindow = () => { | |||
179 | mainWindow.webContents.openDevTools(); | 204 | mainWindow.webContents.openDevTools(); |
180 | } | 205 | } |
181 | 206 | ||
207 | // Windows deep linking handling on app launch | ||
208 | if (isWindows) { | ||
209 | onDidLoad((window) => { | ||
210 | const url = process.argv.slice(1); | ||
211 | if (url) { | ||
212 | handleDeepLink(window, url.toString()); | ||
213 | } | ||
214 | }); | ||
215 | } | ||
216 | |||
182 | // Emitted when the window is closed. | 217 | // Emitted when the window is closed. |
183 | mainWindow.on('close', (e) => { | 218 | mainWindow.on('close', (e) => { |
184 | // Dereference the window object, usually you would store windows | 219 | // Dereference the window object, usually you would store windows |
@@ -248,6 +283,13 @@ const createWindow = () => { | |||
248 | // initialization and is ready to create browser windows. | 283 | // initialization and is ready to create browser windows. |
249 | // Some APIs can only be used after this event occurs. | 284 | // Some APIs can only be used after this event occurs. |
250 | app.on('ready', () => { | 285 | app.on('ready', () => { |
286 | // Register App URL | ||
287 | app.setAsDefaultProtocolClient('franz'); | ||
288 | |||
289 | if (isDevMode) { | ||
290 | app.setAsDefaultProtocolClient('franz-dev'); | ||
291 | } | ||
292 | |||
251 | if (process.platform === 'win32') { | 293 | if (process.platform === 'win32') { |
252 | app.setUserTasks([{ | 294 | app.setUserTasks([{ |
253 | program: process.execPath, | 295 | program: process.execPath, |
@@ -263,23 +305,43 @@ app.on('ready', () => { | |||
263 | }); | 305 | }); |
264 | 306 | ||
265 | // This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕 | 307 | // This is the worst possible implementation as the webview.webContents based callback doesn't work 🖕 |
308 | // TODO: rewrite to handle multiple login calls | ||
309 | const noop = () => null; | ||
310 | let authCallback = noop; | ||
266 | app.on('login', (event, webContents, request, authInfo, callback) => { | 311 | app.on('login', (event, webContents, request, authInfo, callback) => { |
267 | event.preventDefault(); | 312 | authCallback = callback; |
268 | debug('browser login event', authInfo); | 313 | debug('browser login event', authInfo); |
314 | event.preventDefault(); | ||
269 | if (authInfo.isProxy && authInfo.scheme === 'basic') { | 315 | if (authInfo.isProxy && authInfo.scheme === 'basic') { |
270 | webContents.send('get-service-id'); | 316 | webContents.send('get-service-id'); |
271 | 317 | ||
272 | ipcMain.on('service-id', (e, id) => { | 318 | ipcMain.once('service-id', (e, id) => { |
273 | debug('Received service id', id); | 319 | debug('Received service id', id); |
274 | 320 | ||
275 | const ps = proxySettings.get(id); | 321 | const ps = proxySettings.get(id); |
276 | callback(ps.user, ps.password); | 322 | callback(ps.user, ps.password); |
277 | }); | 323 | }); |
278 | } else { | 324 | } else if (authInfo.scheme === 'basic') { |
279 | // TODO: implement basic auth | 325 | debug('basic auth handler', authInfo); |
326 | basicAuthHandler(mainWindow, authInfo); | ||
280 | } | 327 | } |
281 | }); | 328 | }); |
282 | 329 | ||
330 | // TODO: evaluate if we need to store the authCallback for every service | ||
331 | ipcMain.on('feature-basic-auth-credentials', (e, { user, password }) => { | ||
332 | debug('Received basic auth credentials', user, '********'); | ||
333 | |||
334 | authCallback(user, password); | ||
335 | authCallback = noop; | ||
336 | }); | ||
337 | |||
338 | ipcMain.on('feature-basic-auth-cancel', () => { | ||
339 | debug('Cancel basic auth'); | ||
340 | |||
341 | authCallback(null); | ||
342 | authCallback = noop; | ||
343 | }); | ||
344 | |||
283 | // Quit when all windows are closed. | 345 | // Quit when all windows are closed. |
284 | app.on('window-all-closed', () => { | 346 | app.on('window-all-closed', () => { |
285 | // On OS X it is common for applications and their menu bar | 347 | // On OS X it is common for applications and their menu bar |
@@ -305,13 +367,13 @@ app.on('activate', () => { | |||
305 | }); | 367 | }); |
306 | 368 | ||
307 | app.on('will-finish-launching', () => { | 369 | app.on('will-finish-launching', () => { |
308 | // Protocol handler for osx | 370 | // Protocol handler for macOS |
309 | app.on('open-url', (event, url) => { | 371 | app.on('open-url', (event, url) => { |
310 | event.preventDefault(); | 372 | event.preventDefault(); |
311 | console.log(`open-url event: ${url}`); | 373 | |
312 | handleDeepLink(mainWindow, url); | 374 | onDidLoad((window) => { |
375 | debug('open-url event', url); | ||
376 | handleDeepLink(window, url); | ||
377 | }); | ||
313 | }); | 378 | }); |
314 | }); | 379 | }); |
315 | |||
316 | // Register App URL | ||
317 | app.setAsDefaultProtocolClient('franz'); | ||
diff --git a/src/lib/analytics.js b/src/lib/analytics.js index 8b9a44579..7044e5bb7 100644 --- a/src/lib/analytics.js +++ b/src/lib/analytics.js | |||
@@ -1,6 +1,5 @@ | |||
1 | import { remote } from 'electron'; | 1 | import { remote } from 'electron'; |
2 | import { GA_ID } from '../config'; | 2 | import { GA_ID } from '../config'; |
3 | // import { isDevMode } from '../environment'; | ||
4 | 3 | ||
5 | const debug = require('debug')('Franz:Analytics'); | 4 | const debug = require('debug')('Franz:Analytics'); |
6 | 5 | ||
@@ -36,7 +35,7 @@ export function gaPage(page) { | |||
36 | export function gaEvent(category, action, label) { | 35 | export function gaEvent(category, action, label) { |
37 | ga('send', 'event', category, action, label); | 36 | ga('send', 'event', category, action, label); |
38 | 37 | ||
39 | debug('GA track page', category, action); | 38 | debug('GA track event', category, action); |
40 | } | 39 | } |
41 | 40 | ||
42 | setTimeout(() => { | 41 | setTimeout(() => { |
diff --git a/src/models/Service.js b/src/models/Service.js index 7a955a6d9..8a2a8d3d8 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -15,7 +15,7 @@ export default class Service { | |||
15 | 15 | ||
16 | events = {}; | 16 | events = {}; |
17 | 17 | ||
18 | isAttached = false; | 18 | @observable isAttached = false; |
19 | 19 | ||
20 | @observable isActive = false; // Is current webview active | 20 | @observable isActive = false; // Is current webview active |
21 | 21 | ||
@@ -202,7 +202,7 @@ export default class Service { | |||
202 | 202 | ||
203 | this.webview.addEventListener('did-fail-load', (event) => { | 203 | this.webview.addEventListener('did-fail-load', (event) => { |
204 | debug('Service failed to load', this.name, event); | 204 | debug('Service failed to load', this.name, event); |
205 | if (event.isMainFrame && event.errorCode !== -3) { | 205 | if (event.isMainFrame && event.errorCode !== -21 && event.errorCode !== -3) { |
206 | this.isError = true; | 206 | this.isError = true; |
207 | this.errorMessage = event.errorDescription; | 207 | this.errorMessage = event.errorDescription; |
208 | this.isLoading = false; | 208 | this.isLoading = false; |
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index dd4642d70..b21d48a11 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js | |||
@@ -143,10 +143,13 @@ export default class AppStore extends Store { | |||
143 | 143 | ||
144 | // Handle deep linking (franz://) | 144 | // Handle deep linking (franz://) |
145 | ipcRenderer.on('navigateFromDeepLink', (event, data) => { | 145 | ipcRenderer.on('navigateFromDeepLink', (event, data) => { |
146 | const { url } = data; | 146 | debug('Navigate from deep link', data); |
147 | let { url } = data; | ||
147 | if (!url) return; | 148 | if (!url) return; |
148 | 149 | ||
149 | this.stores.router.push(data.url); | 150 | url = url.replace(/\/$/, ''); |
151 | |||
152 | this.stores.router.push(url); | ||
150 | }); | 153 | }); |
151 | 154 | ||
152 | // Set active the next service | 155 | // Set active the next service |
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 2a0713b6f..0adee6adf 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js | |||
@@ -1,4 +1,4 @@ | |||
1 | import { computed, observable } from 'mobx'; | 1 | import { computed, observable, reaction } from 'mobx'; |
2 | 2 | ||
3 | import Store from './lib/Store'; | 3 | import Store from './lib/Store'; |
4 | import CachedRequest from './lib/CachedRequest'; | 4 | import CachedRequest from './lib/CachedRequest'; |
@@ -6,6 +6,7 @@ import CachedRequest from './lib/CachedRequest'; | |||
6 | import delayApp from '../features/delayApp'; | 6 | import delayApp from '../features/delayApp'; |
7 | import spellchecker from '../features/spellchecker'; | 7 | import spellchecker from '../features/spellchecker'; |
8 | import serviceProxy from '../features/serviceProxy'; | 8 | import serviceProxy from '../features/serviceProxy'; |
9 | import basicAuth from '../features/basicAuth'; | ||
9 | 10 | ||
10 | import { DEFAULT_FEATURES_CONFIG } from '../config'; | 11 | import { DEFAULT_FEATURES_CONFIG } from '../config'; |
11 | 12 | ||
@@ -21,6 +22,13 @@ export default class FeaturesStore extends Store { | |||
21 | 22 | ||
22 | await this.featuresRequest._promise; | 23 | await this.featuresRequest._promise; |
23 | setTimeout(this._enableFeatures.bind(this), 1); | 24 | setTimeout(this._enableFeatures.bind(this), 1); |
25 | |||
26 | // single key reaction | ||
27 | reaction(() => this.stores.user.data.isPremium, () => { | ||
28 | if (this.stores.user.isLoggedIn) { | ||
29 | this.featuresRequest.invalidate({ immediately: true }); | ||
30 | } | ||
31 | }); | ||
24 | } | 32 | } |
25 | 33 | ||
26 | @computed get anonymousFeatures() { | 34 | @computed get anonymousFeatures() { |
@@ -47,5 +55,6 @@ export default class FeaturesStore extends Store { | |||
47 | delayApp(this.stores, this.actions); | 55 | delayApp(this.stores, this.actions); |
48 | spellchecker(this.stores, this.actions); | 56 | spellchecker(this.stores, this.actions); |
49 | serviceProxy(this.stores, this.actions); | 57 | serviceProxy(this.stores, this.actions); |
58 | basicAuth(this.stores, this.actions); | ||
50 | } | 59 | } |
51 | } | 60 | } |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 84f84891a..efd57a09d 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -70,6 +70,7 @@ export default class ServicesStore extends Store { | |||
70 | this._mapActiveServiceToServiceModelReaction.bind(this), | 70 | this._mapActiveServiceToServiceModelReaction.bind(this), |
71 | this._saveActiveService.bind(this), | 71 | this._saveActiveService.bind(this), |
72 | this._logoutReaction.bind(this), | 72 | this._logoutReaction.bind(this), |
73 | this._handleMuteSettings.bind(this), | ||
73 | ]); | 74 | ]); |
74 | 75 | ||
75 | // Just bind this | 76 | // Just bind this |
@@ -291,6 +292,8 @@ export default class ServicesStore extends Store { | |||
291 | this.all[index].isActive = false; | 292 | this.all[index].isActive = false; |
292 | }); | 293 | }); |
293 | service.isActive = true; | 294 | service.isActive = true; |
295 | |||
296 | this._focusActiveService(); | ||
294 | } | 297 | } |
295 | 298 | ||
296 | @action _setActiveNext() { | 299 | @action _setActiveNext() { |
@@ -341,6 +344,9 @@ export default class ServicesStore extends Store { | |||
341 | const service = this.one(serviceId); | 344 | const service = this.one(serviceId); |
342 | 345 | ||
343 | if (service.webview) { | 346 | if (service.webview) { |
347 | if (document.activeElement) { | ||
348 | document.activeElement.blur(); | ||
349 | } | ||
344 | service.webview.focus(); | 350 | service.webview.focus(); |
345 | } | 351 | } |
346 | } | 352 | } |
@@ -622,6 +628,20 @@ export default class ServicesStore extends Store { | |||
622 | } | 628 | } |
623 | } | 629 | } |
624 | 630 | ||
631 | _handleMuteSettings() { | ||
632 | const { enabled } = this; | ||
633 | const { isAppMuted } = this.stores.settings.app; | ||
634 | |||
635 | enabled.forEach((service) => { | ||
636 | const { isAttached } = service; | ||
637 | const isMuted = isAppMuted || service.isMuted; | ||
638 | |||
639 | if (isAttached) { | ||
640 | service.webview.setAudioMuted(isMuted); | ||
641 | } | ||
642 | }); | ||
643 | } | ||
644 | |||
625 | _shareSettingsWithServiceProcess() { | 645 | _shareSettingsWithServiceProcess() { |
626 | const settings = this.stores.settings.app; | 646 | const settings = this.stores.settings.app; |
627 | this.actions.service.sendIPCMessageToAllServices({ | 647 | this.actions.service.sendIPCMessageToAllServices({ |
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 7addb5760..77d84afe1 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js | |||
@@ -129,10 +129,6 @@ export default class UserStore extends Store { | |||
129 | return Boolean(localStorage.getItem('authToken')); | 129 | return Boolean(localStorage.getItem('authToken')); |
130 | } | 130 | } |
131 | 131 | ||
132 | // @computed get isTokenValid() { | ||
133 | // return this.authToken !== null && moment(this.tokenExpiry).isAfter(moment()); | ||
134 | // } | ||
135 | |||
136 | @computed get isTokenExpired() { | 132 | @computed get isTokenExpired() { |
137 | if (!this.authToken) return false; | 133 | if (!this.authToken) return false; |
138 | 134 | ||
@@ -160,6 +156,14 @@ export default class UserStore extends Store { | |||
160 | gaEvent('User', 'login'); | 156 | gaEvent('User', 'login'); |
161 | } | 157 | } |
162 | 158 | ||
159 | @action _tokenLogin(authToken) { | ||
160 | this._setUserData(authToken); | ||
161 | |||
162 | this.stores.router.push('/'); | ||
163 | |||
164 | gaEvent('User', 'tokenLogin'); | ||
165 | } | ||
166 | |||
163 | @action async _signup({ | 167 | @action async _signup({ |
164 | firstname, lastname, email, password, accountType, company, | 168 | firstname, lastname, email, password, accountType, company, |
165 | }) { | 169 | }) { |
@@ -206,6 +210,8 @@ export default class UserStore extends Store { | |||
206 | } | 210 | } |
207 | 211 | ||
208 | @action async _update({ userData }) { | 212 | @action async _update({ userData }) { |
213 | if (!this.isLoggedIn) return; | ||
214 | |||
209 | const response = await this.updateUserInfoRequest.execute(userData)._promise; | 215 | const response = await this.updateUserInfoRequest.execute(userData)._promise; |
210 | 216 | ||
211 | this.getUserInfoRequest.patch(() => response.data); | 217 | this.getUserInfoRequest.patch(() => response.data); |
@@ -222,6 +228,7 @@ export default class UserStore extends Store { | |||
222 | // workaround mobx issue | 228 | // workaround mobx issue |
223 | localStorage.removeItem('authToken'); | 229 | localStorage.removeItem('authToken'); |
224 | window.localStorage.removeItem('authToken'); | 230 | window.localStorage.removeItem('authToken'); |
231 | |||
225 | this.getUserInfoRequest.invalidate().reset(); | 232 | this.getUserInfoRequest.invalidate().reset(); |
226 | this.authToken = null; | 233 | this.authToken = null; |
227 | } | 234 | } |
@@ -262,6 +269,18 @@ export default class UserStore extends Store { | |||
262 | const { router } = this.stores; | 269 | const { router } = this.stores; |
263 | const currentRoute = router.location.pathname; | 270 | const currentRoute = router.location.pathname; |
264 | if (!this.isLoggedIn | 271 | if (!this.isLoggedIn |
272 | && currentRoute.includes('token=')) { | ||
273 | router.push(this.WELCOME_ROUTE); | ||
274 | const token = currentRoute.split('=')[1]; | ||
275 | |||
276 | const data = this._parseToken(token); | ||
277 | if (data) { | ||
278 | // Give this some time to sink | ||
279 | setTimeout(() => { | ||
280 | this._tokenLogin(token); | ||
281 | }, 1000); | ||
282 | } | ||
283 | } else if (!this.isLoggedIn | ||
265 | && !currentRoute.includes(this.BASE_ROUTE)) { | 284 | && !currentRoute.includes(this.BASE_ROUTE)) { |
266 | router.push(this.WELCOME_ROUTE); | 285 | router.push(this.WELCOME_ROUTE); |
267 | } else if (this.isLoggedIn | 286 | } else if (this.isLoggedIn |
diff --git a/src/styles/layout.scss b/src/styles/layout.scss index ebf468cf0..8b7cc512a 100644 --- a/src/styles/layout.scss +++ b/src/styles/layout.scss | |||
@@ -19,7 +19,7 @@ html { overflow: hidden; } | |||
19 | 19 | ||
20 | &:hover, | 20 | &:hover, |
21 | &:active { color: $dark-theme-gray-smoke; } | 21 | &:active { color: $dark-theme-gray-smoke; } |
22 | &.is-muted { color: $dark-theme-gray; } | 22 | &.is-muted { color: $theme-brand-primary; } |
23 | } | 23 | } |
24 | } | 24 | } |
25 | 25 | ||
diff --git a/src/styles/services.scss b/src/styles/services.scss index 0e559501c..5acf92d2c 100644 --- a/src/styles/services.scss +++ b/src/styles/services.scss | |||
@@ -39,18 +39,12 @@ | |||
39 | webview { | 39 | webview { |
40 | background: $theme-gray-lighter; | 40 | background: $theme-gray-lighter; |
41 | display: inline-flex; | 41 | display: inline-flex; |
42 | height: 0; | 42 | height: 100%; |
43 | width: 0; | 43 | width: 100%; |
44 | } | 44 | } |
45 | 45 | ||
46 | &.is-active { | 46 | &.is-active { |
47 | z-index: 100; | 47 | z-index: 100; |
48 | |||
49 | webview { | ||
50 | flex: 0 1; | ||
51 | height: 100%; | ||
52 | width: 100%; | ||
53 | } | ||
54 | } | 48 | } |
55 | 49 | ||
56 | &--force-repaint webview { z-index: 5; } | 50 | &--force-repaint webview { z-index: 5; } |
diff --git a/src/theme/dark/index.js b/src/theme/dark/index.js index fb1713184..8fdb321f3 100644 --- a/src/theme/dark/index.js +++ b/src/theme/dark/index.js | |||
@@ -8,6 +8,12 @@ export const colorBackgroundSubscriptionContainer = legacyStyles.themeBrandInfo; | |||
8 | export const colorHeadline = legacyStyles.darkThemeTextColor; | 8 | export const colorHeadline = legacyStyles.darkThemeTextColor; |
9 | export const colorText = legacyStyles.darkThemeTextColor; | 9 | export const colorText = legacyStyles.darkThemeTextColor; |
10 | 10 | ||
11 | // Error Handler | ||
12 | export const colorWebviewErrorHandlerBackground = legacyStyles.darkThemeGrayDarkest; | ||
13 | |||
11 | // Loader | 14 | // Loader |
12 | export const colorFullscreenLoaderSpinner = '#FFF'; | 15 | export const colorFullscreenLoaderSpinner = '#FFF'; |
13 | export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.5); | 16 | export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.5); |
17 | |||
18 | // Modal | ||
19 | export const colorModalOverlayBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.8); | ||
diff --git a/src/theme/default/index.js b/src/theme/default/index.js index 37827621c..61410073d 100644 --- a/src/theme/default/index.js +++ b/src/theme/default/index.js | |||
@@ -23,7 +23,13 @@ export const colorSubscriptionContainerTitle = brandPrimary; | |||
23 | export const colorSubscriptionContainerActionButtonBackground = brandPrimary; | 23 | export const colorSubscriptionContainerActionButtonBackground = brandPrimary; |
24 | export const colorSubscriptionContainerActionButtonColor = '#FFF'; | 24 | export const colorSubscriptionContainerActionButtonColor = '#FFF'; |
25 | 25 | ||
26 | // Error Handler | ||
27 | export const colorWebviewErrorHandlerBackground = legacyStyles.themeGrayLighter; | ||
28 | |||
26 | // Loader | 29 | // Loader |
27 | export const colorAppLoaderSpinner = '#FFF'; | 30 | export const colorAppLoaderSpinner = '#FFF'; |
28 | export const colorFullscreenLoaderSpinner = legacyStyles.themeGrayDark; | 31 | export const colorFullscreenLoaderSpinner = legacyStyles.themeGrayDark; |
29 | export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8); | 32 | export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8); |
33 | |||
34 | // Modal | ||
35 | export const colorModalOverlayBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8); | ||
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js index bd099987d..a76c03e5a 100644 --- a/src/webview/contextMenu.js +++ b/src/webview/contextMenu.js | |||
@@ -277,6 +277,6 @@ export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, get | |||
277 | ), | 277 | ), |
278 | ); | 278 | ); |
279 | 279 | ||
280 | menu.popup(remote.getCurrentWindow()); | 280 | menu.popup(); |
281 | }); | 281 | }); |
282 | } | 282 | } |
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index 9aa89ce01..c718b348e 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js | |||
@@ -73,7 +73,7 @@ class RecipeController { | |||
73 | require(modulePath)(new RecipeWebview(), {...config, recipe,}); | 73 | require(modulePath)(new RecipeWebview(), {...config, recipe,}); |
74 | debug('Initialize Recipe', config, recipe); | 74 | debug('Initialize Recipe', config, recipe); |
75 | 75 | ||
76 | this.settings.service = config; | 76 | this.settings.service = Object.assign(config, { recipe }); |
77 | } catch (err) { | 77 | } catch (err) { |
78 | console.error('Recipe initialization failed', err); | 78 | console.error('Recipe initialization failed', err); |
79 | } | 79 | } |