aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/services
diff options
context:
space:
mode:
authorLibravatar Vijay A <avijayr@protonmail.com>2021-08-13 00:45:01 +0530
committerLibravatar Vijay A <avijayr@protonmail.com>2021-08-13 00:45:01 +0530
commit2d27d5e66649d4f5baf127a53ee5ae524eae3a59 (patch)
treec592ea219ac8cd987fc367f57b54034c450ab2ab /src/components/services
parentFerdi v5.6.0 (diff)
parent5.6.1-nightly.24 [skip ci] (diff)
downloadferdium-app-2d27d5e66649d4f5baf127a53ee5ae524eae3a59.tar.gz
ferdium-app-2d27d5e66649d4f5baf127a53ee5ae524eae3a59.tar.zst
ferdium-app-2d27d5e66649d4f5baf127a53ee5ae524eae3a59.zip
chore: merge from nightly branch into release branch in prep for next beta
Diffstat (limited to 'src/components/services')
-rw-r--r--src/components/services/content/ConnectionLostBanner.js34
-rw-r--r--src/components/services/content/ErrorHandlers/styles.js2
-rw-r--r--src/components/services/content/ServiceRestricted.js78
-rw-r--r--src/components/services/content/ServiceView.js16
-rw-r--r--src/components/services/content/ServiceWebview.js6
-rw-r--r--src/components/services/content/Services.js9
-rw-r--r--src/components/services/content/WebviewCrashHandler.js2
-rw-r--r--src/components/services/tabs/TabBarSortableList.js18
-rw-r--r--src/components/services/tabs/TabItem.js221
-rw-r--r--src/components/services/tabs/Tabbar.js25
10 files changed, 204 insertions, 207 deletions
diff --git a/src/components/services/content/ConnectionLostBanner.js b/src/components/services/content/ConnectionLostBanner.js
index e54a88faa..ebe863333 100644
--- a/src/components/services/content/ConnectionLostBanner.js
+++ b/src/components/services/content/ConnectionLostBanner.js
@@ -5,9 +5,7 @@ import injectSheet from 'react-jss';
5import { Icon } from '@meetfranz/ui'; 5import { Icon } from '@meetfranz/ui';
6import { intlShape, defineMessages } from 'react-intl'; 6import { intlShape, defineMessages } from 'react-intl';
7 7
8import { 8import { mdiAlert } from '@mdi/js';
9 mdiAlert,
10} from '@mdi/js';
11import { LIVE_API_FERDI_WEBSITE } from '../../../config'; 9import { LIVE_API_FERDI_WEBSITE } from '../../../config';
12// import { Button } from '@meetfranz/forms'; 10// import { Button } from '@meetfranz/forms';
13 11
@@ -26,6 +24,12 @@ const messages = defineMessages({
26 }, 24 },
27}); 25});
28 26
27let buttonTransition = 'none';
28
29if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) {
30 buttonTransition = 'opacity 0.25s';
31}
32
29const styles = theme => ({ 33const styles = theme => ({
30 root: { 34 root: {
31 background: theme.colorBackground, 35 background: theme.colorBackground,
@@ -47,7 +51,7 @@ const styles = theme => ({
47 opacity: 0.7, 51 opacity: 0.7,
48 }, 52 },
49 button: { 53 button: {
50 transition: 'opacity 0.25s', 54 transition: buttonTransition,
51 color: theme.colorText, 55 color: theme.colorText,
52 border: [1, 'solid', theme.colorText], 56 border: [1, 'solid', theme.colorText],
53 borderRadius: theme.borderRadiusSmall, 57 borderRadius: theme.borderRadiusSmall,
@@ -65,13 +69,14 @@ const styles = theme => ({
65 }, 69 },
66}); 70});
67 71
68@injectSheet(styles) @observer 72@injectSheet(styles)
73@observer
69class ConnectionLostBanner extends Component { 74class ConnectionLostBanner extends Component {
70 static propTypes = { 75 static propTypes = {
71 classes: PropTypes.object.isRequired, 76 classes: PropTypes.object.isRequired,
72 name: PropTypes.string.isRequired, 77 name: PropTypes.string.isRequired,
73 reload: PropTypes.func.isRequired, 78 reload: PropTypes.func.isRequired,
74 } 79 };
75 80
76 static contextTypes = { 81 static contextTypes = {
77 intl: intlShape, 82 intl: intlShape,
@@ -80,20 +85,13 @@ class ConnectionLostBanner extends Component {
80 inputRef = React.createRef(); 85 inputRef = React.createRef();
81 86
82 render() { 87 render() {
83 const { 88 const { classes, name, reload } = this.props;
84 classes,
85 name,
86 reload,
87 } = this.props;
88 89
89 const { intl } = this.context; 90 const { intl } = this.context;
90 91
91 return ( 92 return (
92 <div className={classes.root}> 93 <div className={classes.root}>
93 <Icon 94 <Icon icon={mdiAlert} className={classes.icon} />
94 icon={mdiAlert}
95 className={classes.icon}
96 />
97 <p> 95 <p>
98 {intl.formatMessage(messages.text, { name })} 96 {intl.formatMessage(messages.text, { name })}
99 <br /> 97 <br />
@@ -104,11 +102,7 @@ class ConnectionLostBanner extends Component {
104 {intl.formatMessage(messages.moreInformation)} 102 {intl.formatMessage(messages.moreInformation)}
105 </a> 103 </a>
106 </p> 104 </p>
107 <button 105 <button type="button" className={classes.button} onClick={reload}>
108 type="button"
109 className={classes.button}
110 onClick={reload}
111 >
112 {intl.formatMessage(messages.cta)} 106 {intl.formatMessage(messages.cta)}
113 </button> 107 </button>
114 </div> 108 </div>
diff --git a/src/components/services/content/ErrorHandlers/styles.js b/src/components/services/content/ErrorHandlers/styles.js
index 9e2509ee5..72d62f5e3 100644
--- a/src/components/services/content/ErrorHandlers/styles.js
+++ b/src/components/services/content/ErrorHandlers/styles.js
@@ -1,4 +1,4 @@
1export default theme => ({ 1export default (theme) => ({
2 component: { 2 component: {
3 left: 0, 3 left: 0,
4 position: 'absolute', 4 position: 'absolute',
diff --git a/src/components/services/content/ServiceRestricted.js b/src/components/services/content/ServiceRestricted.js
deleted file mode 100644
index 4b8d926aa..000000000
--- a/src/components/services/content/ServiceRestricted.js
+++ /dev/null
@@ -1,78 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import { serviceLimitStore } from '../../../features/serviceLimit';
7import Button from '../../ui/Button';
8import { RESTRICTION_TYPES } from '../../../models/Service';
9
10const messages = defineMessages({
11 headlineServiceLimit: {
12 id: 'service.restrictedHandler.serviceLimit.headline',
13 defaultMessage: '!!!You have reached your service limit.',
14 },
15 textServiceLimit: {
16 id: 'service.restrictedHandler.serviceLimit.text',
17 defaultMessage: '!!!Please upgrade your account to use more than {count} services.',
18 },
19 headlineCustomUrl: {
20 id: 'service.restrictedHandler.customUrl.headline',
21 defaultMessage: '!!!Franz Professional Plan required',
22 },
23 textCustomUrl: {
24 id: 'service.restrictedHandler.customUrl.text',
25 defaultMessage: '!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.',
26 },
27 action: {
28 id: 'service.restrictedHandler.action',
29 defaultMessage: '!!!Upgrade Account',
30 },
31});
32
33export default @observer class ServiceRestricted extends Component {
34 static propTypes = {
35 name: PropTypes.string.isRequired,
36 upgrade: PropTypes.func.isRequired,
37 type: PropTypes.number.isRequired,
38 };
39
40 static contextTypes = {
41 intl: intlShape,
42 };
43
44 countdownInterval = null;
45
46 countdownIntervalTimeout = 1000;
47
48 render() {
49 const {
50 name,
51 upgrade,
52 type,
53 } = this.props;
54 const { intl } = this.context;
55
56 return (
57 <div className="services__info-layer">
58 {type === RESTRICTION_TYPES.SERVICE_LIMIT && (
59 <>
60 <h1>{intl.formatMessage(messages.headlineServiceLimit)}</h1>
61 <p>{intl.formatMessage(messages.textServiceLimit, { count: serviceLimitStore.serviceLimit })}</p>
62 </>
63 )}
64 {type === RESTRICTION_TYPES.CUSTOM_URL && (
65 <>
66 <h1>{intl.formatMessage(messages.headlineCustomUrl)}</h1>
67 <p>{intl.formatMessage(messages.textCustomUrl)}</p>
68 </>
69 )}
70 <Button
71 label={intl.formatMessage(messages.action, { name })}
72 buttonType="inverted"
73 onClick={() => upgrade()}
74 />
75 </div>
76 );
77 }
78}
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 17d2db5a0..3fc084ff0 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -145,19 +145,17 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
145 </> 145 </>
146 ) : ( 146 ) : (
147 <> 147 <>
148 {(!service.isHibernating || service.isHibernationEnabled) ? ( 148 {!service.isHibernating ? (
149 <> 149 <>
150 {showNavBar && ( 150 {showNavBar && (
151 <WebControlsScreen service={service} /> 151 <WebControlsScreen service={service} />
152 )} 152 )}
153 {!service.isHibernating && ( 153 <ServiceWebview
154 <ServiceWebview 154 service={service}
155 service={service} 155 setWebviewReference={setWebviewReference}
156 setWebviewReference={setWebviewReference} 156 detachService={detachService}
157 detachService={detachService} 157 isSpellcheckerEnabled={isSpellcheckerEnabled}
158 isSpellcheckerEnabled={isSpellcheckerEnabled} 158 />
159 />
160 )}
161 </> 159 </>
162 ) : ( 160 ) : (
163 <div> 161 <div>
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index 9e5fed996..3b499a5db 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { observable, reaction } from 'mobx'; 4import { observable, reaction } from 'mobx';
5import ElectronWebView from 'react-electron-web-view'; 5import ElectronWebView from 'react-electron-web-view';
6import path from 'path'; 6import { join } from 'path';
7 7
8import ServiceModel from '../../../models/Service'; 8import ServiceModel from '../../../models/Service';
9 9
@@ -59,7 +59,7 @@ class ServiceWebview extends Component {
59 isSpellcheckerEnabled, 59 isSpellcheckerEnabled,
60 } = this.props; 60 } = this.props;
61 61
62 const preloadScript = path.join(__dirname, '../../../', 'webview', 'recipe.js'); 62 const preloadScript = join(__dirname, '..', '..', '..', 'webview', 'recipe.js');
63 63
64 return ( 64 return (
65 <ElectronWebView 65 <ElectronWebView
@@ -83,7 +83,7 @@ class ServiceWebview extends Component {
83 useragent={service.userAgent} 83 useragent={service.userAgent}
84 disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined} 84 disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined}
85 allowpopups 85 allowpopups
86 webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}, contextIsolation=false`} 86 webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}`}
87 /> 87 />
88 ); 88 );
89 } 89 }
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index caa3cf9aa..bb93ff7d4 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -30,7 +30,7 @@ const messages = defineMessages({
30 }, 30 },
31 serverInfo: { 31 serverInfo: {
32 id: 'services.serverInfo', 32 id: 'services.serverInfo',
33 defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner.', 33 defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner. If you are switching over (from one of the hosted servers) to using Ferdi without an account, please be informed that you can export your data from that server and subsequently import it using the Help menu to resurrect all your workspaces and configured services!',
34 }, 34 },
35}); 35});
36 36
@@ -54,7 +54,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
54 openSettings: PropTypes.func.isRequired, 54 openSettings: PropTypes.func.isRequired,
55 update: PropTypes.func.isRequired, 55 update: PropTypes.func.isRequired,
56 userHasCompletedSignup: PropTypes.bool.isRequired, 56 userHasCompletedSignup: PropTypes.bool.isRequired,
57 hasActivatedTrial: PropTypes.bool.isRequired,
58 classes: PropTypes.object.isRequired, 57 classes: PropTypes.object.isRequired,
59 actions: PropTypes.object.isRequired, 58 actions: PropTypes.object.isRequired,
60 isSpellcheckerEnabled: PropTypes.bool.isRequired, 59 isSpellcheckerEnabled: PropTypes.bool.isRequired,
@@ -109,7 +108,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
109 openSettings, 108 openSettings,
110 update, 109 update,
111 userHasCompletedSignup, 110 userHasCompletedSignup,
112 hasActivatedTrial,
113 classes, 111 classes,
114 isSpellcheckerEnabled, 112 isSpellcheckerEnabled,
115 } = this.props; 113 } = this.props;
@@ -123,7 +121,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
123 121
124 return ( 122 return (
125 <div className="services"> 123 <div className="services">
126 {(userHasCompletedSignup || hasActivatedTrial) && ( 124 {userHasCompletedSignup && (
127 <div className={classes.confettiContainer}> 125 <div className={classes.confettiContainer}>
128 <Confetti 126 <Confetti
129 width={window.width} 127 width={window.width}
@@ -169,7 +167,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
169 </div> 167 </div>
170 </Appear> 168 </Appear>
171 )} 169 )}
172 {services.filter(service => !service.isTodosService).map(service => ( 170 {services.filter((service) => !service.isTodosService).map((service) => (
173 <ServiceView 171 <ServiceView
174 key={service.id} 172 key={service.id}
175 service={service} 173 service={service}
@@ -186,7 +184,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
186 }, 184 },
187 redirect: false, 185 redirect: false,
188 })} 186 })}
189 upgrade={() => openSettings({ path: 'user' })}
190 isSpellcheckerEnabled={isSpellcheckerEnabled} 187 isSpellcheckerEnabled={isSpellcheckerEnabled}
191 /> 188 />
192 ))} 189 ))}
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js
index b62940c06..10ff0bbbb 100644
--- a/src/components/services/content/WebviewCrashHandler.js
+++ b/src/components/services/content/WebviewCrashHandler.js
@@ -47,7 +47,7 @@ export default @observer class WebviewCrashHandler extends Component {
47 const { reload } = this.props; 47 const { reload } = this.props;
48 48
49 this.countdownInterval = setInterval(() => { 49 this.countdownInterval = setInterval(() => {
50 this.setState(prevState => ({ 50 this.setState((prevState) => ({
51 countdown: prevState.countdown - this.countdownIntervalTimeout, 51 countdown: prevState.countdown - this.countdownIntervalTimeout,
52 })); 52 }));
53 53
diff --git a/src/components/services/tabs/TabBarSortableList.js b/src/components/services/tabs/TabBarSortableList.js
index 489027d57..1a389991d 100644
--- a/src/components/services/tabs/TabBarSortableList.js
+++ b/src/components/services/tabs/TabBarSortableList.js
@@ -14,9 +14,12 @@ class TabBarSortableList extends Component {
14 reload: PropTypes.func.isRequired, 14 reload: PropTypes.func.isRequired,
15 toggleNotifications: PropTypes.func.isRequired, 15 toggleNotifications: PropTypes.func.isRequired,
16 toggleAudio: PropTypes.func.isRequired, 16 toggleAudio: PropTypes.func.isRequired,
17 toggleDarkMode: PropTypes.func.isRequired,
17 deleteService: PropTypes.func.isRequired, 18 deleteService: PropTypes.func.isRequired,
18 disableService: PropTypes.func.isRequired, 19 disableService: PropTypes.func.isRequired,
19 enableService: PropTypes.func.isRequired, 20 enableService: PropTypes.func.isRequired,
21 hibernateService: PropTypes.func.isRequired,
22 wakeUpService: PropTypes.func.isRequired,
20 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, 23 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired,
21 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, 24 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired,
22 } 25 }
@@ -28,9 +31,12 @@ class TabBarSortableList extends Component {
28 reload, 31 reload,
29 toggleNotifications, 32 toggleNotifications,
30 toggleAudio, 33 toggleAudio,
34 toggleDarkMode,
31 deleteService, 35 deleteService,
32 disableService, 36 disableService,
33 enableService, 37 enableService,
38 hibernateService,
39 wakeUpService,
34 openSettings, 40 openSettings,
35 showMessageBadgeWhenMutedSetting, 41 showMessageBadgeWhenMutedSetting,
36 showMessageBadgesEvenWhenMuted, 42 showMessageBadgesEvenWhenMuted,
@@ -50,23 +56,17 @@ class TabBarSortableList extends Component {
50 reload={() => reload({ serviceId: service.id })} 56 reload={() => reload({ serviceId: service.id })}
51 toggleNotifications={() => toggleNotifications({ serviceId: service.id })} 57 toggleNotifications={() => toggleNotifications({ serviceId: service.id })}
52 toggleAudio={() => toggleAudio({ serviceId: service.id })} 58 toggleAudio={() => toggleAudio({ serviceId: service.id })}
59 toggleDarkMode={() => toggleDarkMode({ serviceId: service.id })}
53 deleteService={() => deleteService({ serviceId: service.id })} 60 deleteService={() => deleteService({ serviceId: service.id })}
54 disableService={() => disableService({ serviceId: service.id })} 61 disableService={() => disableService({ serviceId: service.id })}
55 enableService={() => enableService({ serviceId: service.id })} 62 enableService={() => enableService({ serviceId: service.id })}
63 hibernateService={() => hibernateService({ serviceId: service.id })}
64 wakeUpService={() => wakeUpService({ serviceId: service.id })}
56 openSettings={openSettings} 65 openSettings={openSettings}
57 showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting} 66 showMessageBadgeWhenMutedSetting={showMessageBadgeWhenMutedSetting}
58 showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted} 67 showMessageBadgesEvenWhenMuted={showMessageBadgesEvenWhenMuted}
59 /> 68 />
60 ))} 69 ))}
61 {/* <li>
62 <button
63 className="sidebar__add-service"
64 onClick={() => openSettings({ path: 'recipes' })}
65 data-tip={`${intl.formatMessage(messages.addNewService)} (${ctrlKey}+N)`}
66 >
67 <span className="mdi mdi-plus" />
68 </button>
69 </li> */}
70 </ul> 70 </ul>
71 ); 71 );
72 } 72 }
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
index 5c3149a11..6a6d2c8c5 100644
--- a/src/components/services/tabs/TabItem.js
+++ b/src/components/services/tabs/TabItem.js
@@ -1,6 +1,4 @@
1import { 1import { Menu, dialog, app, getCurrentWindow } from '@electron/remote';
2 Menu, dialog, app, getCurrentWindow,
3} from '@electron/remote';
4import React, { Component } from 'react'; 2import React, { Component } from 'react';
5import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, intlShape } from 'react-intl';
6import PropTypes from 'prop-types'; 4import PropTypes from 'prop-types';
@@ -12,9 +10,11 @@ import ms from 'ms';
12 10
13import { observable, autorun } from 'mobx'; 11import { observable, autorun } from 'mobx';
14import ServiceModel from '../../../models/Service'; 12import ServiceModel from '../../../models/Service';
15import { ctrlKey, cmdKey } from '../../../environment'; 13import { shortcutKey } from '../../../environment';
16 14
17const IS_SERVICE_DEBUGGING_ENABLED = (localStorage.getItem('debug') || '').includes('Ferdi:Service'); 15const IS_SERVICE_DEBUGGING_ENABLED = (
16 localStorage.getItem('debug') || ''
17).includes('Ferdi:Service');
18 18
19const messages = defineMessages({ 19const messages = defineMessages({
20 reload: { 20 reload: {
@@ -41,6 +41,14 @@ const messages = defineMessages({
41 id: 'tabs.item.enableAudio', 41 id: 'tabs.item.enableAudio',
42 defaultMessage: '!!!Enable audio', 42 defaultMessage: '!!!Enable audio',
43 }, 43 },
44 enableDarkMode: {
45 id: 'tabs.item.enableDarkMode',
46 defaultMessage: '!!!Enable Dark mode',
47 },
48 disableDarkMode: {
49 id: 'tabs.item.disableDarkMode',
50 defaultMessage: '!!!Disable Dark mode',
51 },
44 disableService: { 52 disableService: {
45 id: 'tabs.item.disableService', 53 id: 'tabs.item.disableService',
46 defaultMessage: '!!!Disable Service', 54 defaultMessage: '!!!Disable Service',
@@ -49,16 +57,35 @@ const messages = defineMessages({
49 id: 'tabs.item.enableService', 57 id: 'tabs.item.enableService',
50 defaultMessage: '!!!Enable Service', 58 defaultMessage: '!!!Enable Service',
51 }, 59 },
60 hibernateService: {
61 id: 'tabs.item.hibernateService',
62 defaultMessage: '!!!Hibernate Service',
63 },
64 wakeUpService: {
65 id: 'tabs.item.wakeUpService',
66 defaultMessage: '!!!Wake Up Service',
67 },
52 deleteService: { 68 deleteService: {
53 id: 'tabs.item.deleteService', 69 id: 'tabs.item.deleteService',
54 defaultMessage: '!!!Delete Service', 70 defaultMessage: '!!!Delete Service',
55 }, 71 },
56 confirmDeleteService: { 72 confirmDeleteService: {
57 id: 'tabs.item.confirmDeleteService', 73 id: 'tabs.item.confirmDeleteService',
58 defaultMessage: '!!!Do you really want to delete the {serviceName} service?', 74 defaultMessage:
75 '!!!Do you really want to delete the {serviceName} service?',
59 }, 76 },
60}); 77});
61 78
79let pollIndicatorTransition = 'none';
80let polledTransition = 'none';
81let pollAnsweredTransition = 'none';
82
83if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) {
84 pollIndicatorTransition = 'background 0.5s';
85 polledTransition = 'background 0.1s';
86 pollAnsweredTransition = 'background 0.1s';
87}
88
62const styles = { 89const styles = {
63 pollIndicator: { 90 pollIndicator: {
64 position: 'absolute', 91 position: 'absolute',
@@ -67,7 +94,7 @@ const styles = {
67 height: 10, 94 height: 10,
68 borderRadius: 5, 95 borderRadius: 5,
69 background: 'gray', 96 background: 'gray',
70 transition: 'background 0.5s', 97 transition: pollIndicatorTransition,
71 }, 98 },
72 pollIndicatorPoll: { 99 pollIndicatorPoll: {
73 left: 2, 100 left: 2,
@@ -77,18 +104,20 @@ const styles = {
77 }, 104 },
78 polled: { 105 polled: {
79 background: 'yellow !important', 106 background: 'yellow !important',
80 transition: 'background 0.1s', 107 transition: polledTransition,
81 }, 108 },
82 pollAnswered: { 109 pollAnswered: {
83 background: 'green !important', 110 background: 'green !important',
84 transition: 'background 0.1s', 111 transition: pollAnsweredTransition,
85 }, 112 },
86 stale: { 113 stale: {
87 background: 'red !important', 114 background: 'red !important',
88 }, 115 },
89}; 116};
90 117
91@injectSheet(styles) @observer class TabItem extends Component { 118@injectSheet(styles)
119@observer
120class TabItem extends Component {
92 static propTypes = { 121 static propTypes = {
93 classes: PropTypes.object.isRequired, 122 classes: PropTypes.object.isRequired,
94 service: PropTypes.instanceOf(ServiceModel).isRequired, 123 service: PropTypes.instanceOf(ServiceModel).isRequired,
@@ -97,10 +126,13 @@ const styles = {
97 reload: PropTypes.func.isRequired, 126 reload: PropTypes.func.isRequired,
98 toggleNotifications: PropTypes.func.isRequired, 127 toggleNotifications: PropTypes.func.isRequired,
99 toggleAudio: PropTypes.func.isRequired, 128 toggleAudio: PropTypes.func.isRequired,
129 toggleDarkMode: PropTypes.func.isRequired,
100 openSettings: PropTypes.func.isRequired, 130 openSettings: PropTypes.func.isRequired,
101 deleteService: PropTypes.func.isRequired, 131 deleteService: PropTypes.func.isRequired,
102 disableService: PropTypes.func.isRequired, 132 disableService: PropTypes.func.isRequired,
103 enableService: PropTypes.func.isRequired, 133 enableService: PropTypes.func.isRequired,
134 hibernateService: PropTypes.func.isRequired,
135 wakeUpService: PropTypes.func.isRequired,
104 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, 136 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired,
105 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, 137 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired,
106 }; 138 };
@@ -121,13 +153,17 @@ const styles = {
121 if (Date.now() - service.lastPoll < ms('0.2s')) { 153 if (Date.now() - service.lastPoll < ms('0.2s')) {
122 this.isPolled = true; 154 this.isPolled = true;
123 155
124 setTimeout(() => { this.isPolled = false; }, ms('1s')); 156 setTimeout(() => {
157 this.isPolled = false;
158 }, ms('1s'));
125 } 159 }
126 160
127 if (Date.now() - service.lastPollAnswer < ms('0.2s')) { 161 if (Date.now() - service.lastPollAnswer < ms('0.2s')) {
128 this.isPollAnswered = true; 162 this.isPollAnswered = true;
129 163
130 setTimeout(() => { this.isPollAnswered = false; }, ms('1s')); 164 setTimeout(() => {
165 this.isPollAnswered = false;
166 }, ms('1s'));
131 } 167 }
132 }); 168 });
133 } 169 }
@@ -142,67 +178,103 @@ const styles = {
142 reload, 178 reload,
143 toggleNotifications, 179 toggleNotifications,
144 toggleAudio, 180 toggleAudio,
181 toggleDarkMode,
145 deleteService, 182 deleteService,
146 disableService, 183 disableService,
147 enableService, 184 enableService,
185 hibernateService,
186 wakeUpService,
148 openSettings, 187 openSettings,
149 showMessageBadgeWhenMutedSetting, 188 showMessageBadgeWhenMutedSetting,
150 showMessageBadgesEvenWhenMuted, 189 showMessageBadgesEvenWhenMuted,
151 } = this.props; 190 } = this.props;
152 const { intl } = this.context; 191 const { intl } = this.context;
153 192
154 const menuTemplate = [{ 193 const menuTemplate = [
155 label: service.name || service.recipe.name, 194 {
156 enabled: false, 195 label: service.name || service.recipe.name,
157 }, { 196 enabled: false,
158 type: 'separator', 197 },
159 }, { 198 {
160 label: intl.formatMessage(messages.reload), 199 type: 'separator',
161 click: reload, 200 },
162 accelerator: `${cmdKey}+R`, 201 {
163 }, { 202 label: intl.formatMessage(messages.reload),
164 label: intl.formatMessage(messages.edit), 203 click: reload,
165 click: () => openSettings({ 204 accelerator: `${shortcutKey()}+R`,
166 path: `services/edit/${service.id}`, 205 },
167 }), 206 {
168 }, { 207 label: intl.formatMessage(messages.edit),
169 type: 'separator', 208 click: () =>
170 }, { 209 openSettings({
171 label: service.isNotificationEnabled 210 path: `services/edit/${service.id}`,
172 ? intl.formatMessage(messages.disableNotifications) 211 }),
173 : intl.formatMessage(messages.enableNotifications),
174 click: () => toggleNotifications(),
175 }, {
176 label: service.isMuted
177 ? intl.formatMessage(messages.enableAudio)
178 : intl.formatMessage(messages.disableAudio),
179 click: () => toggleAudio(),
180 }, {
181 label: intl.formatMessage(service.isEnabled ? messages.disableService : messages.enableService),
182 click: () => (service.isEnabled ? disableService() : enableService()),
183 }, {
184 type: 'separator',
185 }, {
186 label: intl.formatMessage(messages.deleteService),
187 click: () => {
188 const selection = dialog.showMessageBoxSync(app.mainWindow, {
189 type: 'question',
190 message: intl.formatMessage(messages.deleteService),
191 detail: intl.formatMessage(messages.confirmDeleteService, { serviceName: service.name || service.recipe.name }),
192 buttons: [
193 'Yes',
194 'No',
195 ],
196 });
197 if (selection === 0) {
198 deleteService();
199 }
200 }, 212 },
201 }]; 213 {
214 type: 'separator',
215 },
216 {
217 label: service.isNotificationEnabled
218 ? intl.formatMessage(messages.disableNotifications)
219 : intl.formatMessage(messages.enableNotifications),
220 click: () => toggleNotifications(),
221 },
222 {
223 label: service.isMuted
224 ? intl.formatMessage(messages.enableAudio)
225 : intl.formatMessage(messages.disableAudio),
226 click: () => toggleAudio(),
227 },
228 {
229 label: service.isDarkModeEnabled
230 ? intl.formatMessage(messages.enableDarkMode)
231 : intl.formatMessage(messages.disableDarkMode),
232 click: () => toggleDarkMode(),
233 },
234 {
235 label: intl.formatMessage(
236 service.isEnabled ? messages.disableService : messages.enableService,
237 ),
238 click: () => (service.isEnabled ? disableService() : enableService()),
239 },
240 {
241 label: intl.formatMessage(
242 service.isHibernating
243 ? messages.wakeUpService
244 : messages.hibernateService,
245 ),
246 click: () =>
247 (service.isHibernating ? wakeUpService() : hibernateService()),
248 enabled: service.canHibernate,
249 },
250 {
251 type: 'separator',
252 },
253 {
254 label: intl.formatMessage(messages.deleteService),
255 click: () => {
256 const selection = dialog.showMessageBoxSync(app.mainWindow, {
257 type: 'question',
258 message: intl.formatMessage(messages.deleteService),
259 detail: intl.formatMessage(messages.confirmDeleteService, {
260 serviceName: service.name || service.recipe.name,
261 }),
262 buttons: ['Yes', 'No'],
263 });
264 if (selection === 0) {
265 deleteService();
266 }
267 },
268 },
269 ];
202 const menu = Menu.buildFromTemplate(menuTemplate); 270 const menu = Menu.buildFromTemplate(menuTemplate);
203 271
204 let notificationBadge = null; 272 let notificationBadge = null;
205 if ((showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && service.isBadgeEnabled) { 273 if (
274 (showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) &&
275 showMessageBadgesEvenWhenMuted &&
276 service.isBadgeEnabled
277 ) {
206 notificationBadge = ( 278 notificationBadge = (
207 <span> 279 <span>
208 {service.unreadDirectMessageCount > 0 && ( 280 {service.unreadDirectMessageCount > 0 && (
@@ -210,17 +282,13 @@ const styles = {
210 {service.unreadDirectMessageCount} 282 {service.unreadDirectMessageCount}
211 </span> 283 </span>
212 )} 284 )}
213 {service.unreadIndirectMessageCount > 0 285 {service.unreadIndirectMessageCount > 0 &&
214 && service.unreadDirectMessageCount === 0 286 service.unreadDirectMessageCount === 0 &&
215 && service.isIndirectMessageBadgeEnabled && ( 287 service.isIndirectMessageBadgeEnabled && (
216 <span className="tab-item__message-count is-indirect"> 288 <span className="tab-item__message-count is-indirect">•</span>
217
218 </span>
219 )} 289 )}
220 {service.isHibernating && !service.isHibernationEnabled && ( 290 {service.isHibernating && (
221 <span className="tab-item__message-count hibernating"> 291 <span className="tab-item__message-count hibernating">•</span>
222
223 </span>
224 )} 292 )}
225 </span> 293 </span>
226 ); 294 );
@@ -229,7 +297,8 @@ const styles = {
229 return ( 297 return (
230 <li 298 <li
231 className={classnames({ 299 className={classnames({
232 [classes.stale]: IS_SERVICE_DEBUGGING_ENABLED && service.lostRecipeConnection, 300 [classes.stale]:
301 IS_SERVICE_DEBUGGING_ENABLED && service.lostRecipeConnection,
233 'tab-item': true, 302 'tab-item': true,
234 'is-active': service.isActive, 303 'is-active': service.isActive,
235 'has-custom-icon': service.hasCustomIcon, 304 'has-custom-icon': service.hasCustomIcon,
@@ -237,13 +306,11 @@ const styles = {
237 })} 306 })}
238 onClick={clickHandler} 307 onClick={clickHandler}
239 onContextMenu={() => menu.popup(getCurrentWindow())} 308 onContextMenu={() => menu.popup(getCurrentWindow())}
240 data-tip={`${service.name} ${shortcutIndex <= 9 ? `(${ctrlKey}+${shortcutIndex})` : ''}`} 309 data-tip={`${service.name} ${
310 shortcutIndex <= 9 ? `(${shortcutKey(false)}+${shortcutIndex})` : ''
311 }`}
241 > 312 >
242 <img 313 <img src={service.icon} className="tab-item__icon" alt="" />
243 src={service.icon}
244 className="tab-item__icon"
245 alt=""
246 />
247 {notificationBadge} 314 {notificationBadge}
248 {IS_SERVICE_DEBUGGING_ENABLED && ( 315 {IS_SERVICE_DEBUGGING_ENABLED && (
249 <> 316 <>
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js
index 5e8260ad0..ab1e46c9f 100644
--- a/src/components/services/tabs/Tabbar.js
+++ b/src/components/services/tabs/Tabbar.js
@@ -15,8 +15,11 @@ export default @observer class TabBar extends Component {
15 reload: PropTypes.func.isRequired, 15 reload: PropTypes.func.isRequired,
16 toggleNotifications: PropTypes.func.isRequired, 16 toggleNotifications: PropTypes.func.isRequired,
17 toggleAudio: PropTypes.func.isRequired, 17 toggleAudio: PropTypes.func.isRequired,
18 toggleDarkMode: PropTypes.func.isRequired,
18 deleteService: PropTypes.func.isRequired, 19 deleteService: PropTypes.func.isRequired,
19 updateService: PropTypes.func.isRequired, 20 updateService: PropTypes.func.isRequired,
21 hibernateService: PropTypes.func.isRequired,
22 wakeUpService: PropTypes.func.isRequired,
20 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired, 23 showMessageBadgeWhenMutedSetting: PropTypes.bool.isRequired,
21 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, 24 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired,
22 }; 25 };
@@ -31,7 +34,7 @@ export default @observer class TabBar extends Component {
31 reorder({ oldIndex, newIndex }); 34 reorder({ oldIndex, newIndex });
32 }; 35 };
33 36
34 shouldPreventSorting = event => event.target.tagName !== 'LI'; 37 shouldPreventSorting = (event) => event.target.tagName !== 'LI';
35 38
36 toggleService = ({ serviceId, isEnabled }) => { 39 toggleService = ({ serviceId, isEnabled }) => {
37 const { updateService } = this.props; 40 const { updateService } = this.props;
@@ -55,6 +58,18 @@ export default @observer class TabBar extends Component {
55 this.toggleService({ serviceId, isEnabled: true }); 58 this.toggleService({ serviceId, isEnabled: true });
56 } 59 }
57 60
61 hibernateService({ serviceId }) {
62 if (serviceId) {
63 this.props.hibernateService({ serviceId });
64 }
65 }
66
67 wakeUpService({ serviceId }) {
68 if (serviceId) {
69 this.props.wakeUpService({ serviceId });
70 }
71 }
72
58 render() { 73 render() {
59 const { 74 const {
60 services, 75 services,
@@ -64,6 +79,7 @@ export default @observer class TabBar extends Component {
64 reload, 79 reload,
65 toggleNotifications, 80 toggleNotifications,
66 toggleAudio, 81 toggleAudio,
82 toggleDarkMode,
67 deleteService, 83 deleteService,
68 showMessageBadgeWhenMutedSetting, 84 showMessageBadgeWhenMutedSetting,
69 showMessageBadgesEvenWhenMuted, 85 showMessageBadgesEvenWhenMuted,
@@ -80,9 +96,12 @@ export default @observer class TabBar extends Component {
80 reload={reload} 96 reload={reload}
81 toggleNotifications={toggleNotifications} 97 toggleNotifications={toggleNotifications}
82 toggleAudio={toggleAudio} 98 toggleAudio={toggleAudio}
99 toggleDarkMode={toggleDarkMode}
83 deleteService={deleteService} 100 deleteService={deleteService}
84 disableService={args => this.disableService(args)} 101 disableService={(args) => this.disableService(args)}
85 enableService={args => this.enableService(args)} 102 enableService={(args) => this.enableService(args)}
103 hibernateService={(args) => this.hibernateService(args)}
104 wakeUpService={(args) => this.wakeUpService(args)}
86 openSettings={openSettings} 105 openSettings={openSettings}
87 distance={20} 106 distance={20}
88 axis="y" 107 axis="y"