aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.js1
-rw-r--r--src/components/layout/AppLayout.js13
-rw-r--r--src/components/services/content/ErrorHandlers/WebviewErrorHandler.js2
-rw-r--r--src/components/services/content/ErrorHandlers/styles.js6
-rw-r--r--src/components/services/content/ServiceWebview.js3
-rw-r--r--src/components/services/content/Services.js3
-rw-r--r--src/components/ui/Modal/index.js59
-rw-r--r--src/components/ui/Modal/styles.js32
-rw-r--r--src/containers/layout/AppLayoutContainer.js1
-rw-r--r--src/containers/settings/AccountScreen.js8
-rw-r--r--src/electron/ipc-api/settings.js1
-rw-r--r--src/environment.js6
-rw-r--r--src/features/basicAuth/Component.js102
-rw-r--r--src/features/basicAuth/Form.js17
-rw-r--r--src/features/basicAuth/index.js68
-rw-r--r--src/features/basicAuth/mainIpcHandler.js9
-rw-r--r--src/features/basicAuth/styles.js12
-rw-r--r--src/features/delayApp/Component.js14
-rw-r--r--src/features/delayApp/index.js23
-rw-r--r--src/index.js134
-rw-r--r--src/lib/analytics.js3
-rw-r--r--src/models/Service.js4
-rw-r--r--src/stores/AppStore.js7
-rw-r--r--src/stores/FeaturesStore.js11
-rw-r--r--src/stores/ServicesStore.js20
-rw-r--r--src/stores/UserStore.js27
-rw-r--r--src/styles/layout.scss2
-rw-r--r--src/styles/services.scss10
-rw-r--r--src/theme/dark/index.js6
-rw-r--r--src/theme/default/index.js6
-rw-r--r--src/webview/contextMenu.js2
-rw-r--r--src/webview/recipe.js2
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
7import InfoBar from '../ui/InfoBar'; 7import InfoBar from '../ui/InfoBar';
8import { Component as DelayApp } from '../../features/delayApp'; 8import { Component as DelayApp } from '../../features/delayApp';
9import { Component as BasicAuth } from '../../features/basicAuth';
9import ErrorBoundary from '../util/ErrorBoundary'; 10import ErrorBoundary from '../util/ErrorBoundary';
10 11
11import globalMessages from '../../i18n/globalMessages'; 12// import globalMessages from '../../i18n/globalMessages';
12 13
13import { isWindows } from '../../environment'; 14import { 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
34export default @injectSheet(styles) @observer class WebviewCrashHandler extends Component { 34export 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 @@
1export default { 1export 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 @@
1import React, { Component } from 'react';
2import ReactModal from 'react-modal';
3import PropTypes from 'prop-types';
4import classnames from 'classnames';
5import injectCSS from 'react-jss';
6
7import styles from './styles';
8
9export 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 @@
1export 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';
14const { BrowserWindow } = remote; 14const { BrowserWindow } = remote;
15 15
16export default @inject('stores', 'actions') @observer class AccountScreen extends Component { 16export 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
3export default (params) => { 3export 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 @@
1import isDev from 'electron-is-dev';
2
1import { LIVE_API, DEV_API, LOCAL_API } from './config'; 3import { LIVE_API, DEV_API, LOCAL_API } from './config';
2 4
3export const isDevMode = Boolean(process.execPath.match(/[\\/]electron/)); 5export const isDevMode = isDev;
4export const useLiveAPI = process.env.LIVE_API; 6export const useLiveAPI = process.env.LIVE_API;
5export const useLocalAPI = process.env.LOCAL_API; 7export const useLocalAPI = process.env.LOCAL_API;
6 8
7let platform = process.platform; 9let { platform } = process;
8if (process.env.OS_PLATFORM) { 10if (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 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import injectSheet from 'react-jss';
4import { observer } from 'mobx-react';
5import classnames from 'classnames';
6
7import Modal from '../../components/ui/Modal';
8import Input from '../../components/ui/Input';
9import Button from '../../components/ui/Button';
10
11import {
12 state,
13 resetState,
14 sendCredentials,
15 cancelLogin,
16} from '.';
17import Form from './Form';
18
19import styles from './styles';
20
21export 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 @@
1import Form from '../../lib/Form';
2
3export 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 @@
1import { ipcRenderer } from 'electron';
2import { observable } from 'mobx';
3
4import BasicAuthComponent from './Component';
5
6const debug = require('debug')('Franz:feature:basicAuth');
7
8const defaultState = {
9 isModalVisible: false,
10 service: null,
11 authInfo: null,
12};
13
14export const state = observable(defaultState);
15
16export function resetState() {
17 Object.assign(state, defaultState);
18 console.log('reset state', state);
19}
20
21export 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
45export 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
53export 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
62export function cancelLogin() {
63 debug('Cancel basic auth event');
64
65 ipcRenderer.send('feature-basic-auth-cancel');
66}
67
68export 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 @@
1const debug = require('debug')('Franz:feature:basicAuth:main');
2
3export 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 @@
1export 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';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
6 6
7import { gaEvent } from '../../lib/analytics';
8
7import Button from '../../components/ui/Button'; 9import Button from '../../components/ui/Button';
8 10
9import { config } from '.'; 11import { 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';
3import DelayAppComponent from './Component'; 3import DelayAppComponent from './Component';
4 4
5import { DEFAULT_FEATURES_CONFIG } from '../../config'; 5import { DEFAULT_FEATURES_CONFIG } from '../../config';
6import { gaEvent } from '../../lib/analytics';
6 7
7const debug = require('debug')('Franz:feature:delayApp'); 8const debug = require('debug')('Franz:feature:delayApp');
8 9
@@ -22,19 +23,18 @@ function setVisibility(value) {
22} 23}
23 24
24export default function init(stores) { 25export 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 @@
1import { 1import {
2 app, BrowserWindow, shell, ipcMain, 2 app,
3 BrowserWindow,
4 shell,
5 ipcMain,
3} from 'electron'; 6} from 'electron';
4 7import isDevMode from 'electron-is-dev';
5import fs from 'fs-extra'; 8import fs from 'fs-extra';
6import path from 'path'; 9import path from 'path';
7import windowStateKeeper from 'electron-window-state'; 10import windowStateKeeper from 'electron-window-state';
8 11
9import { 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
14if (isDevMode) { 13if (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 */
18import {
19 isMac,
20 isWindows,
21 isLinux,
22} from './environment';
23import { mainIpcHandler as basicAuthHandler } from './features/basicAuth';
18import ipcApi from './electron/ipc-api'; 24import ipcApi from './electron/ipc-api';
19import Tray from './lib/Tray'; 25import Tray from './lib/Tray';
20import Settings from './electron/Settings'; 26import Settings from './electron/Settings';
@@ -36,6 +42,17 @@ const debug = require('debug')('Franz:App');
36let mainWindow; 42let mainWindow;
37let willQuitApp = false; 43let willQuitApp = false;
38 44
45// Register methods to be called once the window has been loaded.
46let onDidLoadFns = [];
47
48function 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
40fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp')); 57fs.emptyDirSync(path.join(app.getPath('userData'), 'recipes', 'temp'));
41fs.ensureFileSync(path.join(app.getPath('userData'), 'window-state.json')); 58fs.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.
250app.on('ready', () => { 285app.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
309const noop = () => null;
310let authCallback = noop;
266app.on('login', (event, webContents, request, authInfo, callback) => { 311app.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
331ipcMain.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
338ipcMain.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.
284app.on('window-all-closed', () => { 346app.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
307app.on('will-finish-launching', () => { 369app.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
317app.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 @@
1import { remote } from 'electron'; 1import { remote } from 'electron';
2import { GA_ID } from '../config'; 2import { GA_ID } from '../config';
3// import { isDevMode } from '../environment';
4 3
5const debug = require('debug')('Franz:Analytics'); 4const debug = require('debug')('Franz:Analytics');
6 5
@@ -36,7 +35,7 @@ export function gaPage(page) {
36export function gaEvent(category, action, label) { 35export 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
42setTimeout(() => { 41setTimeout(() => {
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 @@
1import { computed, observable } from 'mobx'; 1import { computed, observable, reaction } from 'mobx';
2 2
3import Store from './lib/Store'; 3import Store from './lib/Store';
4import CachedRequest from './lib/CachedRequest'; 4import CachedRequest from './lib/CachedRequest';
@@ -6,6 +6,7 @@ import CachedRequest from './lib/CachedRequest';
6import delayApp from '../features/delayApp'; 6import delayApp from '../features/delayApp';
7import spellchecker from '../features/spellchecker'; 7import spellchecker from '../features/spellchecker';
8import serviceProxy from '../features/serviceProxy'; 8import serviceProxy from '../features/serviceProxy';
9import basicAuth from '../features/basicAuth';
9 10
10import { DEFAULT_FEATURES_CONFIG } from '../config'; 11import { 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;
8export const colorHeadline = legacyStyles.darkThemeTextColor; 8export const colorHeadline = legacyStyles.darkThemeTextColor;
9export const colorText = legacyStyles.darkThemeTextColor; 9export const colorText = legacyStyles.darkThemeTextColor;
10 10
11// Error Handler
12export const colorWebviewErrorHandlerBackground = legacyStyles.darkThemeGrayDarkest;
13
11// Loader 14// Loader
12export const colorFullscreenLoaderSpinner = '#FFF'; 15export const colorFullscreenLoaderSpinner = '#FFF';
13export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.5); 16export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.darkThemeGrayDarkest, 0.5);
17
18// Modal
19export 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;
23export const colorSubscriptionContainerActionButtonBackground = brandPrimary; 23export const colorSubscriptionContainerActionButtonBackground = brandPrimary;
24export const colorSubscriptionContainerActionButtonColor = '#FFF'; 24export const colorSubscriptionContainerActionButtonColor = '#FFF';
25 25
26// Error Handler
27export const colorWebviewErrorHandlerBackground = legacyStyles.themeGrayLighter;
28
26// Loader 29// Loader
27export const colorAppLoaderSpinner = '#FFF'; 30export const colorAppLoaderSpinner = '#FFF';
28export const colorFullscreenLoaderSpinner = legacyStyles.themeGrayDark; 31export const colorFullscreenLoaderSpinner = legacyStyles.themeGrayDark;
29export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8); 32export const colorWebviewLoaderBackground = hexToRgba(legacyStyles.themeGrayLighter, 0.8);
33
34// Modal
35export 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 }