aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorLibravatar Markus Hatvan <markus_hatvan@aon.at>2021-08-10 19:04:54 +0200
committerLibravatar GitHub <noreply@github.com>2021-08-10 22:34:54 +0530
commit969eda02a66050cf4518ddfa657e86d1d6d8b6c3 (patch)
tree9f21b062f0c188f2c3ddfbb6594670982610aadf /src/components
parentrefactor: Move platform-specific logic for shortcut keys into common location. (diff)
downloadferdium-app-969eda02a66050cf4518ddfa657e86d1d6d8b6c3.tar.gz
ferdium-app-969eda02a66050cf4518ddfa657e86d1d6d8b6c3.tar.zst
ferdium-app-969eda02a66050cf4518ddfa657e86d1d6d8b6c3.zip
feat: follow OS reduced motion setting (#1757)
- add missing meta charset to index.html - dont restrict scaling for user in index.html - load animations.css conditionally based on motion preference - load transitions conditionally in js and css based on motion preference Co-authored-by: Vijay Raghavan Aravamudhan <vraravam@users.noreply.github.com>
Diffstat (limited to 'src/components')
-rw-r--r--src/components/auth/SetupAssistant.js110
-rw-r--r--src/components/layout/AppLayout.js86
-rw-r--r--src/components/services/content/ConnectionLostBanner.js36
-rw-r--r--src/components/services/tabs/TabItem.js193
-rw-r--r--src/components/ui/AppLoader/styles.js8
5 files changed, 247 insertions, 186 deletions
diff --git a/src/components/auth/SetupAssistant.js b/src/components/auth/SetupAssistant.js
index bd9069eb7..06ab09892 100644
--- a/src/components/auth/SetupAssistant.js
+++ b/src/components/auth/SetupAssistant.js
@@ -18,15 +18,16 @@ const SLACK_ID = 'slack';
18const messages = defineMessages({ 18const messages = defineMessages({
19 headline: { 19 headline: {
20 id: 'setupAssistant.headline', 20 id: 'setupAssistant.headline',
21 defaultMessage: '!!!Let\'s get started', 21 defaultMessage: "!!!Let's get started",
22 }, 22 },
23 subHeadline: { 23 subHeadline: {
24 id: 'setupAssistant.subheadline', 24 id: 'setupAssistant.subheadline',
25 defaultMessage: '!!!Choose from our most used services and get back on top of your messaging now.', 25 defaultMessage:
26 '!!!Choose from our most used services and get back on top of your messaging now.',
26 }, 27 },
27 submitButtonLabel: { 28 submitButtonLabel: {
28 id: 'setupAssistant.submit.label', 29 id: 'setupAssistant.submit.label',
29 defaultMessage: '!!!Let\'s go', 30 defaultMessage: "!!!Let's go",
30 }, 31 },
31 inviteSuccessInfo: { 32 inviteSuccessInfo: {
32 id: 'invite.successInfo', 33 id: 'invite.successInfo',
@@ -34,14 +35,19 @@ const messages = defineMessages({
34 }, 35 },
35}); 36});
36 37
37const styles = (theme) => ({ 38let transition = 'none';
39
40if (window.matchMedia('(prefers-reduced-motion: no-preference)')) {
41 transition = 'all 0.25s';
42}
43
44const styles = theme => ({
38 root: { 45 root: {
39 width: '500px !important', 46 width: '500px !important',
40 textAlign: 'center', 47 textAlign: 'center',
41 padding: 20, 48 padding: 20,
42 49
43 '& h1': { 50 '& h1': {},
44 },
45 }, 51 },
46 servicesGrid: { 52 servicesGrid: {
47 display: 'flex', 53 display: 'flex',
@@ -60,7 +66,7 @@ const styles = (theme) => ({
60 borderRadius: theme.borderRadius, 66 borderRadius: theme.borderRadius,
61 marginBottom: 10, 67 marginBottom: 10,
62 opacity: 0.5, 68 opacity: 0.5,
63 transition: 'all 0.25s', 69 transition,
64 border: [3, 'solid', 'transparent'], 70 border: [3, 'solid', 'transparent'],
65 71
66 '& h2': { 72 '& h2': {
@@ -70,10 +76,8 @@ const styles = (theme) => ({
70 76
71 '&:hover': { 77 '&:hover': {
72 border: [3, 'solid', theme.brandPrimary], 78 border: [3, 'solid', theme.brandPrimary],
73 '& $serviceIcon': { 79 '& $serviceIcon': {},
74 },
75 }, 80 },
76
77 }, 81 },
78 selected: { 82 selected: {
79 border: [3, 'solid', theme.brandPrimary], 83 border: [3, 'solid', theme.brandPrimary],
@@ -82,7 +86,7 @@ const styles = (theme) => ({
82 }, 86 },
83 serviceIcon: { 87 serviceIcon: {
84 width: 50, 88 width: 50,
85 transition: 'all 0.25s', 89 transition,
86 }, 90 },
87 91
88 slackModalContent: { 92 slackModalContent: {
@@ -125,7 +129,8 @@ const styles = (theme) => ({
125 }, 129 },
126}); 130});
127 131
128@injectSheet(styles) @observer 132@injectSheet(styles)
133@observer
129class SetupAssistant extends Component { 134class SetupAssistant extends Component {
130 static propTypes = { 135 static propTypes = {
131 classes: PropTypes.object.isRequired, 136 classes: PropTypes.object.isRequired,
@@ -144,13 +149,17 @@ class SetupAssistant extends Component {
144 }; 149 };
145 150
146 state = { 151 state = {
147 services: [{ 152 services: [
148 id: 'whatsapp', 153 {
149 }, { 154 id: 'whatsapp',
150 id: 'messenger', 155 },
151 }, { 156 {
152 id: 'gmail', 157 id: 'messenger',
153 }], 158 },
159 {
160 id: 'gmail',
161 },
162 ],
154 isSlackModalOpen: false, 163 isSlackModalOpen: false,
155 slackWorkspace: '', 164 slackWorkspace: '',
156 }; 165 };
@@ -158,10 +167,12 @@ class SetupAssistant extends Component {
158 slackWorkspaceHandler() { 167 slackWorkspaceHandler() {
159 const { slackWorkspace = '', services } = this.state; 168 const { slackWorkspace = '', services } = this.state;
160 169
161 const sanitizedWorkspace = slackWorkspace.trim().replace(/^https?:\/\//, ''); 170 const sanitizedWorkspace = slackWorkspace
171 .trim()
172 .replace(/^https?:\/\//, '');
162 173
163 if (sanitizedWorkspace) { 174 if (sanitizedWorkspace) {
164 const index = services.findIndex((s) => s.id === SLACK_ID); 175 const index = services.findIndex(s => s.id === SLACK_ID);
165 176
166 if (index === -1) { 177 if (index === -1) {
167 const newServices = services; 178 const newServices = services;
@@ -179,9 +190,17 @@ class SetupAssistant extends Component {
179 render() { 190 render() {
180 const { intl } = this.context; 191 const { intl } = this.context;
181 const { 192 const {
182 classes, isInviteSuccessful, onSubmit, services, isSettingUpServices, 193 classes,
194 isInviteSuccessful,
195 onSubmit,
196 services,
197 isSettingUpServices,
183 } = this.props; 198 } = this.props;
184 const { isSlackModalOpen, slackWorkspace, services: addedServices } = this.state; 199 const {
200 isSlackModalOpen,
201 slackWorkspace,
202 services: addedServices,
203 } = this.state;
185 204
186 return ( 205 return (
187 <div className={`auth__container ${classes.root}`}> 206 <div className={`auth__container ${classes.root}`}>
@@ -197,29 +216,22 @@ class SetupAssistant extends Component {
197 </Appear> 216 </Appear>
198 )} 217 )}
199 218
200 <img 219 <img src="./assets/images/logo.svg" className="auth__logo" alt="" />
201 src="./assets/images/logo.svg" 220 <h1>{intl.formatMessage(messages.headline)}</h1>
202 className="auth__logo" 221 <h2>{intl.formatMessage(messages.subHeadline)}</h2>
203 alt=""
204 />
205 <h1>
206 {intl.formatMessage(messages.headline)}
207 </h1>
208 <h2>
209 {intl.formatMessage(messages.subHeadline)}
210 </h2>
211 <div className={classnames('grid', classes.servicesGrid)}> 222 <div className={classnames('grid', classes.servicesGrid)}>
212 {Object.keys(services).map((id) => { 223 {Object.keys(services).map(id => {
213 const service = services[id]; 224 const service = services[id];
214 return ( 225 return (
215 <button 226 <button
216 className={classnames({ 227 className={classnames({
217 [classes.serviceContainer]: true, 228 [classes.serviceContainer]: true,
218 [classes.selected]: this.state.services.findIndex((s) => s.id === id) !== -1, 229 [classes.selected]:
230 this.state.services.findIndex(s => s.id === id) !== -1,
219 })} 231 })}
220 key={id} 232 key={id}
221 onClick={() => { 233 onClick={() => {
222 const index = this.state.services.findIndex((s) => s.id === id); 234 const index = this.state.services.findIndex(s => s.id === id);
223 if (index === -1) { 235 if (index === -1) {
224 if (id === SLACK_ID) { 236 if (id === SLACK_ID) {
225 this.setState({ isSlackModalOpen: true }); 237 this.setState({ isSlackModalOpen: true });
@@ -244,9 +256,7 @@ class SetupAssistant extends Component {
244 className={classes.serviceIcon} 256 className={classes.serviceIcon}
245 alt="" 257 alt=""
246 /> 258 />
247 <h2> 259 <h2>{service.name}</h2>
248 {service.name}
249 </h2>
250 {id === SLACK_ID && slackWorkspace && ( 260 {id === SLACK_ID && slackWorkspace && (
251 <Badge type="secondary" className={classes.slackBadge}> 261 <Badge type="secondary" className={classes.slackBadge}>
252 {slackWorkspace} 262 {slackWorkspace}
@@ -275,22 +285,22 @@ class SetupAssistant extends Component {
275 <div className={classes.slackModalContent}> 285 <div className={classes.slackModalContent}>
276 <img src={`${CDN_URL}/recipes/dist/slack/src/icon.svg`} alt="" /> 286 <img src={`${CDN_URL}/recipes/dist/slack/src/icon.svg`} alt="" />
277 <h1>Create your first Slack workspace</h1> 287 <h1>Create your first Slack workspace</h1>
278 <form onSubmit={(e) => { 288 <form
279 e.preventDefault(); 289 onSubmit={e => {
280 this.slackWorkspaceHandler(); 290 e.preventDefault();
281 }} 291 this.slackWorkspaceHandler();
292 }}
282 > 293 >
283 <Input 294 <Input
284 suffix=".slack.com" 295 suffix=".slack.com"
285 placeholder="workspace-url" 296 placeholder="workspace-url"
286 onChange={(e) => this.setState({ slackWorkspace: e.target.value })} 297 onChange={e =>
298 this.setState({ slackWorkspace: e.target.value })
299 }
287 value={slackWorkspace} 300 value={slackWorkspace}
288 /> 301 />
289 <div className={classes.modalActionContainer}> 302 <div className={classes.modalActionContainer}>
290 <Button 303 <Button type="submit" label="Save" />
291 type="submit"
292 label="Save"
293 />
294 <Button 304 <Button
295 type="link" 305 type="link"
296 buttonType="secondary" 306 buttonType="secondary"
@@ -305,7 +315,7 @@ class SetupAssistant extends Component {
305 <Button 315 <Button
306 type="button" 316 type="button"
307 className="auth__button" 317 className="auth__button"
308 // disabled={!atLeastOneEmailAddress} 318 // disabled={!atLeastOneEmailAddress}
309 label={intl.formatMessage(messages.submitButtonLabel)} 319 label={intl.formatMessage(messages.submitButtonLabel)}
310 onClick={() => onSubmit(this.state.services)} 320 onClick={() => onSubmit(this.state.services)}
311 busy={isSettingUpServices} 321 busy={isSettingUpServices}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 6e1d7c9a0..de962cc12 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -40,22 +40,32 @@ const messages = defineMessages({
40 }, 40 },
41 authRequestFailed: { 41 authRequestFailed: {
42 id: 'infobar.authRequestFailed', 42 id: 'infobar.authRequestFailed',
43 defaultMessage: '!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.', 43 defaultMessage:
44 '!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.',
44 }, 45 },
45}); 46});
46 47
47const styles = (theme) => ({ 48let transition = 'none';
49
50if (window.matchMedia('(prefers-reduced-motion: no-preference)')) {
51 transition = 'transform 0.5s ease';
52}
53
54const styles = theme => ({
48 appContent: { 55 appContent: {
49 // width: `calc(100% + ${theme.workspaces.drawer.width}px)`, 56 // width: `calc(100% + ${theme.workspaces.drawer.width}px)`,
50 width: '100%', 57 width: '100%',
51 transition: 'transform 0.5s ease', 58 transition,
52 transform() { 59 transform() {
53 return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaces.drawer.width}px)`; 60 return workspaceStore.isWorkspaceDrawerOpen
61 ? 'translateX(0)'
62 : `translateX(-${theme.workspaces.drawer.width}px)`;
54 }, 63 },
55 }, 64 },
56}); 65});
57 66
58@injectSheet(styles) @observer 67@injectSheet(styles)
68@observer
59class AppLayout extends Component { 69class AppLayout extends Component {
60 static propTypes = { 70 static propTypes = {
61 classes: PropTypes.object.isRequired, 71 classes: PropTypes.object.isRequired,
@@ -80,7 +90,7 @@ class AppLayout extends Component {
80 90
81 state = { 91 state = {
82 shouldShowAppUpdateInfoBar: true, 92 shouldShowAppUpdateInfoBar: true,
83 } 93 };
84 94
85 static defaultProps = { 95 static defaultProps = {
86 children: [], 96 children: [],
@@ -118,42 +128,48 @@ class AppLayout extends Component {
118 return ( 128 return (
119 <ErrorBoundary> 129 <ErrorBoundary>
120 <div className="app"> 130 <div className="app">
121 {isWindows && !isFullScreen && <TitleBar menu={window.ferdi.menu.template} icon="assets/images/logo.svg" />} 131 {isWindows && !isFullScreen && (
132 <TitleBar
133 menu={window.ferdi.menu.template}
134 icon="assets/images/logo.svg"
135 />
136 )}
122 <div className={`app__content ${classes.appContent}`}> 137 <div className={`app__content ${classes.appContent}`}>
123 {workspacesDrawer} 138 {workspacesDrawer}
124 {sidebar} 139 {sidebar}
125 <div className="app__service"> 140 <div className="app__service">
126 <WorkspaceSwitchingIndicator /> 141 <WorkspaceSwitchingIndicator />
127 {news.length > 0 && news.map((item) => ( 142 {news.length > 0 &&
143 news.map(item => (
144 <InfoBar
145 key={item.id}
146 position="top"
147 type={item.type}
148 sticky={item.sticky}
149 onHide={() => removeNewsItem({ newsId: item.id })}
150 >
151 <span
152 dangerouslySetInnerHTML={createMarkup(item.message)}
153 onClick={event => {
154 const { target } = event;
155 if (target && target.hasAttribute('data-is-news-cta')) {
156 removeNewsItem({ newsId: item.id });
157 }
158 }}
159 />
160 </InfoBar>
161 ))}
162 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
128 <InfoBar 163 <InfoBar
129 key={item.id} 164 type="danger"
130 position="top" 165 ctaLabel="Try again"
131 type={item.type} 166 ctaLoading={areRequiredRequestsLoading}
132 sticky={item.sticky} 167 sticky
133 onHide={() => removeNewsItem({ newsId: item.id })} 168 onClick={retryRequiredRequests}
134 > 169 >
135 <span 170 <span className="mdi mdi-flash" />
136 dangerouslySetInnerHTML={createMarkup(item.message)} 171 {intl.formatMessage(messages.requiredRequestsFailed)}
137 onClick={(event) => {
138 const { target } = event;
139 if (target && target.hasAttribute('data-is-news-cta')) {
140 removeNewsItem({ newsId: item.id });
141 }
142 }}
143 />
144 </InfoBar> 172 </InfoBar>
145 ))}
146 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
147 <InfoBar
148 type="danger"
149 ctaLabel="Try again"
150 ctaLoading={areRequiredRequestsLoading}
151 sticky
152 onClick={retryRequiredRequests}
153 >
154 <span className="mdi mdi-flash" />
155 {intl.formatMessage(messages.requiredRequestsFailed)}
156 </InfoBar>
157 )} 173 )}
158 {authRequestFailed && ( 174 {authRequestFailed && (
159 <InfoBar 175 <InfoBar
@@ -178,7 +194,7 @@ class AppLayout extends Component {
178 {intl.formatMessage(messages.servicesUpdated)} 194 {intl.formatMessage(messages.servicesUpdated)}
179 </InfoBar> 195 </InfoBar>
180 )} 196 )}
181 { appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && ( 197 {appUpdateIsDownloaded && this.state.shouldShowAppUpdateInfoBar && (
182 <AppUpdateInfoBar 198 <AppUpdateInfoBar
183 nextAppReleaseVersion={nextAppReleaseVersion} 199 nextAppReleaseVersion={nextAppReleaseVersion}
184 onInstallUpdate={installAppUpdate} 200 onInstallUpdate={installAppUpdate}
diff --git a/src/components/services/content/ConnectionLostBanner.js b/src/components/services/content/ConnectionLostBanner.js
index 0b9d3122c..343ddf8b4 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,7 +24,13 @@ const messages = defineMessages({
26 }, 24 },
27}); 25});
28 26
29const styles = (theme) => ({ 27let buttonTransition = 'none';
28
29if (window.matchMedia('(prefers-reduced-motion: no-preference)')) {
30 buttonTransition = 'opacity 0.25s';
31}
32
33const styles = theme => ({
30 root: { 34 root: {
31 background: theme.colorBackground, 35 background: theme.colorBackground,
32 borderRadius: theme.borderRadius, 36 borderRadius: theme.borderRadius,
@@ -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/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
index ccf3333f8..023e152c7 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';
@@ -14,7 +12,9 @@ import { observable, autorun } from 'mobx';
14import ServiceModel from '../../../models/Service'; 12import ServiceModel from '../../../models/Service';
15import { ctrlKey, cmdKey } from '../../../environment'; 13import { ctrlKey, cmdKey } 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: {
@@ -63,10 +63,21 @@ const messages = defineMessages({
63 }, 63 },
64 confirmDeleteService: { 64 confirmDeleteService: {
65 id: 'tabs.item.confirmDeleteService', 65 id: 'tabs.item.confirmDeleteService',
66 defaultMessage: '!!!Do you really want to delete the {serviceName} service?', 66 defaultMessage:
67 '!!!Do you really want to delete the {serviceName} service?',
67 }, 68 },
68}); 69});
69 70
71let pollIndicatorTransition = 'none';
72let polledTransition = 'none';
73let pollAnsweredTransition = 'none';
74
75if (window.matchMedia('(prefers-reduced-motion: no-preference)')) {
76 pollIndicatorTransition = 'background 0.5s';
77 polledTransition = 'background 0.1s';
78 pollAnsweredTransition = 'background 0.1s';
79}
80
70const styles = { 81const styles = {
71 pollIndicator: { 82 pollIndicator: {
72 position: 'absolute', 83 position: 'absolute',
@@ -75,7 +86,7 @@ const styles = {
75 height: 10, 86 height: 10,
76 borderRadius: 5, 87 borderRadius: 5,
77 background: 'gray', 88 background: 'gray',
78 transition: 'background 0.5s', 89 transition: pollIndicatorTransition,
79 }, 90 },
80 pollIndicatorPoll: { 91 pollIndicatorPoll: {
81 left: 2, 92 left: 2,
@@ -85,18 +96,20 @@ const styles = {
85 }, 96 },
86 polled: { 97 polled: {
87 background: 'yellow !important', 98 background: 'yellow !important',
88 transition: 'background 0.1s', 99 transition: polledTransition,
89 }, 100 },
90 pollAnswered: { 101 pollAnswered: {
91 background: 'green !important', 102 background: 'green !important',
92 transition: 'background 0.1s', 103 transition: pollAnsweredTransition,
93 }, 104 },
94 stale: { 105 stale: {
95 background: 'red !important', 106 background: 'red !important',
96 }, 107 },
97}; 108};
98 109
99@injectSheet(styles) @observer class TabItem extends Component { 110@injectSheet(styles)
111@observer
112class TabItem extends Component {
100 static propTypes = { 113 static propTypes = {
101 classes: PropTypes.object.isRequired, 114 classes: PropTypes.object.isRequired,
102 service: PropTypes.instanceOf(ServiceModel).isRequired, 115 service: PropTypes.instanceOf(ServiceModel).isRequired,
@@ -131,13 +144,17 @@ const styles = {
131 if (Date.now() - service.lastPoll < ms('0.2s')) { 144 if (Date.now() - service.lastPoll < ms('0.2s')) {
132 this.isPolled = true; 145 this.isPolled = true;
133 146
134 setTimeout(() => { this.isPolled = false; }, ms('1s')); 147 setTimeout(() => {
148 this.isPolled = false;
149 }, ms('1s'));
135 } 150 }
136 151
137 if (Date.now() - service.lastPollAnswer < ms('0.2s')) { 152 if (Date.now() - service.lastPollAnswer < ms('0.2s')) {
138 this.isPollAnswered = true; 153 this.isPollAnswered = true;
139 154
140 setTimeout(() => { this.isPollAnswered = false; }, ms('1s')); 155 setTimeout(() => {
156 this.isPollAnswered = false;
157 }, ms('1s'));
141 } 158 }
142 }); 159 });
143 } 160 }
@@ -163,62 +180,85 @@ const styles = {
163 } = this.props; 180 } = this.props;
164 const { intl } = this.context; 181 const { intl } = this.context;
165 182
166 const menuTemplate = [{ 183 const menuTemplate = [
167 label: service.name || service.recipe.name, 184 {
168 enabled: false, 185 label: service.name || service.recipe.name,
169 }, { 186 enabled: false,
170 type: 'separator', 187 },
171 }, { 188 {
172 label: intl.formatMessage(messages.reload), 189 type: 'separator',
173 click: reload, 190 },
174 accelerator: `${cmdKey}+R`, 191 {
175 }, { 192 label: intl.formatMessage(messages.reload),
176 label: intl.formatMessage(messages.edit), 193 click: reload,
177 click: () => openSettings({ 194 accelerator: `${cmdKey}+R`,
178 path: `services/edit/${service.id}`, 195 },
179 }), 196 {
180 }, { 197 label: intl.formatMessage(messages.edit),
181 type: 'separator', 198 click: () =>
182 }, { 199 openSettings({
183 label: service.isNotificationEnabled 200 path: `services/edit/${service.id}`,
184 ? intl.formatMessage(messages.disableNotifications) 201 }),
185 : intl.formatMessage(messages.enableNotifications), 202 },
186 click: () => toggleNotifications(), 203 {
187 }, { 204 type: 'separator',
188 label: service.isMuted
189 ? intl.formatMessage(messages.enableAudio)
190 : intl.formatMessage(messages.disableAudio),
191 click: () => toggleAudio(),
192 }, {
193 label: intl.formatMessage(service.isEnabled ? messages.disableService : messages.enableService),
194 click: () => (service.isEnabled ? disableService() : enableService()),
195 }, {
196 label: intl.formatMessage(service.isHibernating ? messages.wakeUpService : messages.hibernateService),
197 click: () => (service.isHibernating ? wakeUpService() : hibernateService()),
198 enabled: service.canHibernate,
199 }, {
200 type: 'separator',
201 }, {
202 label: intl.formatMessage(messages.deleteService),
203 click: () => {
204 const selection = dialog.showMessageBoxSync(app.mainWindow, {
205 type: 'question',
206 message: intl.formatMessage(messages.deleteService),
207 detail: intl.formatMessage(messages.confirmDeleteService, { serviceName: service.name || service.recipe.name }),
208 buttons: [
209 'Yes',
210 'No',
211 ],
212 });
213 if (selection === 0) {
214 deleteService();
215 }
216 }, 205 },
217 }]; 206 {
207 label: service.isNotificationEnabled
208 ? intl.formatMessage(messages.disableNotifications)
209 : intl.formatMessage(messages.enableNotifications),
210 click: () => toggleNotifications(),
211 },
212 {
213 label: service.isMuted
214 ? intl.formatMessage(messages.enableAudio)
215 : intl.formatMessage(messages.disableAudio),
216 click: () => toggleAudio(),
217 },
218 {
219 label: intl.formatMessage(
220 service.isEnabled ? messages.disableService : messages.enableService,
221 ),
222 click: () => (service.isEnabled ? disableService() : enableService()),
223 },
224 {
225 label: intl.formatMessage(
226 service.isHibernating
227 ? messages.wakeUpService
228 : messages.hibernateService,
229 ),
230 click: () =>
231 (service.isHibernating ? wakeUpService() : hibernateService()),
232 enabled: service.canHibernate,
233 },
234 {
235 type: 'separator',
236 },
237 {
238 label: intl.formatMessage(messages.deleteService),
239 click: () => {
240 const selection = dialog.showMessageBoxSync(app.mainWindow, {
241 type: 'question',
242 message: intl.formatMessage(messages.deleteService),
243 detail: intl.formatMessage(messages.confirmDeleteService, {
244 serviceName: service.name || service.recipe.name,
245 }),
246 buttons: ['Yes', 'No'],
247 });
248 if (selection === 0) {
249 deleteService();
250 }
251 },
252 },
253 ];
218 const menu = Menu.buildFromTemplate(menuTemplate); 254 const menu = Menu.buildFromTemplate(menuTemplate);
219 255
220 let notificationBadge = null; 256 let notificationBadge = null;
221 if ((showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) && showMessageBadgesEvenWhenMuted && service.isBadgeEnabled) { 257 if (
258 (showMessageBadgeWhenMutedSetting || service.isNotificationEnabled) &&
259 showMessageBadgesEvenWhenMuted &&
260 service.isBadgeEnabled
261 ) {
222 notificationBadge = ( 262 notificationBadge = (
223 <span> 263 <span>
224 {service.unreadDirectMessageCount > 0 && ( 264 {service.unreadDirectMessageCount > 0 && (
@@ -226,17 +266,13 @@ const styles = {
226 {service.unreadDirectMessageCount} 266 {service.unreadDirectMessageCount}
227 </span> 267 </span>
228 )} 268 )}
229 {service.unreadIndirectMessageCount > 0 269 {service.unreadIndirectMessageCount > 0 &&
230 && service.unreadDirectMessageCount === 0 270 service.unreadDirectMessageCount === 0 &&
231 && service.isIndirectMessageBadgeEnabled && ( 271 service.isIndirectMessageBadgeEnabled && (
232 <span className="tab-item__message-count is-indirect"> 272 <span className="tab-item__message-count is-indirect">•</span>
233
234 </span>
235 )} 273 )}
236 {service.isHibernating && ( 274 {service.isHibernating && (
237 <span className="tab-item__message-count hibernating"> 275 <span className="tab-item__message-count hibernating">•</span>
238
239 </span>
240 )} 276 )}
241 </span> 277 </span>
242 ); 278 );
@@ -245,7 +281,8 @@ const styles = {
245 return ( 281 return (
246 <li 282 <li
247 className={classnames({ 283 className={classnames({
248 [classes.stale]: IS_SERVICE_DEBUGGING_ENABLED && service.lostRecipeConnection, 284 [classes.stale]:
285 IS_SERVICE_DEBUGGING_ENABLED && service.lostRecipeConnection,
249 'tab-item': true, 286 'tab-item': true,
250 'is-active': service.isActive, 287 'is-active': service.isActive,
251 'has-custom-icon': service.hasCustomIcon, 288 'has-custom-icon': service.hasCustomIcon,
@@ -253,13 +290,11 @@ const styles = {
253 })} 290 })}
254 onClick={clickHandler} 291 onClick={clickHandler}
255 onContextMenu={() => menu.popup(getCurrentWindow())} 292 onContextMenu={() => menu.popup(getCurrentWindow())}
256 data-tip={`${service.name} ${shortcutIndex <= 9 ? `(${ctrlKey}+${shortcutIndex})` : ''}`} 293 data-tip={`${service.name} ${
294 shortcutIndex <= 9 ? `(${ctrlKey}+${shortcutIndex})` : ''
295 }`}
257 > 296 >
258 <img 297 <img src={service.icon} className="tab-item__icon" alt="" />
259 src={service.icon}
260 className="tab-item__icon"
261 alt=""
262 />
263 {notificationBadge} 298 {notificationBadge}
264 {IS_SERVICE_DEBUGGING_ENABLED && ( 299 {IS_SERVICE_DEBUGGING_ENABLED && (
265 <> 300 <>
diff --git a/src/components/ui/AppLoader/styles.js b/src/components/ui/AppLoader/styles.js
index 755a56b40..011f6282d 100644
--- a/src/components/ui/AppLoader/styles.js
+++ b/src/components/ui/AppLoader/styles.js
@@ -1,3 +1,9 @@
1let sloganTransition = 'none';
2
3if (window.matchMedia('(prefers-reduced-motion: no-preference)')) {
4 sloganTransition = 'opacity 1s ease';
5}
6
1export default { 7export default {
2 component: { 8 component: {
3 color: '#FFF', 9 color: '#FFF',
@@ -5,7 +11,7 @@ export default {
5 slogan: { 11 slogan: {
6 display: 'block', 12 display: 'block',
7 opacity: 0, 13 opacity: 0,
8 transition: 'opacity 1s ease', 14 transition: sloganTransition,
9 position: 'absolute', 15 position: 'absolute',
10 textAlign: 'center', 16 textAlign: 'center',
11 width: '100%', 17 width: '100%',