diff options
author | muhamedsalih-tw <104364298+muhamedsalih-tw@users.noreply.github.com> | 2022-11-16 23:30:39 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-16 18:00:39 +0000 |
commit | eb7b2481f631cec5953265eef4ebc3f2fa7e496a (patch) | |
tree | 419d4413f90ece77c0a2204b40948f1a158793d5 /src/components | |
parent | 6.2.1-nightly.44 [skip ci] (diff) | |
download | ferdium-app-eb7b2481f631cec5953265eef4ebc3f2fa7e496a.tar.gz ferdium-app-eb7b2481f631cec5953265eef4ebc3f2fa7e496a.tar.zst ferdium-app-eb7b2481f631cec5953265eef4ebc3f2fa7e496a.zip |
Transform JSX components to TSX (#755)
* color picker types
* Import
* SetupAssistant
* Services & appear
* ServiceWebView
* SettingsLayout
* ImportantScreen
* WorkspaceDrawer
* SetupAssistant
* chore: update vscode settings
* chore: removed stale Import screen component & its tree
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/auth/Import.js | 168 | ||||
-rw-r--r-- | src/components/auth/SetupAssistant.tsx (renamed from src/components/auth/SetupAssistant.jsx) | 65 | ||||
-rw-r--r-- | src/components/services/content/ServiceWebview.tsx (renamed from src/components/services/content/ServiceWebview.jsx) | 48 | ||||
-rw-r--r-- | src/components/services/content/Services.tsx (renamed from src/components/services/content/Services.jsx) | 81 | ||||
-rw-r--r-- | src/components/settings/SettingsLayout.tsx (renamed from src/components/settings/SettingsLayout.jsx) | 47 | ||||
-rw-r--r-- | src/components/settings/settings/EditSettingsForm.tsx | 25 | ||||
-rw-r--r-- | src/components/ui/ColorPickerInput.tsx | 102 | ||||
-rw-r--r-- | src/components/ui/colorPickerInput/index.tsx | 88 | ||||
-rw-r--r-- | src/components/ui/effects/Appear.tsx | 21 |
9 files changed, 240 insertions, 405 deletions
diff --git a/src/components/auth/Import.js b/src/components/auth/Import.js deleted file mode 100644 index b897116e2..000000000 --- a/src/components/auth/Import.js +++ /dev/null | |||
@@ -1,168 +0,0 @@ | |||
1 | import { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | ||
4 | import { defineMessages, injectIntl } from 'react-intl'; | ||
5 | import { Link } from 'react-router-dom'; | ||
6 | import classnames from 'classnames'; | ||
7 | import Form from '../../lib/Form'; | ||
8 | import Toggle from '../ui/toggle'; | ||
9 | import Button from '../ui/button'; | ||
10 | import { H1 } from '../ui/headline'; | ||
11 | |||
12 | const messages = defineMessages({ | ||
13 | headline: { | ||
14 | id: 'import.headline', | ||
15 | defaultMessage: 'Import your Ferdium 4 services', | ||
16 | }, | ||
17 | notSupportedHeadline: { | ||
18 | id: 'import.notSupportedHeadline', | ||
19 | defaultMessage: 'Services not yet supported in Ferdium 5', | ||
20 | }, | ||
21 | submitButtonLabel: { | ||
22 | id: 'import.submit.label', | ||
23 | defaultMessage: 'Import {count} services', | ||
24 | }, | ||
25 | skipButtonLabel: { | ||
26 | id: 'import.skip.label', | ||
27 | defaultMessage: 'I want to add services manually', | ||
28 | }, | ||
29 | }); | ||
30 | |||
31 | class Import extends Component { | ||
32 | static propTypes = { | ||
33 | services: MobxPropTypes.arrayOrObservableArray.isRequired, | ||
34 | onSubmit: PropTypes.func.isRequired, | ||
35 | isSubmitting: PropTypes.bool.isRequired, | ||
36 | inviteRoute: PropTypes.string.isRequired, | ||
37 | }; | ||
38 | |||
39 | componentDidMount() { | ||
40 | const config = { | ||
41 | fields: { | ||
42 | import: [ | ||
43 | ...this.props.services | ||
44 | .filter(s => s.recipe) | ||
45 | .map(s => ({ | ||
46 | fields: { | ||
47 | add: { | ||
48 | default: true, | ||
49 | options: s, | ||
50 | }, | ||
51 | }, | ||
52 | })), | ||
53 | ], | ||
54 | }, | ||
55 | }; | ||
56 | |||
57 | this.form = new Form(config, this.props.intl); | ||
58 | } | ||
59 | |||
60 | submit(e) { | ||
61 | const { services } = this.props; | ||
62 | e.preventDefault(); | ||
63 | this.form.submit({ | ||
64 | onSuccess: form => { | ||
65 | const servicesImport = form | ||
66 | .values() | ||
67 | .import.map( | ||
68 | (value, i) => !value.add || services.filter(s => s.recipe)[i], | ||
69 | ) | ||
70 | .filter(s => typeof s !== 'boolean'); | ||
71 | |||
72 | this.props.onSubmit({ services: servicesImport }); | ||
73 | }, | ||
74 | onError: () => {}, | ||
75 | }); | ||
76 | } | ||
77 | |||
78 | render() { | ||
79 | const { intl } = this.props; | ||
80 | const { services, isSubmitting, inviteRoute } = this.props; | ||
81 | |||
82 | const availableServices = services.filter(s => s.recipe); | ||
83 | const unavailableServices = services.filter(s => !s.recipe); | ||
84 | |||
85 | return ( | ||
86 | <div className="auth__scroll-container"> | ||
87 | <div className="auth__container auth__container--signup"> | ||
88 | <form | ||
89 | className="franz-form auth__form" | ||
90 | onSubmit={e => this.submit(e)} | ||
91 | > | ||
92 | <img src="./assets/images/logo.svg" className="auth__logo" alt="" /> | ||
93 | <H1>{intl.formatMessage(messages.headline)}</H1> | ||
94 | <table className="service-table available-services"> | ||
95 | <tbody> | ||
96 | {this.form.$('import').map((service, i) => ( | ||
97 | <tr key={service.id} className="service-table__row"> | ||
98 | <td className="service-table__toggle"> | ||
99 | <Toggle {...service.$('add').bind()} showLabel={false} /> | ||
100 | </td> | ||
101 | <td className="service-table__column-icon"> | ||
102 | <img | ||
103 | src={ | ||
104 | availableServices[i].custom_icon || | ||
105 | availableServices[i].recipe.icons.svg | ||
106 | } | ||
107 | className={classnames({ | ||
108 | 'service-table__icon': true, | ||
109 | 'has-custom-icon': availableServices[i].custom_icon, | ||
110 | })} | ||
111 | alt="" | ||
112 | /> | ||
113 | </td> | ||
114 | <td className="service-table__column-name"> | ||
115 | {availableServices[i].name !== '' | ||
116 | ? availableServices[i].name | ||
117 | : availableServices[i].recipe.name} | ||
118 | </td> | ||
119 | </tr> | ||
120 | ))} | ||
121 | </tbody> | ||
122 | </table> | ||
123 | {unavailableServices.length > 0 && ( | ||
124 | <div className="unavailable-services"> | ||
125 | <strong> | ||
126 | {intl.formatMessage(messages.notSupportedHeadline)} | ||
127 | </strong> | ||
128 | <p> | ||
129 | {services | ||
130 | .filter(s => !s.recipe) | ||
131 | .map((service, i) => ( | ||
132 | <span key={service.id}> | ||
133 | {service.name !== '' ? service.name : service.service} | ||
134 | {unavailableServices.length > i + 1 ? ', ' : ''} | ||
135 | </span> | ||
136 | ))} | ||
137 | </p> | ||
138 | </div> | ||
139 | )} | ||
140 | |||
141 | {isSubmitting ? ( | ||
142 | <Button | ||
143 | className="auth__button is-loading" | ||
144 | label={`${intl.formatMessage(messages.submitButtonLabel)} ...`} | ||
145 | loaded={false} | ||
146 | disabled | ||
147 | /> | ||
148 | ) : ( | ||
149 | <Button | ||
150 | type="submit" | ||
151 | className="auth__button" | ||
152 | label={intl.formatMessage(messages.submitButtonLabel)} | ||
153 | /> | ||
154 | )} | ||
155 | <Link | ||
156 | to={inviteRoute} | ||
157 | className="franz-form__button franz-form__button--secondary auth__button auth__button--skip" | ||
158 | > | ||
159 | {intl.formatMessage(messages.skipButtonLabel)} | ||
160 | </Link> | ||
161 | </form> | ||
162 | </div> | ||
163 | </div> | ||
164 | ); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | export default injectIntl(observer(Import)); | ||
diff --git a/src/components/auth/SetupAssistant.jsx b/src/components/auth/SetupAssistant.tsx index 8d15e36d1..c5fb79919 100644 --- a/src/components/auth/SetupAssistant.jsx +++ b/src/components/auth/SetupAssistant.tsx | |||
@@ -1,10 +1,8 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | 2 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, injectIntl } from 'react-intl'; | 3 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
5 | import injectSheet from 'react-jss'; | 4 | import withStyles, { WithStylesProps } from 'react-jss'; |
6 | import classnames from 'classnames'; | 5 | import classnames from 'classnames'; |
7 | |||
8 | import Input from '../ui/input/index'; | 6 | import Input from '../ui/input/index'; |
9 | import Button from '../ui/button'; | 7 | import Button from '../ui/button'; |
10 | import Badge from '../ui/badge'; | 8 | import Badge from '../ui/badge'; |
@@ -12,7 +10,6 @@ import Modal from '../ui/Modal'; | |||
12 | import Infobox from '../ui/Infobox'; | 10 | import Infobox from '../ui/Infobox'; |
13 | import Appear from '../ui/effects/Appear'; | 11 | import Appear from '../ui/effects/Appear'; |
14 | import globalMessages from '../../i18n/globalMessages'; | 12 | import globalMessages from '../../i18n/globalMessages'; |
15 | |||
16 | import { CDN_URL } from '../../config'; | 13 | import { CDN_URL } from '../../config'; |
17 | import { H1, H2 } from '../ui/headline'; | 14 | import { H1, H2 } from '../ui/headline'; |
18 | 15 | ||
@@ -42,11 +39,10 @@ const messages = defineMessages({ | |||
42 | }, | 39 | }, |
43 | }); | 40 | }); |
44 | 41 | ||
45 | let transition = 'none'; | 42 | const transition = |
46 | 43 | window && window.matchMedia('(prefers-reduced-motion: no-preference)') | |
47 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | 44 | ? 'all 0.25s' |
48 | transition = 'all 0.25s'; | 45 | : 'none'; |
49 | } | ||
50 | 46 | ||
51 | const styles = theme => ({ | 47 | const styles = theme => ({ |
52 | root: { | 48 | root: { |
@@ -136,39 +132,41 @@ const styles = theme => ({ | |||
136 | }, | 132 | }, |
137 | }); | 133 | }); |
138 | 134 | ||
139 | class SetupAssistant extends Component { | 135 | interface IProps extends WithStylesProps<typeof styles>, WrappedComponentProps { |
140 | static propTypes = { | 136 | onSubmit: (...args: any[]) => void; |
141 | classes: PropTypes.object.isRequired, | 137 | isInviteSuccessful?: boolean; |
142 | onSubmit: PropTypes.func.isRequired, | 138 | services: any; // TODO - [TS DEBT] check legacy services type |
143 | isInviteSuccessful: PropTypes.bool, | 139 | isSettingUpServices: boolean; |
144 | services: PropTypes.object.isRequired, | 140 | } |
145 | isSettingUpServices: PropTypes.bool.isRequired, | ||
146 | }; | ||
147 | 141 | ||
148 | static defaultProps = { | 142 | interface IState { |
149 | isInviteSuccessful: false, | 143 | services: any[]; |
150 | }; | 144 | isSlackModalOpen: boolean; |
145 | slackWorkspace: string; | ||
146 | showSuccessInfo: boolean; | ||
147 | } | ||
151 | 148 | ||
152 | constructor() { | 149 | @observer |
153 | super(); | 150 | class SetupAssistant extends Component<IProps, IState> { |
151 | constructor(props: IProps) { | ||
152 | super(props); | ||
154 | 153 | ||
155 | this.state = { | 154 | this.state = { |
156 | services: [], | 155 | services: [], |
157 | isSlackModalOpen: false, | 156 | isSlackModalOpen: false, |
157 | showSuccessInfo: false, | ||
158 | slackWorkspace: '', | 158 | slackWorkspace: '', |
159 | }; | 159 | }; |
160 | } | 160 | } |
161 | 161 | ||
162 | slackWorkspaceHandler() { | 162 | slackWorkspaceHandler(): void { |
163 | const { slackWorkspace = '', services } = this.state; | 163 | const { slackWorkspace = '', services } = this.state; |
164 | |||
165 | const sanitizedWorkspace = slackWorkspace | 164 | const sanitizedWorkspace = slackWorkspace |
166 | .trim() | 165 | .trim() |
167 | .replace(/^https?:\/\//, ''); | 166 | .replace(/^https?:\/\//, ''); |
168 | 167 | ||
169 | if (sanitizedWorkspace) { | 168 | if (sanitizedWorkspace) { |
170 | const index = services.findIndex(s => s.id === SLACK_ID); | 169 | const index = services.findIndex(s => s.id === SLACK_ID); |
171 | |||
172 | if (index === -1) { | 170 | if (index === -1) { |
173 | const newServices = services; | 171 | const newServices = services; |
174 | newServices.push({ id: SLACK_ID, team: sanitizedWorkspace }); | 172 | newServices.push({ id: SLACK_ID, team: sanitizedWorkspace }); |
@@ -183,13 +181,13 @@ class SetupAssistant extends Component { | |||
183 | } | 181 | } |
184 | 182 | ||
185 | render() { | 183 | render() { |
186 | const { intl } = this.props; | ||
187 | const { | 184 | const { |
188 | classes, | 185 | classes, |
189 | isInviteSuccessful, | 186 | isInviteSuccessful = false, |
190 | onSubmit, | 187 | onSubmit, |
191 | services, | 188 | services, |
192 | isSettingUpServices, | 189 | isSettingUpServices, |
190 | intl, | ||
193 | } = this.props; | 191 | } = this.props; |
194 | const { | 192 | const { |
195 | isSlackModalOpen, | 193 | isSlackModalOpen, |
@@ -204,7 +202,7 @@ class SetupAssistant extends Component { | |||
204 | <Infobox | 202 | <Infobox |
205 | type="success" | 203 | type="success" |
206 | icon="checkbox-marked-circle-outline" | 204 | icon="checkbox-marked-circle-outline" |
207 | dismissable | 205 | dismissible |
208 | > | 206 | > |
209 | {intl.formatMessage(messages.inviteSuccessInfo)} | 207 | {intl.formatMessage(messages.inviteSuccessInfo)} |
210 | </Infobox> | 208 | </Infobox> |
@@ -300,11 +298,14 @@ class SetupAssistant extends Component { | |||
300 | label={intl.formatMessage(globalMessages.save)} | 298 | label={intl.formatMessage(globalMessages.save)} |
301 | /> | 299 | /> |
302 | <Button | 300 | <Button |
303 | type="link" | 301 | type="button" |
304 | buttonType="secondary" | 302 | buttonType="secondary" |
305 | label={intl.formatMessage(globalMessages.cancel)} | 303 | label={intl.formatMessage(globalMessages.cancel)} |
306 | className={classes.ctaCancel} | 304 | className={classes.ctaCancel} |
307 | onClick={() => this.setState({ slackWorkspace: '' })} | 305 | onClick={e => { |
306 | e.preventDefault(); | ||
307 | this.setState({ slackWorkspace: '' }); | ||
308 | }} | ||
308 | /> | 309 | /> |
309 | </div> | 310 | </div> |
310 | </form> | 311 | </form> |
@@ -332,5 +333,5 @@ class SetupAssistant extends Component { | |||
332 | } | 333 | } |
333 | 334 | ||
334 | export default injectIntl( | 335 | export default injectIntl( |
335 | injectSheet(styles, { injectTheme: true })(observer(SetupAssistant)), | 336 | withStyles(styles, { injectTheme: true })(SetupAssistant), |
336 | ); | 337 | ); |
diff --git a/src/components/services/content/ServiceWebview.jsx b/src/components/services/content/ServiceWebview.tsx index 835c5125e..ac8d1ea66 100644 --- a/src/components/services/content/ServiceWebview.jsx +++ b/src/components/services/content/ServiceWebview.tsx | |||
@@ -1,27 +1,32 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, ReactElement } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | 2 | import { observer } from 'mobx-react'; |
4 | import { action, makeObservable, observable, reaction } from 'mobx'; | 3 | import { action, makeObservable, observable, reaction } from 'mobx'; |
5 | import ElectronWebView from 'react-electron-web-view'; | 4 | import ElectronWebView from 'react-electron-web-view'; |
6 | import { join } from 'path'; | 5 | import { join } from 'path'; |
7 | |||
8 | import ServiceModel from '../../../models/Service'; | 6 | import ServiceModel from '../../../models/Service'; |
9 | 7 | ||
10 | const debug = require('../../../preload-safe-debug')('Ferdium:Services'); | 8 | const debug = require('../../../preload-safe-debug')('Ferdium:Services'); |
11 | 9 | ||
12 | class ServiceWebview extends Component { | 10 | interface IProps { |
13 | static propTypes = { | 11 | service: ServiceModel; |
14 | service: PropTypes.instanceOf(ServiceModel).isRequired, | 12 | setWebviewReference: (options: { |
15 | setWebviewReference: PropTypes.func.isRequired, | 13 | serviceId: string; |
16 | detachService: PropTypes.func.isRequired, | 14 | webview: ElectronWebView | null; |
17 | isSpellcheckerEnabled: PropTypes.bool.isRequired, | 15 | }) => void; |
18 | }; | 16 | detachService: (options: { service: ServiceModel }) => void; |
17 | isSpellcheckerEnabled: boolean; | ||
18 | } | ||
19 | 19 | ||
20 | @observable webview = null; | 20 | @observer |
21 | class ServiceWebview extends Component<IProps> { | ||
22 | @observable webview: ElectronWebView | null = null; | ||
21 | 23 | ||
22 | constructor(props) { | 24 | constructor(props: IProps) { |
23 | super(props); | 25 | super(props); |
24 | 26 | ||
27 | this.refocusWebview = this.refocusWebview.bind(this); | ||
28 | this._setWebview = this._setWebview.bind(this); | ||
29 | |||
25 | makeObservable(this); | 30 | makeObservable(this); |
26 | 31 | ||
27 | reaction( | 32 | reaction( |
@@ -45,15 +50,18 @@ class ServiceWebview extends Component { | |||
45 | ); | 50 | ); |
46 | } | 51 | } |
47 | 52 | ||
48 | componentWillUnmount() { | 53 | componentWillUnmount(): void { |
49 | const { service, detachService } = this.props; | 54 | const { service, detachService } = this.props; |
50 | detachService({ service }); | 55 | detachService({ service }); |
51 | } | 56 | } |
52 | 57 | ||
53 | refocusWebview = () => { | 58 | refocusWebview(): void { |
54 | const { webview } = this; | 59 | const { webview } = this; |
55 | debug('Refocus Webview is called', this.props.service); | 60 | debug('Refocus Webview is called', this.props.service); |
56 | if (!webview) return; | 61 | if (!webview) { |
62 | return; | ||
63 | } | ||
64 | |||
57 | if (this.props.service.isActive) { | 65 | if (this.props.service.isActive) { |
58 | webview.view.blur(); | 66 | webview.view.blur(); |
59 | webview.view.focus(); | 67 | webview.view.focus(); |
@@ -67,13 +75,13 @@ class ServiceWebview extends Component { | |||
67 | } else { | 75 | } else { |
68 | debug('Refocus not required - Not active service'); | 76 | debug('Refocus not required - Not active service'); |
69 | } | 77 | } |
70 | }; | 78 | } |
71 | 79 | ||
72 | @action _setWebview(webview) { | 80 | @action _setWebview(webview): void { |
73 | this.webview = webview; | 81 | this.webview = webview; |
74 | } | 82 | } |
75 | 83 | ||
76 | render() { | 84 | render(): ReactElement { |
77 | const { service, setWebviewReference, isSpellcheckerEnabled } = this.props; | 85 | const { service, setWebviewReference, isSpellcheckerEnabled } = this.props; |
78 | 86 | ||
79 | const preloadScript = join( | 87 | const preloadScript = join( |
@@ -114,7 +122,7 @@ class ServiceWebview extends Component { | |||
114 | }); | 122 | }); |
115 | }, 0); | 123 | }, 0); |
116 | }} | 124 | }} |
117 | onUpdateTargetUrl={this.updateTargetUrl} | 125 | // onUpdateTargetUrl={this.updateTargetUrl} // TODO - [TS DEBT] need to check where its from |
118 | useragent={service.userAgent} | 126 | useragent={service.userAgent} |
119 | disablewebsecurity={ | 127 | disablewebsecurity={ |
120 | service.recipe.disablewebsecurity ? true : undefined | 128 | service.recipe.disablewebsecurity ? true : undefined |
@@ -129,4 +137,4 @@ class ServiceWebview extends Component { | |||
129 | } | 137 | } |
130 | } | 138 | } |
131 | 139 | ||
132 | export default observer(ServiceWebview); | 140 | export default ServiceWebview; |
diff --git a/src/components/services/content/Services.jsx b/src/components/services/content/Services.tsx index da700b5b1..53cddd907 100644 --- a/src/components/services/content/Services.jsx +++ b/src/components/services/content/Services.tsx | |||
@@ -1,14 +1,13 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, ReactElement } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import { observer } from 'mobx-react'; |
3 | import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react'; | ||
4 | import { Link } from 'react-router-dom'; | 3 | import { Link } from 'react-router-dom'; |
5 | import { defineMessages, injectIntl } from 'react-intl'; | 4 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
6 | import Confetti from 'react-confetti'; | 5 | import Confetti from 'react-confetti'; |
7 | import ms from 'ms'; | 6 | import ms from 'ms'; |
8 | import injectSheet from 'react-jss'; | 7 | import withStyles, { WithStylesProps } from 'react-jss'; |
9 | |||
10 | import ServiceView from './ServiceView'; | 8 | import ServiceView from './ServiceView'; |
11 | import Appear from '../../ui/effects/Appear'; | 9 | import Appear from '../../ui/effects/Appear'; |
10 | import Service from '../../../models/Service'; | ||
12 | 11 | ||
13 | const messages = defineMessages({ | 12 | const messages = defineMessages({ |
14 | getStarted: { | 13 | getStarted: { |
@@ -39,37 +38,40 @@ const styles = { | |||
39 | }, | 38 | }, |
40 | }; | 39 | }; |
41 | 40 | ||
42 | class Services extends Component { | 41 | interface IProps extends WrappedComponentProps, WithStylesProps<typeof styles> { |
43 | static propTypes = { | 42 | services?: Service[]; |
44 | services: MobxPropTypes.arrayOrObservableArray, | 43 | setWebviewReference: () => void; |
45 | setWebviewReference: PropTypes.func.isRequired, | 44 | detachService: () => void; |
46 | detachService: PropTypes.func.isRequired, | 45 | handleIPCMessage: () => void; |
47 | handleIPCMessage: PropTypes.func.isRequired, | 46 | openWindow: () => void; |
48 | openWindow: PropTypes.func.isRequired, | 47 | reload: (options: { serviceId: string }) => void; |
49 | reload: PropTypes.func.isRequired, | 48 | openSettings: (options: { path: string }) => void; |
50 | openSettings: PropTypes.func.isRequired, | 49 | update: (options: { |
51 | update: PropTypes.func.isRequired, | 50 | serviceId: string; |
52 | userHasCompletedSignup: PropTypes.bool.isRequired, | 51 | serviceData: { isEnabled: boolean }; |
53 | // eslint-disable-next-line react/forbid-prop-types | 52 | redirect: boolean; |
54 | classes: PropTypes.object.isRequired, | 53 | }) => void; |
55 | isSpellcheckerEnabled: PropTypes.bool.isRequired, | 54 | userHasCompletedSignup: boolean; |
56 | }; | 55 | isSpellcheckerEnabled: boolean; |
56 | } | ||
57 | 57 | ||
58 | static defaultProps = { | 58 | interface IState { |
59 | services: [], | 59 | showConfetti: boolean; |
60 | }; | 60 | } |
61 | 61 | ||
62 | _confettiTimeout = null; | 62 | @observer |
63 | class Services extends Component<IProps, IState> { | ||
64 | _confettiTimeout: number | null = null; | ||
63 | 65 | ||
64 | constructor() { | 66 | constructor(props: IProps) { |
65 | super(); | 67 | super(props); |
66 | 68 | ||
67 | this.state = { | 69 | this.state = { |
68 | showConfetti: true, | 70 | showConfetti: true, |
69 | }; | 71 | }; |
70 | } | 72 | } |
71 | 73 | ||
72 | componentDidMount() { | 74 | componentDidMount(): void { |
73 | this._confettiTimeout = window.setTimeout(() => { | 75 | this._confettiTimeout = window.setTimeout(() => { |
74 | this.setState({ | 76 | this.setState({ |
75 | showConfetti: false, | 77 | showConfetti: false, |
@@ -77,15 +79,15 @@ class Services extends Component { | |||
77 | }, ms('8s')); | 79 | }, ms('8s')); |
78 | } | 80 | } |
79 | 81 | ||
80 | componentWillUnmount() { | 82 | componentWillUnmount(): void { |
81 | if (this._confettiTimeout) { | 83 | if (this._confettiTimeout) { |
82 | clearTimeout(this._confettiTimeout); | 84 | clearTimeout(this._confettiTimeout); |
83 | } | 85 | } |
84 | } | 86 | } |
85 | 87 | ||
86 | render() { | 88 | render(): ReactElement { |
87 | const { | 89 | const { |
88 | services, | 90 | services = [], |
89 | handleIPCMessage, | 91 | handleIPCMessage, |
90 | setWebviewReference, | 92 | setWebviewReference, |
91 | detachService, | 93 | detachService, |
@@ -96,32 +98,31 @@ class Services extends Component { | |||
96 | userHasCompletedSignup, | 98 | userHasCompletedSignup, |
97 | classes, | 99 | classes, |
98 | isSpellcheckerEnabled, | 100 | isSpellcheckerEnabled, |
101 | intl, | ||
99 | } = this.props; | 102 | } = this.props; |
100 | 103 | ||
101 | const { showConfetti } = this.state; | 104 | const { showConfetti } = this.state; |
102 | 105 | ||
103 | const { intl } = this.props; | ||
104 | |||
105 | return ( | 106 | return ( |
106 | <div className="services"> | 107 | <div className="services"> |
107 | {userHasCompletedSignup && ( | 108 | {userHasCompletedSignup && ( |
108 | <div className={classes.confettiContainer}> | 109 | <div className={classes.confettiContainer}> |
109 | <Confetti | 110 | <Confetti |
110 | width={window.width} | 111 | width={window.innerWidth} |
111 | height={window.height} | 112 | height={window.innerHeight} |
112 | numberOfPieces={showConfetti ? 200 : 0} | 113 | numberOfPieces={showConfetti ? 200 : 0} |
113 | /> | 114 | /> |
114 | </div> | 115 | </div> |
115 | )} | 116 | )} |
116 | {services.length === 0 && ( | 117 | {services.length === 0 && ( |
117 | <Appear timeout={1500} transitionName="slideUp"> | 118 | <Appear transitionName="slideUp"> |
118 | <div className="services__no-service"> | 119 | <div className="services__no-service"> |
119 | <img | 120 | <img |
120 | src="./assets/images/logo-beard-only.svg" | 121 | src="./assets/images/logo-beard-only.svg" |
121 | alt="Logo" | 122 | alt="Logo" |
122 | style={{ maxHeight: '50vh' }} | 123 | style={{ maxHeight: '50vh' }} |
123 | /> | 124 | /> |
124 | <Appear timeout={300} transitionName="slideUp"> | 125 | <Appear transitionName="slideUp"> |
125 | <Link to="/settings/recipes" className="button"> | 126 | <Link to="/settings/recipes" className="button"> |
126 | {intl.formatMessage(messages.getStarted)} | 127 | {intl.formatMessage(messages.getStarted)} |
127 | </Link> | 128 | </Link> |
@@ -158,8 +159,4 @@ class Services extends Component { | |||
158 | } | 159 | } |
159 | } | 160 | } |
160 | 161 | ||
161 | export default injectIntl( | 162 | export default injectIntl(withStyles(styles, { injectTheme: true })(Services)); |
162 | injectSheet(styles, { injectTheme: true })( | ||
163 | inject('actions')(observer(Services)), | ||
164 | ), | ||
165 | ); | ||
diff --git a/src/components/settings/SettingsLayout.jsx b/src/components/settings/SettingsLayout.tsx index 989c428f2..3b706571e 100644 --- a/src/components/settings/SettingsLayout.jsx +++ b/src/components/settings/SettingsLayout.tsx | |||
@@ -1,8 +1,6 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, ReactElement } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | 2 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, injectIntl } from 'react-intl'; | 3 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
5 | |||
6 | import { mdiClose } from '@mdi/js'; | 4 | import { mdiClose } from '@mdi/js'; |
7 | import { Outlet } from 'react-router-dom'; | 5 | import { Outlet } from 'react-router-dom'; |
8 | import ErrorBoundary from '../util/ErrorBoundary'; | 6 | import ErrorBoundary from '../util/ErrorBoundary'; |
@@ -17,35 +15,35 @@ const messages = defineMessages({ | |||
17 | }, | 15 | }, |
18 | }); | 16 | }); |
19 | 17 | ||
20 | class SettingsLayout extends Component { | 18 | interface IProps extends WrappedComponentProps { |
21 | static propTypes = { | 19 | navigation: ReactElement; |
22 | navigation: PropTypes.element.isRequired, | 20 | closeSettings: () => void; |
23 | closeSettings: PropTypes.func.isRequired, | 21 | } |
24 | }; | 22 | |
23 | @observer | ||
24 | class SettingsLayout extends Component<IProps> { | ||
25 | constructor(props: IProps) { | ||
26 | super(props); | ||
25 | 27 | ||
26 | componentDidMount() { | 28 | this.handleKeyDown = this.handleKeyDown.bind(this); |
27 | document.addEventListener('keydown', this.handleKeyDown.bind(this), false); | ||
28 | } | 29 | } |
29 | 30 | ||
30 | componentWillUnmount() { | 31 | componentDidMount(): void { |
31 | document.removeEventListener( | 32 | document.addEventListener('keydown', this.handleKeyDown, false); |
32 | 'keydown', | ||
33 | // eslint-disable-next-line unicorn/no-invalid-remove-event-listener | ||
34 | this.handleKeyDown.bind(this), | ||
35 | false, | ||
36 | ); | ||
37 | } | 33 | } |
38 | 34 | ||
39 | handleKeyDown(e) { | 35 | componentWillUnmount(): void { |
36 | document.removeEventListener('keydown', this.handleKeyDown, false); | ||
37 | } | ||
38 | |||
39 | handleKeyDown(e: KeyboardEvent): void { | ||
40 | if (isEscKeyPress(e.keyCode)) { | 40 | if (isEscKeyPress(e.keyCode)) { |
41 | this.props.closeSettings(); | 41 | this.props.closeSettings(); |
42 | } | 42 | } |
43 | } | 43 | } |
44 | 44 | ||
45 | render() { | 45 | render(): ReactElement { |
46 | const { navigation, closeSettings } = this.props; | 46 | const { navigation, closeSettings, intl } = this.props; |
47 | |||
48 | const { intl } = this.props; | ||
49 | 47 | ||
50 | return ( | 48 | return ( |
51 | <Appear transitionName="fadeIn-fast"> | 49 | <Appear transitionName="fadeIn-fast"> |
@@ -59,7 +57,6 @@ class SettingsLayout extends Component { | |||
59 | /> | 57 | /> |
60 | <div className="settings franz-form"> | 58 | <div className="settings franz-form"> |
61 | {navigation} | 59 | {navigation} |
62 | |||
63 | <Outlet /> | 60 | <Outlet /> |
64 | <button | 61 | <button |
65 | type="button" | 62 | type="button" |
@@ -77,4 +74,4 @@ class SettingsLayout extends Component { | |||
77 | } | 74 | } |
78 | } | 75 | } |
79 | 76 | ||
80 | export default injectIntl(observer(SettingsLayout)); | 77 | export default injectIntl(SettingsLayout); |
diff --git a/src/components/settings/settings/EditSettingsForm.tsx b/src/components/settings/settings/EditSettingsForm.tsx index e796a48ec..8ccad9e49 100644 --- a/src/components/settings/settings/EditSettingsForm.tsx +++ b/src/components/settings/settings/EditSettingsForm.tsx | |||
@@ -1,5 +1,5 @@ | |||
1 | import { systemPreferences } from '@electron/remote'; | 1 | import { systemPreferences } from '@electron/remote'; |
2 | import { Component } from 'react'; | 2 | import { Component, ReactElement } from 'react'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import prettyBytes from 'pretty-bytes'; | 4 | import prettyBytes from 'pretty-bytes'; |
5 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | 5 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
@@ -9,7 +9,7 @@ import Button from '../../ui/button'; | |||
9 | import Toggle from '../../ui/toggle'; | 9 | import Toggle from '../../ui/toggle'; |
10 | import Select from '../../ui/Select'; | 10 | import Select from '../../ui/Select'; |
11 | import Input from '../../ui/input/index'; | 11 | import Input from '../../ui/input/index'; |
12 | import ColorPickerInput from '../../ui/ColorPickerInput'; | 12 | import ColorPickerInput from '../../ui/colorPickerInput'; |
13 | import Infobox from '../../ui/Infobox'; | 13 | import Infobox from '../../ui/Infobox'; |
14 | import { H1, H2, H3, H5 } from '../../ui/headline'; | 14 | import { H1, H2, H3, H5 } from '../../ui/headline'; |
15 | import { | 15 | import { |
@@ -258,14 +258,14 @@ const messages = defineMessages({ | |||
258 | }, | 258 | }, |
259 | }); | 259 | }); |
260 | 260 | ||
261 | const Hr = () => ( | 261 | const Hr = (): ReactElement => ( |
262 | <hr | 262 | <hr |
263 | className="settings__hr" | 263 | className="settings__hr" |
264 | style={{ marginBottom: 20, borderStyle: 'dashed' }} | 264 | style={{ marginBottom: 20, borderStyle: 'dashed' }} |
265 | /> | 265 | /> |
266 | ); | 266 | ); |
267 | 267 | ||
268 | const HrSections = () => ( | 268 | const HrSections = (): ReactElement => ( |
269 | <hr | 269 | <hr |
270 | className="settings__hr-sections" | 270 | className="settings__hr-sections" |
271 | style={{ marginTop: 20, marginBottom: 40, borderStyle: 'solid' }} | 271 | style={{ marginTop: 20, marginBottom: 40, borderStyle: 'solid' }} |
@@ -325,8 +325,11 @@ class EditSettingsForm extends Component<IProps, IState> { | |||
325 | this.setState({ clearCacheButtonClicked: true }); | 325 | this.setState({ clearCacheButtonClicked: true }); |
326 | }; | 326 | }; |
327 | 327 | ||
328 | submit(e) { | 328 | submit(e): void { |
329 | e.preventDefault(); | 329 | if (e) { |
330 | e.preventDefault(); | ||
331 | } | ||
332 | |||
330 | this.props.form.submit({ | 333 | this.props.form.submit({ |
331 | onSuccess: form => { | 334 | onSuccess: form => { |
332 | const values = form.values(); | 335 | const values = form.values(); |
@@ -344,7 +347,7 @@ class EditSettingsForm extends Component<IProps, IState> { | |||
344 | }); | 347 | }); |
345 | } | 348 | } |
346 | 349 | ||
347 | render() { | 350 | render(): ReactElement { |
348 | const { | 351 | const { |
349 | checkForUpdates, | 352 | checkForUpdates, |
350 | installUpdate, | 353 | installUpdate, |
@@ -742,8 +745,8 @@ class EditSettingsForm extends Component<IProps, IState> { | |||
742 | {intl.formatMessage(messages.overallTheme)} | 745 | {intl.formatMessage(messages.overallTheme)} |
743 | <div className="settings__settings-group__apply-color"> | 746 | <div className="settings__settings-group__apply-color"> |
744 | <ColorPickerInput | 747 | <ColorPickerInput |
745 | onChange={e => this.submit(e)} | 748 | {...form.$('accentColor').bind()} |
746 | field={form.$('accentColor')} | 749 | onColorChange={this.submit.bind(this)} |
747 | className="color-picker-input" | 750 | className="color-picker-input" |
748 | /> | 751 | /> |
749 | </div> | 752 | </div> |
@@ -752,8 +755,8 @@ class EditSettingsForm extends Component<IProps, IState> { | |||
752 | {intl.formatMessage(messages.progressbarTheme)} | 755 | {intl.formatMessage(messages.progressbarTheme)} |
753 | <div className="settings__settings-group__apply-color"> | 756 | <div className="settings__settings-group__apply-color"> |
754 | <ColorPickerInput | 757 | <ColorPickerInput |
755 | onChange={e => this.submit(e)} | 758 | {...form.$('progressbarAccentColor').bind()} |
756 | field={form.$('progressbarAccentColor')} | 759 | onColorChange={this.submit.bind(this)} |
757 | className="color-picker-input" | 760 | className="color-picker-input" |
758 | /> | 761 | /> |
759 | </div> | 762 | </div> |
diff --git a/src/components/ui/ColorPickerInput.tsx b/src/components/ui/ColorPickerInput.tsx deleted file mode 100644 index da1fffb71..000000000 --- a/src/components/ui/ColorPickerInput.tsx +++ /dev/null | |||
@@ -1,102 +0,0 @@ | |||
1 | import { | ||
2 | ChangeEvent, | ||
3 | ChangeEventHandler, | ||
4 | Component, | ||
5 | createRef, | ||
6 | RefObject, | ||
7 | } from 'react'; | ||
8 | import { observer } from 'mobx-react'; | ||
9 | import classnames from 'classnames'; | ||
10 | import { SliderPicker } from 'react-color'; | ||
11 | import { noop } from 'lodash'; | ||
12 | import { Field } from '../../@types/mobx-form.types'; | ||
13 | |||
14 | interface IProps { | ||
15 | field: Field; | ||
16 | className?: string; | ||
17 | focus?: boolean; | ||
18 | onChange: ChangeEventHandler<HTMLInputElement>; | ||
19 | } | ||
20 | |||
21 | // TODO - [TS DEBT] check if field can be spread instead of having it single field attribute in interface | ||
22 | @observer | ||
23 | class ColorPickerInput extends Component<IProps> { | ||
24 | private inputElement: RefObject<HTMLInputElement> = | ||
25 | createRef<HTMLInputElement>(); | ||
26 | |||
27 | componentDidMount() { | ||
28 | const { focus = false } = this.props; | ||
29 | if (focus) { | ||
30 | this.focus(); | ||
31 | } | ||
32 | } | ||
33 | |||
34 | onChange(e: ChangeEvent<HTMLInputElement>) { | ||
35 | const { field, onChange = noop } = this.props; | ||
36 | |||
37 | onChange(e); | ||
38 | if (field.onChange) { | ||
39 | field.onChange(e); | ||
40 | } | ||
41 | } | ||
42 | |||
43 | focus() { | ||
44 | if (this.inputElement && this.inputElement.current) { | ||
45 | this.inputElement.current.focus(); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | handleChangeComplete = (color: { hex: string }) => { | ||
50 | const { field } = this.props; | ||
51 | field.value = color.hex; | ||
52 | }; | ||
53 | |||
54 | render() { | ||
55 | const { field, className = null } = this.props; | ||
56 | |||
57 | let { type } = field; | ||
58 | type = 'text'; | ||
59 | |||
60 | return ( | ||
61 | <div | ||
62 | className={classnames({ | ||
63 | 'franz-form__field': true, | ||
64 | 'has-error': field.error, | ||
65 | [`${className}`]: className, | ||
66 | })} | ||
67 | > | ||
68 | <SliderPicker | ||
69 | color={field.value} | ||
70 | onChangeComplete={this.handleChangeComplete} | ||
71 | id={field.id} | ||
72 | type={type} | ||
73 | className="franz-form__input" | ||
74 | name={field.name} | ||
75 | value={field.value} | ||
76 | placeholder={field.placeholder} | ||
77 | onBlur={field.onBlur} | ||
78 | onFocus={field.onFocus} | ||
79 | ref={this.inputElement} | ||
80 | disabled={field.disabled} | ||
81 | /> | ||
82 | <div className="franz-form__input-wrapper franz-form__input-wrapper__color-picker"> | ||
83 | <input | ||
84 | id={field.id} | ||
85 | type={type} | ||
86 | className="franz-form__input" | ||
87 | name={field.name} | ||
88 | value={field.value} | ||
89 | placeholder={field.placeholder} | ||
90 | onChange={e => this.onChange(e)} | ||
91 | onBlur={field.onBlur} | ||
92 | onFocus={field.onFocus} | ||
93 | ref={this.inputElement} | ||
94 | disabled={field.disabled} | ||
95 | /> | ||
96 | </div> | ||
97 | </div> | ||
98 | ); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | export default ColorPickerInput; | ||
diff --git a/src/components/ui/colorPickerInput/index.tsx b/src/components/ui/colorPickerInput/index.tsx new file mode 100644 index 000000000..9bab6efec --- /dev/null +++ b/src/components/ui/colorPickerInput/index.tsx | |||
@@ -0,0 +1,88 @@ | |||
1 | import { | ||
2 | Component, | ||
3 | createRef, | ||
4 | InputHTMLAttributes, | ||
5 | ReactElement, | ||
6 | RefObject, | ||
7 | } from 'react'; | ||
8 | import { observer } from 'mobx-react'; | ||
9 | import classnames from 'classnames'; | ||
10 | import { SliderPicker } from 'react-color'; | ||
11 | import { noop } from 'lodash'; | ||
12 | import { FormFields } from '../../../@types/mobx-form.types'; | ||
13 | |||
14 | interface IProps extends InputHTMLAttributes<HTMLInputElement>, FormFields { | ||
15 | className?: string; | ||
16 | focus?: boolean; | ||
17 | onColorChange?: () => void; | ||
18 | error: string; | ||
19 | } | ||
20 | |||
21 | @observer | ||
22 | class ColorPickerInput extends Component<IProps> { | ||
23 | private inputElement: RefObject<HTMLInputElement> = | ||
24 | createRef<HTMLInputElement>(); | ||
25 | |||
26 | componentDidMount(): void { | ||
27 | const { focus = false } = this.props; | ||
28 | if (focus && this.inputElement && this.inputElement.current) { | ||
29 | this.inputElement.current.focus(); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | onChange({ hex }: { hex: string }): void { | ||
34 | const { onColorChange = noop, onChange = noop } = this.props; | ||
35 | onColorChange(); | ||
36 | onChange(hex); | ||
37 | } | ||
38 | |||
39 | render(): ReactElement { | ||
40 | const { | ||
41 | id, | ||
42 | name, | ||
43 | value = '', | ||
44 | placeholder = '', | ||
45 | disabled = false, | ||
46 | className = null, | ||
47 | type = 'text', | ||
48 | error = '', | ||
49 | onChange = noop, | ||
50 | } = this.props; | ||
51 | |||
52 | return ( | ||
53 | <div | ||
54 | className={classnames({ | ||
55 | 'franz-form__field': true, | ||
56 | 'has-error': error, | ||
57 | [`${className}`]: className, | ||
58 | })} | ||
59 | ref={this.inputElement} | ||
60 | > | ||
61 | <SliderPicker | ||
62 | color={value} | ||
63 | onChange={this.onChange.bind(this)} | ||
64 | id={`${id}-SliderPicker`} | ||
65 | type={type} | ||
66 | className="franz-form__input" | ||
67 | name={name} | ||
68 | placeholder={placeholder} | ||
69 | disabled={disabled} | ||
70 | /> | ||
71 | <div className="franz-form__input-wrapper franz-form__input-wrapper__color-picker"> | ||
72 | <input | ||
73 | id={`${id}-Input`} | ||
74 | type={type} | ||
75 | className="franz-form__input" | ||
76 | name={name} | ||
77 | value={value} | ||
78 | placeholder={placeholder} | ||
79 | onChange={onChange} | ||
80 | disabled={disabled} | ||
81 | /> | ||
82 | </div> | ||
83 | </div> | ||
84 | ); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | export default ColorPickerInput; | ||
diff --git a/src/components/ui/effects/Appear.tsx b/src/components/ui/effects/Appear.tsx index bf097b6a6..2076f6ba6 100644 --- a/src/components/ui/effects/Appear.tsx +++ b/src/components/ui/effects/Appear.tsx | |||
@@ -5,11 +5,22 @@ interface IProps { | |||
5 | children: ReactNode; | 5 | children: ReactNode; |
6 | transitionName?: string; | 6 | transitionName?: string; |
7 | className?: string; | 7 | className?: string; |
8 | transitionAppear?: boolean; | ||
9 | transitionLeave?: boolean; | ||
10 | transitionAppearTimeout?: number; | ||
11 | transitionEnterTimeout?: number; | ||
12 | transitionLeaveTimeout?: number; | ||
8 | } | 13 | } |
14 | |||
9 | const Appear = ({ | 15 | const Appear = ({ |
10 | children, | 16 | children, |
11 | transitionName = 'fadeIn', | 17 | transitionName = 'fadeIn', |
12 | className = '', | 18 | className = '', |
19 | transitionAppear = true, | ||
20 | transitionLeave = true, | ||
21 | transitionAppearTimeout = 1500, | ||
22 | transitionEnterTimeout = 1500, | ||
23 | transitionLeaveTimeout = 1500, | ||
13 | }: IProps): ReactElement | null => { | 24 | }: IProps): ReactElement | null => { |
14 | const [mounted, setMounted] = useState(false); | 25 | const [mounted, setMounted] = useState(false); |
15 | 26 | ||
@@ -24,11 +35,11 @@ const Appear = ({ | |||
24 | return ( | 35 | return ( |
25 | <CSSTransitionGroup | 36 | <CSSTransitionGroup |
26 | transitionName={transitionName} | 37 | transitionName={transitionName} |
27 | transitionAppear | 38 | transitionAppear={transitionAppear} |
28 | transitionLeave | 39 | transitionLeave={transitionLeave} |
29 | transitionAppearTimeout={1500} | 40 | transitionAppearTimeout={transitionAppearTimeout} |
30 | transitionEnterTimeout={1500} | 41 | transitionEnterTimeout={transitionEnterTimeout} |
31 | transitionLeaveTimeout={1500} | 42 | transitionLeaveTimeout={transitionLeaveTimeout} |
32 | className={className} | 43 | className={className} |
33 | > | 44 | > |
34 | {children} | 45 | {children} |