aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/services')
-rw-r--r--src/components/services/content/ServiceView.js73
-rw-r--r--src/components/services/content/ServiceWebview.js5
-rw-r--r--src/components/services/content/Services.js6
-rw-r--r--src/components/services/tabs/TabItem.js87
4 files changed, 103 insertions, 68 deletions
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index d91016c71..444d5fea4 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -1,6 +1,6 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { autorun, reaction } from 'mobx'; 3import { autorun } from 'mobx';
4import { observer, inject } from 'mobx-react'; 4import { observer, inject } from 'mobx-react';
5import classnames from 'classnames'; 5import classnames from 'classnames';
6 6
@@ -27,11 +27,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
27 stores: PropTypes.shape({ 27 stores: PropTypes.shape({
28 settings: PropTypes.instanceOf(SettingsStore).isRequired, 28 settings: PropTypes.instanceOf(SettingsStore).isRequired,
29 }).isRequired, 29 }).isRequired,
30 actions: PropTypes.shape({ 30 isSpellcheckerEnabled: PropTypes.bool.isRequired,
31 service: PropTypes.shape({
32 setHibernation: PropTypes.func.isRequired,
33 }).isRequired,
34 }).isRequired,
35 }; 31 };
36 32
37 static defaultProps = { 33 static defaultProps = {
@@ -50,12 +46,6 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
50 46
51 forceRepaintTimeout = null; 47 forceRepaintTimeout = null;
52 48
53 constructor(props) {
54 super(props);
55
56 this.startHibernationTimer = this.startHibernationTimer.bind(this);
57 }
58
59 componentDidMount() { 49 componentDidMount() {
60 this.autorunDisposer = autorun(() => { 50 this.autorunDisposer = autorun(() => {
61 if (this.props.service.isActive) { 51 if (this.props.service.isActive) {
@@ -65,32 +55,6 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
65 }, 100); 55 }, 100);
66 } 56 }
67 }); 57 });
68
69 reaction(
70 () => this.props.service.isActive,
71 () => {
72 if (!this.props.service.isActive && this.props.stores.settings.all.app.hibernate) {
73 // Service is inactive - start hibernation countdown
74 this.startHibernationTimer();
75 } else {
76 if (this.hibernationTimer) {
77 // Service is active but we have an active hibernation timer: Clear timeout
78 clearTimeout(this.hibernationTimer);
79 }
80
81 // Service is active, wake up service from hibernation
82 this.props.actions.service.setHibernation({
83 serviceId: this.props.service.id,
84 hibernating: false,
85 });
86 }
87 },
88 );
89
90 // Start hibernation counter if we are in background
91 if (!this.props.service.isActive && this.props.stores.settings.all.app.hibernate) {
92 this.startHibernationTimer();
93 }
94 } 58 }
95 59
96 componentWillUnmount() { 60 componentWillUnmount() {
@@ -110,19 +74,6 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
110 }); 74 });
111 }; 75 };
112 76
113 startHibernationTimer() {
114 const timerDuration = (Number(this.props.stores.settings.all.app.hibernationStrategy) || 300) * 1000;
115
116 const hibernationTimer = setTimeout(() => {
117 this.props.actions.service.setHibernation({
118 serviceId: this.props.service.id,
119 hibernating: true,
120 });
121 }, timerDuration);
122
123 this.hibernationTimer = hibernationTimer;
124 }
125
126 render() { 77 render() {
127 const { 78 const {
128 detachService, 79 detachService,
@@ -132,6 +83,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
132 edit, 83 edit,
133 enable, 84 enable,
134 stores, 85 stores,
86 isSpellcheckerEnabled,
135 } = this.props; 87 } = this.props;
136 88
137 const { 89 const {
@@ -193,22 +145,19 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
193 </Fragment> 145 </Fragment>
194 ) : ( 146 ) : (
195 <> 147 <>
196 {(!service.isHibernating || service.disableHibernation) ? ( 148 {(!service.isHibernating || service.isHibernationEnabled) ? (
197 <> 149 <>
198 {showNavBar && ( 150 {showNavBar && (
199 <WebControlsScreen service={service} /> 151 <WebControlsScreen service={service} />
200 )} 152 )}
201 <ServiceWebview 153 {!service.isHibernating && (
202 service={service} 154 <ServiceWebview
203 setWebviewReference={setWebviewReference} 155 service={service}
204 detachService={detachService} 156 setWebviewReference={setWebviewReference}
205 /> 157 detachService={detachService}
206 {/* {service.lostRecipeConnection && ( 158 isSpellcheckerEnabled={isSpellcheckerEnabled}
207 <ConnectionLostBanner
208 name={service.name}
209 reload={reload}
210 /> 159 />
211 )} */} 160 )}
212 </> 161 </>
213 ) : ( 162 ) : (
214 <div> 163 <div>
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index 2e3354279..4edbde5e2 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -15,6 +15,7 @@ class ServiceWebview extends Component {
15 service: PropTypes.instanceOf(ServiceModel).isRequired, 15 service: PropTypes.instanceOf(ServiceModel).isRequired,
16 setWebviewReference: PropTypes.func.isRequired, 16 setWebviewReference: PropTypes.func.isRequired,
17 detachService: PropTypes.func.isRequired, 17 detachService: PropTypes.func.isRequired,
18 isSpellcheckerEnabled: PropTypes.bool.isRequired,
18 }; 19 };
19 20
20 @observable webview = null; 21 @observable webview = null;
@@ -55,6 +56,7 @@ class ServiceWebview extends Component {
55 const { 56 const {
56 service, 57 service,
57 setWebviewReference, 58 setWebviewReference,
59 isSpellcheckerEnabled,
58 } = this.props; 60 } = this.props;
59 61
60 const preloadScript = path.join(__dirname, '../../../', 'webview', 'recipe.js'); 62 const preloadScript = path.join(__dirname, '../../../', 'webview', 'recipe.js');
@@ -70,7 +72,7 @@ class ServiceWebview extends Component {
70 autosize 72 autosize
71 src={service.url} 73 src={service.url}
72 preload={preloadScript} 74 preload={preloadScript}
73 partition={`persist:service-${service.id}`} 75 partition={service.partition}
74 onDidAttach={() => { 76 onDidAttach={() => {
75 setWebviewReference({ 77 setWebviewReference({
76 serviceId: service.id, 78 serviceId: service.id,
@@ -81,6 +83,7 @@ class ServiceWebview extends Component {
81 useragent={service.userAgent} 83 useragent={service.userAgent}
82 disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined} 84 disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined}
83 allowpopups 85 allowpopups
86 webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}`}
84 /> 87 />
85 ); 88 );
86 } 89 }
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index da2ee0b9e..f679eeed0 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -10,6 +10,7 @@ import injectSheet from 'react-jss';
10import ServiceView from './ServiceView'; 10import ServiceView from './ServiceView';
11import Appear from '../../ui/effects/Appear'; 11import Appear from '../../ui/effects/Appear';
12import serverlessLogin from '../../../helpers/serverless-helpers'; 12import serverlessLogin from '../../../helpers/serverless-helpers';
13import { TODOS_RECIPE_ID } from '../../../features/todos';
13 14
14const messages = defineMessages({ 15const messages = defineMessages({
15 welcome: { 16 welcome: {
@@ -58,6 +59,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
58 hasActivatedTrial: PropTypes.bool.isRequired, 59 hasActivatedTrial: PropTypes.bool.isRequired,
59 classes: PropTypes.object.isRequired, 60 classes: PropTypes.object.isRequired,
60 actions: PropTypes.object.isRequired, 61 actions: PropTypes.object.isRequired,
62 isSpellcheckerEnabled: PropTypes.bool.isRequired,
61 }; 63 };
62 64
63 static defaultProps = { 65 static defaultProps = {
@@ -111,6 +113,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
111 userHasCompletedSignup, 113 userHasCompletedSignup,
112 hasActivatedTrial, 114 hasActivatedTrial,
113 classes, 115 classes,
116 isSpellcheckerEnabled,
114 } = this.props; 117 } = this.props;
115 118
116 const { 119 const {
@@ -168,7 +171,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
168 </div> 171 </div>
169 </Appear> 172 </Appear>
170 )} 173 )}
171 {services.map(service => ( 174 {services.filter(service => service.recipe.id !== TODOS_RECIPE_ID).map(service => (
172 <ServiceView 175 <ServiceView
173 key={service.id} 176 key={service.id}
174 service={service} 177 service={service}
@@ -186,6 +189,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
186 redirect: false, 189 redirect: false,
187 })} 190 })}
188 upgrade={() => openSettings({ path: 'user' })} 191 upgrade={() => openSettings({ path: 'user' })}
192 isSpellcheckerEnabled={isSpellcheckerEnabled}
189 /> 193 />
190 ))} 194 ))}
191 </div> 195 </div>
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
index efa5fa60c..479f151a6 100644
--- a/src/components/services/tabs/TabItem.js
+++ b/src/components/services/tabs/TabItem.js
@@ -5,9 +5,14 @@ import PropTypes from 'prop-types';
5import { observer } from 'mobx-react'; 5import { observer } from 'mobx-react';
6import classnames from 'classnames'; 6import classnames from 'classnames';
7import { SortableElement } from 'react-sortable-hoc'; 7import { SortableElement } from 'react-sortable-hoc';
8import injectSheet from 'react-jss';
9import ms from 'ms';
8 10
11import { observable, autorun } from 'mobx';
9import ServiceModel from '../../../models/Service'; 12import ServiceModel from '../../../models/Service';
10import { ctrlKey } from '../../../environment'; 13import { ctrlKey, cmdKey } from '../../../environment';
14
15const IS_SERVICE_DEBUGGING_ENABLED = (localStorage.getItem('debug') || '').includes('Franz:Service');
11 16
12const { Menu } = remote; 17const { Menu } = remote;
13 18
@@ -50,9 +55,38 @@ const messages = defineMessages({
50 }, 55 },
51}); 56});
52 57
53@observer 58const styles = {
54class TabItem extends Component { 59 pollIndicator: {
60 position: 'absolute',
61 bottom: 2,
62 width: 10,
63 height: 10,
64 borderRadius: 5,
65 background: 'gray',
66 transition: 'background 0.5s',
67 },
68 pollIndicatorPoll: {
69 left: 2,
70 },
71 pollIndicatorAnswer: {
72 left: 14,
73 },
74 polled: {
75 background: 'yellow !important',
76 transition: 'background 0.1s',
77 },
78 pollAnswered: {
79 background: 'green !important',
80 transition: 'background 0.1s',
81 },
82 stale: {
83 background: 'red !important',
84 },
85};
86
87@injectSheet(styles) @observer class TabItem extends Component {
55 static propTypes = { 88 static propTypes = {
89 classes: PropTypes.object.isRequired,
56 service: PropTypes.instanceOf(ServiceModel).isRequired, 90 service: PropTypes.instanceOf(ServiceModel).isRequired,
57 clickHandler: PropTypes.func.isRequired, 91 clickHandler: PropTypes.func.isRequired,
58 shortcutIndex: PropTypes.number.isRequired, 92 shortcutIndex: PropTypes.number.isRequired,
@@ -71,8 +105,33 @@ class TabItem extends Component {
71 intl: intlShape, 105 intl: intlShape,
72 }; 106 };
73 107
108 @observable isPolled = false;
109
110 @observable isPollAnswered = false;
111
112 componentDidMount() {
113 const { service } = this.props;
114
115 if (IS_SERVICE_DEBUGGING_ENABLED) {
116 autorun(() => {
117 if (Date.now() - service.lastPoll < ms('0.2s')) {
118 this.isPolled = true;
119
120 setTimeout(() => { this.isPolled = false; }, ms('1s'));
121 }
122
123 if (Date.now() - service.lastPollAnswer < ms('0.2s')) {
124 this.isPollAnswered = true;
125
126 setTimeout(() => { this.isPollAnswered = false; }, ms('1s'));
127 }
128 });
129 }
130 }
131
74 render() { 132 render() {
75 const { 133 const {
134 classes,
76 service, 135 service,
77 clickHandler, 136 clickHandler,
78 shortcutIndex, 137 shortcutIndex,
@@ -97,6 +156,7 @@ class TabItem extends Component {
97 }, { 156 }, {
98 label: intl.formatMessage(messages.reload), 157 label: intl.formatMessage(messages.reload),
99 click: reload, 158 click: reload,
159 accelerator: `${cmdKey}+R`,
100 }, { 160 }, {
101 label: intl.formatMessage(messages.edit), 161 label: intl.formatMessage(messages.edit),
102 click: () => openSettings({ 162 click: () => openSettings({
@@ -141,7 +201,7 @@ class TabItem extends Component {
141 201
142 </span> 202 </span>
143 )} 203 )}
144 {service.isHibernating && !service.disableHibernation && ( 204 {service.isHibernating && !service.isHibernationEnabled && (
145 <span className="tab-item__message-count hibernating"> 205 <span className="tab-item__message-count hibernating">
146 206
147 </span> 207 </span>
@@ -153,6 +213,7 @@ class TabItem extends Component {
153 return ( 213 return (
154 <li 214 <li
155 className={classnames({ 215 className={classnames({
216 [classes.stale]: IS_SERVICE_DEBUGGING_ENABLED && service.lostRecipeConnection,
156 'tab-item': true, 217 'tab-item': true,
157 'is-active': service.isActive, 218 'is-active': service.isActive,
158 'has-custom-icon': service.hasCustomIcon, 219 'has-custom-icon': service.hasCustomIcon,
@@ -168,6 +229,24 @@ class TabItem extends Component {
168 alt="" 229 alt=""
169 /> 230 />
170 {notificationBadge} 231 {notificationBadge}
232 {IS_SERVICE_DEBUGGING_ENABLED && (
233 <>
234 <div
235 className={classnames({
236 [classes.pollIndicator]: true,
237 [classes.pollIndicatorPoll]: true,
238 [classes.polled]: this.isPolled,
239 })}
240 />
241 <div
242 className={classnames({
243 [classes.pollIndicator]: true,
244 [classes.pollIndicatorAnswer]: true,
245 [classes.pollAnswered]: this.isPollAnswered,
246 })}
247 />
248 </>
249 )}
171 </li> 250 </li>
172 ); 251 );
173 } 252 }