diff options
author | 2021-08-13 00:45:01 +0530 | |
---|---|---|
committer | 2021-08-13 00:45:01 +0530 | |
commit | 2d27d5e66649d4f5baf127a53ee5ae524eae3a59 (patch) | |
tree | c592ea219ac8cd987fc367f57b54034c450ab2ab /src/components/services | |
parent | Ferdi v5.6.0 (diff) | |
parent | 5.6.1-nightly.24 [skip ci] (diff) | |
download | ferdium-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.js | 34 | ||||
-rw-r--r-- | src/components/services/content/ErrorHandlers/styles.js | 2 | ||||
-rw-r--r-- | src/components/services/content/ServiceRestricted.js | 78 | ||||
-rw-r--r-- | src/components/services/content/ServiceView.js | 16 | ||||
-rw-r--r-- | src/components/services/content/ServiceWebview.js | 6 | ||||
-rw-r--r-- | src/components/services/content/Services.js | 9 | ||||
-rw-r--r-- | src/components/services/content/WebviewCrashHandler.js | 2 | ||||
-rw-r--r-- | src/components/services/tabs/TabBarSortableList.js | 18 | ||||
-rw-r--r-- | src/components/services/tabs/TabItem.js | 221 | ||||
-rw-r--r-- | src/components/services/tabs/Tabbar.js | 25 |
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'; | |||
5 | import { Icon } from '@meetfranz/ui'; | 5 | import { Icon } from '@meetfranz/ui'; |
6 | import { intlShape, defineMessages } from 'react-intl'; | 6 | import { intlShape, defineMessages } from 'react-intl'; |
7 | 7 | ||
8 | import { | 8 | import { mdiAlert } from '@mdi/js'; |
9 | mdiAlert, | ||
10 | } from '@mdi/js'; | ||
11 | import { LIVE_API_FERDI_WEBSITE } from '../../../config'; | 9 | import { 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 | ||
27 | let buttonTransition = 'none'; | ||
28 | |||
29 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
30 | buttonTransition = 'opacity 0.25s'; | ||
31 | } | ||
32 | |||
29 | const styles = theme => ({ | 33 | const 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 | ||
69 | class ConnectionLostBanner extends Component { | 74 | class 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 @@ | |||
1 | export default theme => ({ | 1 | export 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 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | |||
6 | import { serviceLimitStore } from '../../../features/serviceLimit'; | ||
7 | import Button from '../../ui/Button'; | ||
8 | import { RESTRICTION_TYPES } from '../../../models/Service'; | ||
9 | |||
10 | const 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 | |||
33 | export 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'; | |||
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { observable, reaction } from 'mobx'; | 4 | import { observable, reaction } from 'mobx'; |
5 | import ElectronWebView from 'react-electron-web-view'; | 5 | import ElectronWebView from 'react-electron-web-view'; |
6 | import path from 'path'; | 6 | import { join } from 'path'; |
7 | 7 | ||
8 | import ServiceModel from '../../../models/Service'; | 8 | import 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 @@ | |||
1 | import { | 1 | import { Menu, dialog, app, getCurrentWindow } from '@electron/remote'; |
2 | Menu, dialog, app, getCurrentWindow, | ||
3 | } from '@electron/remote'; | ||
4 | import React, { Component } from 'react'; | 2 | import React, { Component } from 'react'; |
5 | import { defineMessages, intlShape } from 'react-intl'; | 3 | import { defineMessages, intlShape } from 'react-intl'; |
6 | import PropTypes from 'prop-types'; | 4 | import PropTypes from 'prop-types'; |
@@ -12,9 +10,11 @@ import ms from 'ms'; | |||
12 | 10 | ||
13 | import { observable, autorun } from 'mobx'; | 11 | import { observable, autorun } from 'mobx'; |
14 | import ServiceModel from '../../../models/Service'; | 12 | import ServiceModel from '../../../models/Service'; |
15 | import { ctrlKey, cmdKey } from '../../../environment'; | 13 | import { shortcutKey } from '../../../environment'; |
16 | 14 | ||
17 | const IS_SERVICE_DEBUGGING_ENABLED = (localStorage.getItem('debug') || '').includes('Ferdi:Service'); | 15 | const IS_SERVICE_DEBUGGING_ENABLED = ( |
16 | localStorage.getItem('debug') || '' | ||
17 | ).includes('Ferdi:Service'); | ||
18 | 18 | ||
19 | const messages = defineMessages({ | 19 | const 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 | ||
79 | let pollIndicatorTransition = 'none'; | ||
80 | let polledTransition = 'none'; | ||
81 | let pollAnsweredTransition = 'none'; | ||
82 | |||
83 | if (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 | |||
62 | const styles = { | 89 | const 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 | ||
120 | class 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" |