diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/auth/SetupAssistant.js | 110 | ||||
-rw-r--r-- | src/components/layout/AppLayout.js | 86 | ||||
-rw-r--r-- | src/components/services/content/ConnectionLostBanner.js | 36 | ||||
-rw-r--r-- | src/components/services/tabs/TabItem.js | 193 | ||||
-rw-r--r-- | src/components/ui/AppLoader/styles.js | 8 |
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'; | |||
18 | const messages = defineMessages({ | 18 | const 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 | ||
37 | const styles = (theme) => ({ | 38 | let transition = 'none'; |
39 | |||
40 | if (window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
41 | transition = 'all 0.25s'; | ||
42 | } | ||
43 | |||
44 | const 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 | ||
129 | class SetupAssistant extends Component { | 134 | class 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 | ||
47 | const styles = (theme) => ({ | 48 | let transition = 'none'; |
49 | |||
50 | if (window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
51 | transition = 'transform 0.5s ease'; | ||
52 | } | ||
53 | |||
54 | const 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 | ||
59 | class AppLayout extends Component { | 69 | class 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'; | |||
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,7 +24,13 @@ const messages = defineMessages({ | |||
26 | }, | 24 | }, |
27 | }); | 25 | }); |
28 | 26 | ||
29 | const styles = (theme) => ({ | 27 | let buttonTransition = 'none'; |
28 | |||
29 | if (window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
30 | buttonTransition = 'opacity 0.25s'; | ||
31 | } | ||
32 | |||
33 | const 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 | ||
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/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 @@ | |||
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'; |
@@ -14,7 +12,9 @@ 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 { ctrlKey, cmdKey } 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: { |
@@ -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 | ||
71 | let pollIndicatorTransition = 'none'; | ||
72 | let polledTransition = 'none'; | ||
73 | let pollAnsweredTransition = 'none'; | ||
74 | |||
75 | if (window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
76 | pollIndicatorTransition = 'background 0.5s'; | ||
77 | polledTransition = 'background 0.1s'; | ||
78 | pollAnsweredTransition = 'background 0.1s'; | ||
79 | } | ||
80 | |||
70 | const styles = { | 81 | const 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 | ||
112 | class 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 @@ | |||
1 | let sloganTransition = 'none'; | ||
2 | |||
3 | if (window.matchMedia('(prefers-reduced-motion: no-preference)')) { | ||
4 | sloganTransition = 'opacity 1s ease'; | ||
5 | } | ||
6 | |||
1 | export default { | 7 | export 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%', |