diff options
18 files changed, 369 insertions, 368 deletions
diff --git a/src/components/settings/settings/EditSettingsForm.tsx b/src/components/settings/settings/EditSettingsForm.tsx index 8ccad9e49..0a05cb0e1 100644 --- a/src/components/settings/settings/EditSettingsForm.tsx +++ b/src/components/settings/settings/EditSettingsForm.tsx | |||
@@ -650,7 +650,7 @@ class EditSettingsForm extends Component<IProps, IState> { | |||
650 | <> | 650 | <> |
651 | <Slider | 651 | <Slider |
652 | type="number" | 652 | type="number" |
653 | onChange={e => this.submit(e)} | 653 | onSliderChange={e => this.submit(e)} |
654 | field={form.$('grayscaleServicesDim')} | 654 | field={form.$('grayscaleServicesDim')} |
655 | /> | 655 | /> |
656 | <Hr /> | 656 | <Hr /> |
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.tsx index 538a9a10c..3ef55fac6 100644 --- a/src/components/settings/team/TeamDashboard.js +++ b/src/components/settings/team/TeamDashboard.tsx | |||
@@ -1,11 +1,9 @@ | |||
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 | import ReactTooltip from 'react-tooltip'; | 4 | import ReactTooltip from 'react-tooltip'; |
6 | import injectSheet from 'react-jss'; | 5 | import withStyles, { WithStylesProps } from 'react-jss'; |
7 | import classnames from 'classnames'; | 6 | import classnames from 'classnames'; |
8 | |||
9 | import Loader from '../../ui/Loader'; | 7 | import Loader from '../../ui/Loader'; |
10 | import Button from '../../ui/button'; | 8 | import Button from '../../ui/button'; |
11 | import Infobox from '../../ui/Infobox'; | 9 | import Infobox from '../../ui/Infobox'; |
@@ -99,17 +97,17 @@ const styles = { | |||
99 | }, | 97 | }, |
100 | }; | 98 | }; |
101 | 99 | ||
102 | class TeamDashboard extends Component { | 100 | interface IProps extends WithStylesProps<typeof styles>, WrappedComponentProps { |
103 | static propTypes = { | 101 | isLoading: boolean; |
104 | isLoading: PropTypes.bool.isRequired, | 102 | userInfoRequestFailed: boolean; |
105 | userInfoRequestFailed: PropTypes.bool.isRequired, | 103 | retryUserInfoRequest: () => void; |
106 | retryUserInfoRequest: PropTypes.func.isRequired, | 104 | openTeamManagement: () => void; |
107 | openTeamManagement: PropTypes.func.isRequired, | 105 | server: string; |
108 | classes: PropTypes.object.isRequired, | 106 | } |
109 | server: PropTypes.string.isRequired, | ||
110 | }; | ||
111 | 107 | ||
112 | render() { | 108 | @observer |
109 | class TeamDashboard extends Component<IProps> { | ||
110 | render(): ReactElement { | ||
113 | const { | 111 | const { |
114 | isLoading, | 112 | isLoading, |
115 | userInfoRequestFailed, | 113 | userInfoRequestFailed, |
@@ -117,68 +115,65 @@ class TeamDashboard extends Component { | |||
117 | openTeamManagement, | 115 | openTeamManagement, |
118 | classes, | 116 | classes, |
119 | server, | 117 | server, |
118 | intl, | ||
120 | } = this.props; | 119 | } = this.props; |
121 | const { intl } = this.props; | ||
122 | 120 | ||
123 | if (server === LIVE_FRANZ_API) { | 121 | return server === LIVE_FRANZ_API ? ( |
124 | return ( | 122 | <div className="settings__main"> |
125 | <div className="settings__main"> | 123 | <div className="settings__header"> |
126 | <div className="settings__header"> | 124 | <span className="settings__header-item"> |
127 | <span className="settings__header-item"> | 125 | {intl.formatMessage(messages.headline)} |
128 | {intl.formatMessage(messages.headline)} | 126 | </span> |
129 | </span> | 127 | </div> |
130 | </div> | 128 | <div className="settings__body"> |
131 | <div className="settings__body"> | 129 | {isLoading && <Loader />} |
132 | {isLoading && <Loader />} | ||
133 | 130 | ||
134 | {!isLoading && userInfoRequestFailed && ( | 131 | {!isLoading && userInfoRequestFailed && ( |
135 | <Infobox | 132 | <Infobox |
136 | icon="alert" | 133 | icon="alert" |
137 | type="danger" | 134 | type="danger" |
138 | ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)} | 135 | ctaLabel={intl.formatMessage(messages.tryReloadUserInfoRequest)} |
139 | ctaLoading={isLoading} | 136 | ctaLoading={isLoading} |
140 | ctaOnClick={retryUserInfoRequest} | 137 | ctaOnClick={retryUserInfoRequest} |
141 | > | 138 | > |
142 | {intl.formatMessage(messages.userInfoRequestFailed)} | 139 | {intl.formatMessage(messages.userInfoRequestFailed)} |
143 | </Infobox> | 140 | </Infobox> |
144 | )} | 141 | )} |
145 | 142 | ||
146 | {!userInfoRequestFailed && !isLoading && ( | 143 | {!userInfoRequestFailed && !isLoading && ( |
147 | <> | 144 | <> |
148 | <H1 | 145 | <H1 |
149 | className={classnames({ | 146 | className={classnames({ |
150 | [classes.headline]: true, | 147 | [classes.headline]: true, |
151 | [classes.headlineWithSpacing]: true, | 148 | [classes.headlineWithSpacing]: true, |
152 | })} | 149 | })} |
153 | > | 150 | > |
154 | {intl.formatMessage(messages.contentHeadline)} | 151 | {intl.formatMessage(messages.contentHeadline)} |
155 | </H1> | 152 | </H1> |
156 | <div className={classes.container}> | 153 | <div className={classes.container}> |
157 | <div className={classes.content}> | 154 | <div className={classes.content}> |
158 | <p>{intl.formatMessage(messages.intro)}</p> | 155 | <p>{intl.formatMessage(messages.intro)}</p> |
159 | <p>{intl.formatMessage(messages.copy)}</p> | 156 | <p>{intl.formatMessage(messages.copy)}</p> |
160 | </div> | ||
161 | <img | ||
162 | className={classes.image} | ||
163 | src="https://cdn.franzinfra.com/announcements/assets/teams.png" | ||
164 | alt="Ferdium for Teams" | ||
165 | /> | ||
166 | </div> | ||
167 | <div className={classes.buttonContainer}> | ||
168 | <Button | ||
169 | label={intl.formatMessage(messages.manageButton)} | ||
170 | onClick={openTeamManagement} | ||
171 | className={classes.cta} | ||
172 | /> | ||
173 | </div> | 157 | </div> |
174 | </> | 158 | <img |
175 | )} | 159 | className={classes.image} |
176 | </div> | 160 | src="https://cdn.franzinfra.com/announcements/assets/teams.png" |
177 | <ReactTooltip place="right" type="dark" effect="solid" /> | 161 | alt="Ferdium for Teams" |
162 | /> | ||
163 | </div> | ||
164 | <div className={classes.buttonContainer}> | ||
165 | <Button | ||
166 | label={intl.formatMessage(messages.manageButton)} | ||
167 | onClick={openTeamManagement} | ||
168 | className={classes.cta} | ||
169 | /> | ||
170 | </div> | ||
171 | </> | ||
172 | )} | ||
178 | </div> | 173 | </div> |
179 | ); | 174 | <ReactTooltip place="right" type="dark" effect="solid" /> |
180 | } | 175 | </div> |
181 | return ( | 176 | ) : ( |
182 | <div className="settings__main"> | 177 | <div className="settings__main"> |
183 | <div className="settings__header"> | 178 | <div className="settings__header"> |
184 | <span className="settings__header-item"> | 179 | <span className="settings__header-item"> |
@@ -205,5 +200,5 @@ class TeamDashboard extends Component { | |||
205 | } | 200 | } |
206 | 201 | ||
207 | export default injectIntl( | 202 | export default injectIntl( |
208 | injectSheet(styles, { injectTheme: true })(observer(TeamDashboard)), | 203 | withStyles(styles, { injectTheme: true })(TeamDashboard), |
209 | ); | 204 | ); |
diff --git a/src/components/ui/FullscreenLoader/index.js b/src/components/ui/FullscreenLoader/index.js deleted file mode 100644 index f8c6b92ee..000000000 --- a/src/components/ui/FullscreenLoader/index.js +++ /dev/null | |||
@@ -1,52 +0,0 @@ | |||
1 | import { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectStyle from 'react-jss'; | ||
5 | import classnames from 'classnames'; | ||
6 | |||
7 | import Loader from '../Loader'; | ||
8 | |||
9 | import styles from './styles'; | ||
10 | import { H1 } from '../headline'; | ||
11 | |||
12 | class FullscreenLoader extends Component { | ||
13 | static propTypes = { | ||
14 | className: PropTypes.string, | ||
15 | title: PropTypes.string, | ||
16 | classes: PropTypes.object.isRequired, | ||
17 | theme: PropTypes.object.isRequired, | ||
18 | spinnerColor: PropTypes.string, | ||
19 | children: PropTypes.node, | ||
20 | }; | ||
21 | |||
22 | static defaultProps = { | ||
23 | className: null, | ||
24 | spinnerColor: null, | ||
25 | children: null, | ||
26 | title: null, | ||
27 | }; | ||
28 | |||
29 | render() { | ||
30 | const { classes, title, children, spinnerColor, className, theme } = | ||
31 | this.props; | ||
32 | |||
33 | return ( | ||
34 | <div className={classes.wrapper}> | ||
35 | <div | ||
36 | className={classnames({ | ||
37 | [`${classes.component}`]: true, | ||
38 | [`${className}`]: className, | ||
39 | })} | ||
40 | > | ||
41 | <H1 className={classes.title}>{title}</H1> | ||
42 | <Loader color={spinnerColor || theme.colorFullscreenLoaderSpinner} /> | ||
43 | {children && <div className={classes.content}>{children}</div>} | ||
44 | </div> | ||
45 | </div> | ||
46 | ); | ||
47 | } | ||
48 | } | ||
49 | |||
50 | export default injectStyle(styles, { injectTheme: true })( | ||
51 | observer(FullscreenLoader), | ||
52 | ); | ||
diff --git a/src/components/ui/FullscreenLoader/index.tsx b/src/components/ui/FullscreenLoader/index.tsx new file mode 100644 index 000000000..002ee7932 --- /dev/null +++ b/src/components/ui/FullscreenLoader/index.tsx | |||
@@ -0,0 +1,54 @@ | |||
1 | import { Component, ReactElement, ReactNode } from 'react'; | ||
2 | import { observer } from 'mobx-react'; | ||
3 | import withStyles, { WithStylesProps } from 'react-jss'; | ||
4 | import classnames from 'classnames'; | ||
5 | import Loader from '../Loader'; | ||
6 | import styles from './styles'; | ||
7 | import { H1 } from '../headline'; | ||
8 | import { Theme } from '../../../themes'; | ||
9 | |||
10 | interface IProps extends WithStylesProps<typeof styles> { | ||
11 | className?: string; | ||
12 | title?: string; | ||
13 | theme?: Theme; | ||
14 | spinnerColor?: string; | ||
15 | loaded?: boolean; | ||
16 | children?: ReactNode; | ||
17 | } | ||
18 | |||
19 | @observer | ||
20 | class FullscreenLoader extends Component<IProps> { | ||
21 | render(): ReactElement { | ||
22 | const { | ||
23 | classes, | ||
24 | theme = '', | ||
25 | className = '', | ||
26 | spinnerColor = '', | ||
27 | children = null, | ||
28 | title = '', | ||
29 | loaded = false, | ||
30 | } = this.props; | ||
31 | |||
32 | return ( | ||
33 | <div className={classes.wrapper}> | ||
34 | <div | ||
35 | className={classnames({ | ||
36 | [`${classes.component}`]: true, | ||
37 | [`${className}`]: className, | ||
38 | })} | ||
39 | > | ||
40 | <H1 className={classes.title}>{title}</H1> | ||
41 | <Loader | ||
42 | color={ | ||
43 | spinnerColor || (theme && theme.colorFullscreenLoaderSpinner) | ||
44 | } | ||
45 | loaded={loaded} | ||
46 | /> | ||
47 | {children && <div className={classes.content}>{children}</div>} | ||
48 | </div> | ||
49 | </div> | ||
50 | ); | ||
51 | } | ||
52 | } | ||
53 | |||
54 | export default withStyles(styles, { injectTheme: true })(FullscreenLoader); | ||
diff --git a/src/components/ui/Loader.tsx b/src/components/ui/Loader.tsx index 5e78ed47a..ebb437d9d 100644 --- a/src/components/ui/Loader.tsx +++ b/src/components/ui/Loader.tsx | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, PropsWithChildren } from 'react'; | 1 | import { Component, ReactElement, ReactNode } from 'react'; |
2 | import { observer, inject } from 'mobx-react'; | 2 | import { observer, inject } from 'mobx-react'; |
3 | import Loader from 'react-loader'; | 3 | import Loader from 'react-loader'; |
4 | 4 | ||
@@ -9,31 +9,30 @@ interface IProps { | |||
9 | color?: string; | 9 | color?: string; |
10 | loaded?: boolean; | 10 | loaded?: boolean; |
11 | stores?: FerdiumStores; | 11 | stores?: FerdiumStores; |
12 | children?: ReactNode; | ||
12 | } | 13 | } |
13 | 14 | ||
14 | // Can this file be merged into the './loader/index.tsx' file? | 15 | // Can this file be merged into the './loader/index.tsx' file? |
15 | @inject('stores') | 16 | @inject('stores') |
16 | @observer | 17 | @observer |
17 | class LoaderComponent extends Component<PropsWithChildren<IProps>> { | 18 | class LoaderComponent extends Component<IProps> { |
18 | static defaultProps = { | 19 | render(): ReactElement { |
19 | loaded: false, | 20 | const { |
20 | color: 'ACCENT', | 21 | loaded = false, |
21 | }; | 22 | color = 'ACCENT', |
23 | className, | ||
24 | children, | ||
25 | } = this.props; | ||
22 | 26 | ||
23 | render() { | 27 | const loaderColor = |
24 | const { children, loaded, className } = this.props; | 28 | color !== 'ACCENT' ? color : this.props.stores!.settings.app.accentColor; |
25 | |||
26 | const color = | ||
27 | this.props.color !== 'ACCENT' | ||
28 | ? this.props.color | ||
29 | : this.props.stores!.settings.app.accentColor; | ||
30 | 29 | ||
31 | return ( | 30 | return ( |
32 | <Loader | 31 | <Loader |
33 | loaded={loaded} | 32 | loaded={loaded} |
34 | width={4} | 33 | width={4} |
35 | scale={0.6} | 34 | scale={0.6} |
36 | color={color} | 35 | color={loaderColor} |
37 | component="span" | 36 | component="span" |
38 | className={className} | 37 | className={className} |
39 | > | 38 | > |
diff --git a/src/components/ui/ServiceIcon.js b/src/components/ui/ServiceIcon.tsx index b05d791be..39a32e44d 100644 --- a/src/components/ui/ServiceIcon.js +++ b/src/components/ui/ServiceIcon.tsx | |||
@@ -1,9 +1,7 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, ReactNode } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | 2 | import { observer } from 'mobx-react'; |
4 | import injectSheet from 'react-jss'; | 3 | import withStyles, { WithStylesProps } from 'react-jss'; |
5 | import classnames from 'classnames'; | 4 | import classnames from 'classnames'; |
6 | |||
7 | import ServiceModel from '../../models/Service'; | 5 | import ServiceModel from '../../models/Service'; |
8 | 6 | ||
9 | const styles = theme => ({ | 7 | const styles = theme => ({ |
@@ -24,20 +22,16 @@ const styles = theme => ({ | |||
24 | }, | 22 | }, |
25 | }); | 23 | }); |
26 | 24 | ||
27 | // Should this file be converted into the coding style similar to './toggle/index.tsx'? | 25 | interface IProps extends WithStylesProps<typeof styles> { |
28 | class ServiceIcon extends Component { | 26 | service: ServiceModel; |
29 | static propTypes = { | 27 | className?: string; |
30 | classes: PropTypes.object.isRequired, | 28 | } |
31 | service: PropTypes.instanceOf(ServiceModel).isRequired, | ||
32 | className: PropTypes.string, | ||
33 | }; | ||
34 | |||
35 | static defaultProps = { | ||
36 | className: '', | ||
37 | }; | ||
38 | 29 | ||
39 | render() { | 30 | // TODO - [TS DEBT] Should this file be converted into the coding style similar to './toggle/index.tsx'? |
40 | const { classes, className, service } = this.props; | 31 | @observer |
32 | class ServiceIcon extends Component<IProps> { | ||
33 | render(): ReactNode { | ||
34 | const { classes, className = '', service } = this.props; | ||
41 | 35 | ||
42 | return ( | 36 | return ( |
43 | <div className={classnames([classes.root, className])}> | 37 | <div className={classnames([classes.root, className])}> |
@@ -55,6 +49,4 @@ class ServiceIcon extends Component { | |||
55 | } | 49 | } |
56 | } | 50 | } |
57 | 51 | ||
58 | export default injectSheet(styles, { injectTheme: true })( | 52 | export default withStyles(styles, { injectTheme: true })(ServiceIcon); |
59 | observer(ServiceIcon), | ||
60 | ); | ||
diff --git a/src/components/ui/Slider.js b/src/components/ui/Slider.tsx index 90f4df1c4..ed9fe9073 100644 --- a/src/components/ui/Slider.js +++ b/src/components/ui/Slider.tsx | |||
@@ -1,32 +1,34 @@ | |||
1 | import { Component } from 'react'; | 1 | import { ChangeEvent, Component, ReactElement } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | 2 | import { observer } from 'mobx-react'; |
4 | import classnames from 'classnames'; | 3 | import classnames from 'classnames'; |
5 | import { Field } from 'mobx-react-form'; | 4 | import { noop } from 'lodash'; |
6 | 5 | ||
7 | // Should this file be converted into the coding style similar to './toggle/index.tsx'? | 6 | interface IProps { |
8 | class Slider extends Component { | 7 | field: any; |
9 | static propTypes = { | 8 | className?: string; |
10 | field: PropTypes.instanceOf(Field).isRequired, | 9 | showLabel?: boolean; |
11 | className: PropTypes.string, | 10 | disabled?: boolean; |
12 | showLabel: PropTypes.bool, | 11 | type?: 'range' | 'number'; |
13 | disabled: PropTypes.bool, | 12 | onSliderChange?: (e: ChangeEvent) => void; |
14 | }; | 13 | } |
15 | |||
16 | static defaultProps = { | ||
17 | className: '', | ||
18 | showLabel: true, | ||
19 | disabled: false, | ||
20 | }; | ||
21 | |||
22 | onChange(e) { | ||
23 | const { field } = this.props; | ||
24 | 14 | ||
15 | // TODO - [TS DEBT] Should this file be converted into the coding style similar to './toggle/index.tsx'? | ||
16 | @observer | ||
17 | class Slider extends Component<IProps> { | ||
18 | onChange(e: ChangeEvent<HTMLInputElement>): void { | ||
19 | const { field, onSliderChange = noop } = this.props; | ||
25 | field.onChange(e); | 20 | field.onChange(e); |
21 | onSliderChange(e); | ||
26 | } | 22 | } |
27 | 23 | ||
28 | render() { | 24 | render(): ReactElement { |
29 | const { field, className, showLabel, disabled } = this.props; | 25 | const { |
26 | field, | ||
27 | className = '', | ||
28 | showLabel = true, | ||
29 | disabled = false, | ||
30 | type = 'range', | ||
31 | } = this.props; | ||
30 | 32 | ||
31 | if (field.value === '' && field.default !== '') { | 33 | if (field.value === '' && field.default !== '') { |
32 | field.value = field.default; | 34 | field.value = field.default; |
@@ -43,7 +45,7 @@ class Slider extends Component { | |||
43 | <div className="slider-container"> | 45 | <div className="slider-container"> |
44 | <input | 46 | <input |
45 | className="slider" | 47 | className="slider" |
46 | type="range" | 48 | type={type} |
47 | id={field.id} | 49 | id={field.id} |
48 | name={field.name} | 50 | name={field.name} |
49 | value={field.value} | 51 | value={field.value} |
@@ -64,4 +66,4 @@ class Slider extends Component { | |||
64 | } | 66 | } |
65 | } | 67 | } |
66 | 68 | ||
67 | export default observer(Slider); | 69 | export default Slider; |
diff --git a/src/components/ui/StatusBarTargetUrl.js b/src/components/ui/StatusBarTargetUrl.tsx index 3e0c98c5d..7b053f410 100644 --- a/src/components/ui/StatusBarTargetUrl.js +++ b/src/components/ui/StatusBarTargetUrl.tsx | |||
@@ -1,24 +1,18 @@ | |||
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 classnames from 'classnames'; | 3 | import classnames from 'classnames'; |
5 | |||
6 | import Appear from './effects/Appear'; | 4 | import Appear from './effects/Appear'; |
7 | 5 | ||
8 | // Should this file be converted into the coding style similar to './toggle/index.tsx'? | 6 | interface IProps { |
9 | class StatusBarTargetUrl extends Component { | 7 | className?: string; |
10 | static propTypes = { | 8 | text?: string; |
11 | className: PropTypes.string, | 9 | } |
12 | text: PropTypes.string, | ||
13 | }; | ||
14 | |||
15 | static defaultProps = { | ||
16 | className: '', | ||
17 | text: '', | ||
18 | }; | ||
19 | 10 | ||
11 | // TODO - [TS DEBT] Should this file be converted into the coding style similar to './toggle/index.tsx'? | ||
12 | @observer | ||
13 | class StatusBarTargetUrl extends Component<IProps> { | ||
20 | render() { | 14 | render() { |
21 | const { className, text } = this.props; | 15 | const { className = '', text = '' } = this.props; |
22 | 16 | ||
23 | return ( | 17 | return ( |
24 | <Appear | 18 | <Appear |
@@ -33,4 +27,4 @@ class StatusBarTargetUrl extends Component { | |||
33 | } | 27 | } |
34 | } | 28 | } |
35 | 29 | ||
36 | export default observer(StatusBarTargetUrl); | 30 | export default StatusBarTargetUrl; |
diff --git a/src/components/ui/WebviewLoader/index.js b/src/components/ui/WebviewLoader/index.js deleted file mode 100644 index 20945d191..000000000 --- a/src/components/ui/WebviewLoader/index.js +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | import { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { defineMessages, injectIntl } from 'react-intl'; | ||
6 | |||
7 | import FullscreenLoader from '../FullscreenLoader'; | ||
8 | import styles from './styles'; | ||
9 | |||
10 | const messages = defineMessages({ | ||
11 | loading: { | ||
12 | id: 'service.webviewLoader.loading', | ||
13 | defaultMessage: 'Loading {service}', | ||
14 | }, | ||
15 | }); | ||
16 | |||
17 | class WebviewLoader extends Component { | ||
18 | static propTypes = { | ||
19 | name: PropTypes.string.isRequired, | ||
20 | classes: PropTypes.object.isRequired, | ||
21 | }; | ||
22 | |||
23 | render() { | ||
24 | const { classes, name } = this.props; | ||
25 | const { intl } = this.props; | ||
26 | return ( | ||
27 | <FullscreenLoader | ||
28 | className={classes.component} | ||
29 | title={`${intl.formatMessage(messages.loading, { service: name })}`} | ||
30 | /> | ||
31 | ); | ||
32 | } | ||
33 | } | ||
34 | |||
35 | export default injectIntl( | ||
36 | injectSheet(styles, { injectTheme: true })(observer(WebviewLoader)), | ||
37 | ); | ||
diff --git a/src/components/ui/WebviewLoader/index.tsx b/src/components/ui/WebviewLoader/index.tsx new file mode 100644 index 000000000..81923b6ca --- /dev/null +++ b/src/components/ui/WebviewLoader/index.tsx | |||
@@ -0,0 +1,44 @@ | |||
1 | import { Component, ReactElement } from 'react'; | ||
2 | import { observer } from 'mobx-react'; | ||
3 | import injectSheet, { WithStylesProps } from 'react-jss'; | ||
4 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; | ||
5 | import FullscreenLoader from '../FullscreenLoader'; | ||
6 | |||
7 | const messages = defineMessages({ | ||
8 | loading: { | ||
9 | id: 'service.webviewLoader.loading', | ||
10 | defaultMessage: 'Loading {service}', | ||
11 | }, | ||
12 | }); | ||
13 | |||
14 | const styles = theme => ({ | ||
15 | component: { | ||
16 | background: theme.colorWebviewLoaderBackground, | ||
17 | padding: 20, | ||
18 | width: 'auto', | ||
19 | margin: [0, 'auto'], | ||
20 | borderRadius: 6, | ||
21 | }, | ||
22 | }); | ||
23 | |||
24 | interface IProps extends WithStylesProps<typeof styles>, WrappedComponentProps { | ||
25 | name: string; | ||
26 | loaded?: boolean; | ||
27 | } | ||
28 | |||
29 | class WebviewLoader extends Component<IProps> { | ||
30 | render(): ReactElement { | ||
31 | const { classes, name, loaded = false, intl } = this.props; | ||
32 | return ( | ||
33 | <FullscreenLoader | ||
34 | className={classes.component} | ||
35 | title={`${intl.formatMessage(messages.loading, { service: name })}`} | ||
36 | loaded={loaded} | ||
37 | /> | ||
38 | ); | ||
39 | } | ||
40 | } | ||
41 | |||
42 | export default injectIntl( | ||
43 | injectSheet(styles, { injectTheme: true })(observer(WebviewLoader)), | ||
44 | ); | ||
diff --git a/src/components/ui/WebviewLoader/styles.ts b/src/components/ui/WebviewLoader/styles.ts deleted file mode 100644 index dbd75db8a..000000000 --- a/src/components/ui/WebviewLoader/styles.ts +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | export default theme => ({ | ||
2 | component: { | ||
3 | background: theme.colorWebviewLoaderBackground, | ||
4 | padding: 20, | ||
5 | width: 'auto', | ||
6 | margin: [0, 'auto'], | ||
7 | borderRadius: 6, | ||
8 | }, | ||
9 | }); | ||
diff --git a/src/features/todos/actions.ts b/src/features/todos/actions.ts index b47a076b9..31b14d40b 100644 --- a/src/features/todos/actions.ts +++ b/src/features/todos/actions.ts | |||
@@ -1,17 +1,19 @@ | |||
1 | import { Webview } from 'react-electron-web-view'; | ||
1 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
2 | import { ReactElement } from 'react'; | ||
3 | import { createActionsFromDefinitions } from '../../actions/lib/actions'; | 3 | import { createActionsFromDefinitions } from '../../actions/lib/actions'; |
4 | 4 | ||
5 | export interface TodoClientMessage { | ||
6 | action: string; | ||
7 | data: object; | ||
8 | } | ||
9 | |||
5 | interface TodoActionsType { | 10 | interface TodoActionsType { |
6 | resize: (width: number) => void; | 11 | resize: (width: number) => void; |
7 | toggleTodosPanel: () => void; | 12 | toggleTodosPanel: () => void; |
8 | toggleTodosFeatureVisibility: () => void; | 13 | toggleTodosFeatureVisibility: () => void; |
9 | setTodosWebview: (webview: ReactElement) => void; | 14 | setTodosWebview: (webview: Webview) => void; |
10 | handleHostMessage: (action: string, data: object) => void; | 15 | handleHostMessage: (action: string, data: object) => void; |
11 | handleClientMessage: ( | 16 | handleClientMessage: (channel: string, message: TodoClientMessage) => void; |
12 | channel: string, | ||
13 | message: { action: string; data: object }, | ||
14 | ) => void; | ||
15 | openDevTools: () => void; | 17 | openDevTools: () => void; |
16 | reload: () => void; | 18 | reload: () => void; |
17 | } | 19 | } |
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.tsx index 780864b91..3385ff74c 100644 --- a/src/features/todos/components/TodosWebview.js +++ b/src/features/todos/components/TodosWebview.tsx | |||
@@ -1,11 +1,10 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, createRef, ReactElement, MouseEvent } from 'react'; |
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | 2 | import { observer } from 'mobx-react'; |
4 | import injectSheet from 'react-jss'; | 3 | import withStyles, { WithStylesProps } from 'react-jss'; |
5 | import Webview from 'react-electron-web-view'; | 4 | import Webview from 'react-electron-web-view'; |
6 | import classnames from 'classnames'; | 5 | import classnames from 'classnames'; |
7 | |||
8 | import { TODOS_PARTITION_ID } from '../../../config'; | 6 | import { TODOS_PARTITION_ID } from '../../../config'; |
7 | import { TodoClientMessage } from '../actions'; | ||
9 | 8 | ||
10 | const styles = theme => ({ | 9 | const styles = theme => ({ |
11 | root: { | 10 | root: { |
@@ -48,51 +47,71 @@ const styles = theme => ({ | |||
48 | }, | 47 | }, |
49 | }); | 48 | }); |
50 | 49 | ||
51 | class TodosWebview extends Component { | 50 | interface IProps extends WithStylesProps<typeof styles> { |
52 | static propTypes = { | 51 | isTodosServiceActive: boolean; |
53 | classes: PropTypes.object.isRequired, | 52 | isVisible: boolean; |
54 | isTodosServiceActive: PropTypes.bool.isRequired, | 53 | handleClientMessage: (channel: string, message: TodoClientMessage) => void; |
55 | isVisible: PropTypes.bool.isRequired, | 54 | setTodosWebview: (webView: Webview) => void; |
56 | handleClientMessage: PropTypes.func.isRequired, | 55 | resize: (newWidth: number) => void; |
57 | setTodosWebview: PropTypes.func.isRequired, | 56 | width: number; |
58 | resize: PropTypes.func.isRequired, | 57 | minWidth: number; |
59 | width: PropTypes.number.isRequired, | 58 | userAgent: string; |
60 | minWidth: PropTypes.number.isRequired, | 59 | todoUrl: string; |
61 | userAgent: PropTypes.string.isRequired, | 60 | isTodoUrlValid: boolean; |
62 | todoUrl: PropTypes.string.isRequired, | 61 | } |
63 | isTodoUrlValid: PropTypes.bool.isRequired, | 62 | |
64 | }; | 63 | interface IState { |
65 | 64 | isDragging: boolean; | |
66 | state = { | 65 | width: number; |
67 | isDragging: false, | 66 | initialPos: number; |
68 | width: 300, | 67 | delta: number; |
69 | }; | 68 | } |
69 | |||
70 | @observer | ||
71 | class TodosWebview extends Component<IProps, IState> { | ||
72 | private node = createRef<HTMLDivElement>(); | ||
73 | |||
74 | private webview: Webview; | ||
75 | |||
76 | constructor(props: IProps) { | ||
77 | super(props); | ||
78 | |||
79 | this.state = { | ||
80 | isDragging: false, | ||
81 | width: 300, | ||
82 | initialPos: 0, | ||
83 | delta: 0, | ||
84 | }; | ||
85 | this.resizePanel = this.resizePanel.bind(this); | ||
86 | this.stopResize = this.stopResize.bind(this); | ||
87 | } | ||
70 | 88 | ||
71 | componentDidMount() { | 89 | componentDidMount() { |
72 | this.setState({ | 90 | this.setState({ |
73 | width: this.props.width, | 91 | width: this.props.width, |
74 | }); | 92 | }); |
75 | 93 | ||
76 | this.node.addEventListener('mousemove', this.resizePanel.bind(this)); | 94 | if (this.node.current) { |
77 | this.node.addEventListener('mouseup', this.stopResize.bind(this)); | 95 | this.node.current.addEventListener('mousemove', this.resizePanel); |
78 | this.node.addEventListener('mouseleave', this.stopResize.bind(this)); | 96 | this.node.current.addEventListener('mouseup', this.stopResize); |
97 | this.node.current.addEventListener('mouseleave', this.stopResize); | ||
98 | } | ||
79 | } | 99 | } |
80 | 100 | ||
81 | startResize = event => { | 101 | startResize(e: MouseEvent<HTMLDivElement>): void { |
82 | this.setState({ | 102 | this.setState({ |
83 | isDragging: true, | 103 | isDragging: true, |
84 | initialPos: event.clientX, | 104 | initialPos: e.clientX, |
85 | delta: 0, | 105 | delta: 0, |
86 | }); | 106 | }); |
87 | }; | 107 | } |
88 | 108 | ||
89 | resizePanel(e) { | 109 | resizePanel(e: MouseEventInit): void { |
90 | const { minWidth } = this.props; | 110 | const { minWidth } = this.props; |
91 | |||
92 | const { isDragging, initialPos } = this.state; | 111 | const { isDragging, initialPos } = this.state; |
93 | 112 | ||
94 | if (isDragging && Math.abs(e.clientX - window.innerWidth) > minWidth) { | 113 | if (isDragging && Math.abs(e.clientX! - window.innerWidth) > minWidth) { |
95 | const delta = e.clientX - initialPos; | 114 | const delta = e.clientX! - initialPos; |
96 | 115 | ||
97 | this.setState({ | 116 | this.setState({ |
98 | delta, | 117 | delta, |
@@ -100,9 +119,8 @@ class TodosWebview extends Component { | |||
100 | } | 119 | } |
101 | } | 120 | } |
102 | 121 | ||
103 | stopResize() { | 122 | stopResize(): void { |
104 | const { resize, minWidth } = this.props; | 123 | const { resize, minWidth } = this.props; |
105 | |||
106 | const { isDragging, delta, width } = this.state; | 124 | const { isDragging, delta, width } = this.state; |
107 | 125 | ||
108 | if (isDragging) { | 126 | if (isDragging) { |
@@ -123,14 +141,17 @@ class TodosWebview extends Component { | |||
123 | } | 141 | } |
124 | 142 | ||
125 | startListeningToIpcMessages() { | 143 | startListeningToIpcMessages() { |
144 | if (!this.webview) { | ||
145 | return; | ||
146 | } | ||
147 | |||
126 | const { handleClientMessage } = this.props; | 148 | const { handleClientMessage } = this.props; |
127 | if (!this.webview) return; | ||
128 | this.webview.addEventListener('ipc-message', e => { | 149 | this.webview.addEventListener('ipc-message', e => { |
129 | handleClientMessage({ channel: e.channel, message: e.args[0] }); | 150 | handleClientMessage(e.channel, e.args[0]); |
130 | }); | 151 | }); |
131 | } | 152 | } |
132 | 153 | ||
133 | render() { | 154 | render(): ReactElement { |
134 | const { | 155 | const { |
135 | classes, | 156 | classes, |
136 | isTodosServiceActive, | 157 | isTodosServiceActive, |
@@ -141,10 +162,9 @@ class TodosWebview extends Component { | |||
141 | } = this.props; | 162 | } = this.props; |
142 | 163 | ||
143 | const { width, delta, isDragging } = this.state; | 164 | const { width, delta, isDragging } = this.state; |
144 | |||
145 | let displayedWidth = isVisible ? width : 0; | 165 | let displayedWidth = isVisible ? width : 0; |
146 | if (isTodosServiceActive) { | 166 | if (isTodosServiceActive) { |
147 | displayedWidth = null; | 167 | displayedWidth = 0; |
148 | } | 168 | } |
149 | 169 | ||
150 | return ( | 170 | return ( |
@@ -157,9 +177,7 @@ class TodosWebview extends Component { | |||
157 | })} | 177 | })} |
158 | style={{ width: displayedWidth }} | 178 | style={{ width: displayedWidth }} |
159 | onMouseUp={() => this.stopResize()} | 179 | onMouseUp={() => this.stopResize()} |
160 | ref={node => { | 180 | ref={this.node} |
161 | this.node = node; | ||
162 | }} | ||
163 | id="todos-panel" | 181 | id="todos-panel" |
164 | > | 182 | > |
165 | <div | 183 | <div |
@@ -168,7 +186,7 @@ class TodosWebview extends Component { | |||
168 | left: delta, | 186 | left: delta, |
169 | ...(isDragging ? { width: 600, marginLeft: -200 } : {}), | 187 | ...(isDragging ? { width: 600, marginLeft: -200 } : {}), |
170 | }} // This hack is required as resizing with webviews beneath behaves quite bad | 188 | }} // This hack is required as resizing with webviews beneath behaves quite bad |
171 | onMouseDown={e => this.startResize(e)} | 189 | onMouseDown={this.startResize} |
172 | /> | 190 | /> |
173 | {isDragging && ( | 191 | {isDragging && ( |
174 | <div | 192 | <div |
@@ -178,7 +196,7 @@ class TodosWebview extends Component { | |||
178 | )} | 196 | )} |
179 | {isTodoUrlValid && ( | 197 | {isTodoUrlValid && ( |
180 | <Webview | 198 | <Webview |
181 | className={classes.webview} | 199 | // className={classes.webview} // TODO - [TS DEBT] style not found |
182 | onDidAttach={() => { | 200 | onDidAttach={() => { |
183 | const { setTodosWebview } = this.props; | 201 | const { setTodosWebview } = this.props; |
184 | setTodosWebview(this.webview); | 202 | setTodosWebview(this.webview); |
@@ -198,6 +216,4 @@ class TodosWebview extends Component { | |||
198 | } | 216 | } |
199 | } | 217 | } |
200 | 218 | ||
201 | export default injectSheet(styles, { injectTheme: true })( | 219 | export default withStyles(styles, { injectTheme: true })(TodosWebview); |
202 | observer(TodosWebview), | ||
203 | ); | ||
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.tsx index b97506767..17f61bd95 100644 --- a/src/features/todos/containers/TodosScreen.js +++ b/src/features/todos/containers/TodosScreen.tsx | |||
@@ -1,22 +1,26 @@ | |||
1 | import { Component } from 'react'; | 1 | import { Component, ReactElement } from 'react'; |
2 | import { observer, inject } from 'mobx-react'; | 2 | import { observer, inject } from 'mobx-react'; |
3 | import PropTypes from 'prop-types'; | ||
4 | |||
5 | import FeaturesStore from '../../../stores/FeaturesStore'; | ||
6 | import TodosWebview from '../components/TodosWebview'; | 3 | import TodosWebview from '../components/TodosWebview'; |
7 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; | 4 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; |
8 | import { todosStore } from '..'; | 5 | import { todosStore } from '..'; |
9 | import { TODOS_MIN_WIDTH } from '../../../config'; | 6 | import { TODOS_MIN_WIDTH } from '../../../config'; |
10 | import { todoActions } from '../actions'; | 7 | import { todoActions } from '../actions'; |
11 | import ServicesStore from '../../../stores/ServicesStore'; | 8 | import { RealStores } from '../../../stores'; |
9 | |||
10 | interface IProps { | ||
11 | stores?: RealStores; | ||
12 | } | ||
12 | 13 | ||
13 | class TodosScreen extends Component { | 14 | @inject('stores', 'actions') |
14 | render() { | 15 | @observer |
15 | if ( | 16 | class TodosScreen extends Component<IProps> { |
17 | render(): ReactElement | null { | ||
18 | const showTodoScreen = | ||
16 | !todosStore || | 19 | !todosStore || |
17 | !todosStore.isFeatureActive || | 20 | !todosStore.isFeatureActive || |
18 | todosStore.isTodosPanelForceHidden | 21 | todosStore.isTodosPanelForceHidden; |
19 | ) { | 22 | |
23 | if (showTodoScreen) { | ||
20 | return null; | 24 | return null; |
21 | } | 25 | } |
22 | 26 | ||
@@ -24,15 +28,15 @@ class TodosScreen extends Component { | |||
24 | <ErrorBoundary> | 28 | <ErrorBoundary> |
25 | <TodosWebview | 29 | <TodosWebview |
26 | isTodosServiceActive={ | 30 | isTodosServiceActive={ |
27 | this.props.stores.services.isTodosServiceActive || false | 31 | this.props.stores!.services.isTodosServiceActive || false |
28 | } | 32 | } |
29 | isVisible={todosStore.isTodosPanelVisible} | 33 | isVisible={todosStore.isTodosPanelVisible} |
30 | togglePanel={todoActions.toggleTodosPanel} | 34 | // togglePanel={todoActions.toggleTodosPanel} // TODO - [TECH DEBT][PROP NOT USED IN COMPONENT] check it later |
31 | handleClientMessage={todoActions.handleClientMessage} | 35 | handleClientMessage={todoActions.handleClientMessage} |
32 | setTodosWebview={webview => todoActions.setTodosWebview({ webview })} | 36 | setTodosWebview={webview => todoActions.setTodosWebview(webview)} |
33 | width={todosStore.width} | 37 | width={todosStore.width} |
34 | minWidth={TODOS_MIN_WIDTH} | 38 | minWidth={TODOS_MIN_WIDTH} |
35 | resize={width => todoActions.resize({ width })} | 39 | resize={width => todoActions.resize(width)} |
36 | userAgent={todosStore.userAgent} | 40 | userAgent={todosStore.userAgent} |
37 | todoUrl={todosStore.todoUrl} | 41 | todoUrl={todosStore.todoUrl} |
38 | isTodoUrlValid={todosStore.isTodoUrlValid} | 42 | isTodoUrlValid={todosStore.isTodoUrlValid} |
@@ -42,11 +46,4 @@ class TodosScreen extends Component { | |||
42 | } | 46 | } |
43 | } | 47 | } |
44 | 48 | ||
45 | export default inject('stores', 'actions')(observer(TodosScreen)); | 49 | export default TodosScreen; |
46 | |||
47 | TodosScreen.propTypes = { | ||
48 | stores: PropTypes.shape({ | ||
49 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
50 | services: PropTypes.instanceOf(ServicesStore).isRequired, | ||
51 | }).isRequired, | ||
52 | }; | ||
diff --git a/src/features/utils/FeatureStore.js b/src/features/utils/FeatureStore.ts index eada332d7..2bdd167f3 100644 --- a/src/features/utils/FeatureStore.js +++ b/src/features/utils/FeatureStore.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import Reaction from '../../stores/lib/Reaction'; | ||
2 | |||
1 | export default class FeatureStore { | 3 | export default class FeatureStore { |
2 | _actions = []; | 4 | _actions: any = []; |
3 | 5 | ||
4 | _reactions = []; | 6 | _reactions: Reaction[] = []; |
5 | 7 | ||
6 | stop() { | 8 | stop() { |
7 | this._stopActions(); | 9 | this._stopActions(); |
@@ -9,32 +11,38 @@ export default class FeatureStore { | |||
9 | } | 11 | } |
10 | 12 | ||
11 | // ACTIONS | 13 | // ACTIONS |
12 | |||
13 | _registerActions(actions) { | 14 | _registerActions(actions) { |
14 | this._actions = actions; | 15 | this._actions = actions; |
15 | this._startActions(); | 16 | this._startActions(); |
16 | } | 17 | } |
17 | 18 | ||
18 | _startActions(actions = this._actions) { | 19 | _startActions(actions = this._actions) { |
19 | for (const a of actions) a.start(); | 20 | for (const action of actions) { |
21 | action.start(); | ||
22 | } | ||
20 | } | 23 | } |
21 | 24 | ||
22 | _stopActions(actions = this._actions) { | 25 | _stopActions(actions = this._actions) { |
23 | for (const a of actions) a.stop(); | 26 | for (const action of actions) { |
27 | action.stop(); | ||
28 | } | ||
24 | } | 29 | } |
25 | 30 | ||
26 | // REACTIONS | 31 | // REACTIONS |
27 | |||
28 | _registerReactions(reactions) { | 32 | _registerReactions(reactions) { |
29 | this._reactions = reactions; | 33 | this._reactions = reactions; |
30 | this._startReactions(); | 34 | this._startReactions(); |
31 | } | 35 | } |
32 | 36 | ||
33 | _startReactions(reactions = this._reactions) { | 37 | _startReactions(reactions = this._reactions) { |
34 | for (const r of reactions) r.start(); | 38 | for (const reaction of reactions) { |
39 | reaction.start(); | ||
40 | } | ||
35 | } | 41 | } |
36 | 42 | ||
37 | _stopReactions(reactions = this._reactions) { | 43 | _stopReactions(reactions = this._reactions) { |
38 | for (const r of reactions) r.stop(); | 44 | for (const reaction of reactions) { |
45 | reaction.stop(); | ||
46 | } | ||
39 | } | 47 | } |
40 | } | 48 | } |
diff --git a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js b/src/features/workspaces/components/WorkspaceSwitchingIndicator.tsx index ff73758c1..c9af22c96 100644 --- a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js +++ b/src/features/workspaces/components/WorkspaceSwitchingIndicator.tsx | |||
@@ -1,12 +1,11 @@ | |||
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 injectSheet from 'react-jss'; | 3 | import withStyles, { WithStylesProps } from 'react-jss'; |
5 | import classnames from 'classnames'; | 4 | import classnames from 'classnames'; |
6 | import { defineMessages, injectIntl } from 'react-intl'; | 5 | import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; |
7 | |||
8 | import Loader from '../../../components/ui/loader/index'; | 6 | import Loader from '../../../components/ui/loader/index'; |
9 | import { workspaceStore } from '../index'; | 7 | import { workspaceStore } from '../index'; |
8 | import { Theme } from '../../../themes'; | ||
10 | 9 | ||
11 | const messages = defineMessages({ | 10 | const messages = defineMessages({ |
12 | switchingTo: { | 11 | switchingTo: { |
@@ -15,11 +14,10 @@ const messages = defineMessages({ | |||
15 | }, | 14 | }, |
16 | }); | 15 | }); |
17 | 16 | ||
18 | let wrapperTransition = 'none'; | 17 | const wrapperTransition = |
19 | 18 | window && window.matchMedia('(prefers-reduced-motion: no-preference)') | |
20 | if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { | 19 | ? 'width 0.5s ease' |
21 | wrapperTransition = 'width 0.5s ease'; | 20 | : 'none'; |
22 | } | ||
23 | 21 | ||
24 | const styles = theme => ({ | 22 | const styles = theme => ({ |
25 | wrapper: { | 23 | wrapper: { |
@@ -53,26 +51,30 @@ const styles = theme => ({ | |||
53 | }, | 51 | }, |
54 | }); | 52 | }); |
55 | 53 | ||
56 | class WorkspaceSwitchingIndicator extends Component { | 54 | interface IProps extends WithStylesProps<typeof styles>, WrappedComponentProps { |
57 | static propTypes = { | 55 | theme?: Theme; |
58 | classes: PropTypes.object.isRequired, | 56 | } |
59 | theme: PropTypes.object.isRequired, | ||
60 | }; | ||
61 | 57 | ||
62 | render() { | 58 | @observer |
63 | const { classes, theme } = this.props; | 59 | class WorkspaceSwitchingIndicator extends Component<IProps> { |
64 | const { intl } = this.props; | 60 | render(): ReactElement | null { |
61 | const { classes, intl, theme } = this.props; | ||
65 | const { isSwitchingWorkspace, nextWorkspace } = workspaceStore; | 62 | const { isSwitchingWorkspace, nextWorkspace } = workspaceStore; |
66 | if (!isSwitchingWorkspace) return null; | 63 | |
64 | if (!isSwitchingWorkspace) { | ||
65 | return null; | ||
66 | } | ||
67 | |||
67 | const nextWorkspaceName = nextWorkspace | 68 | const nextWorkspaceName = nextWorkspace |
68 | ? nextWorkspace.name | 69 | ? nextWorkspace.name |
69 | : 'All services'; | 70 | : 'All services'; |
71 | |||
70 | return ( | 72 | return ( |
71 | <div className={classnames([classes.wrapper])}> | 73 | <div className={classnames([classes.wrapper])}> |
72 | <div className={classes.component}> | 74 | <div className={classes.component}> |
73 | <Loader | 75 | <Loader |
74 | className={classes.spinner} | 76 | className={classes.spinner} |
75 | color={theme.workspaces.switchingIndicator.spinnerColor} | 77 | color={theme?.workspaces.switchingIndicator.spinnerColor} |
76 | /> | 78 | /> |
77 | <p className={classes.message}> | 79 | <p className={classes.message}> |
78 | {`${intl.formatMessage(messages.switchingTo)} ${nextWorkspaceName}`} | 80 | {`${intl.formatMessage(messages.switchingTo)} ${nextWorkspaceName}`} |
@@ -84,7 +86,5 @@ class WorkspaceSwitchingIndicator extends Component { | |||
84 | } | 86 | } |
85 | 87 | ||
86 | export default injectIntl( | 88 | export default injectIntl( |
87 | injectSheet(styles, { injectTheme: true })( | 89 | withStyles(styles, { injectTheme: true })(WorkspaceSwitchingIndicator), |
88 | observer(WorkspaceSwitchingIndicator), | ||
89 | ), | ||
90 | ); | 90 | ); |
diff --git a/src/helpers/url-helpers.ts b/src/helpers/url-helpers.ts index 69a2cc4dc..9c5cf7752 100644 --- a/src/helpers/url-helpers.ts +++ b/src/helpers/url-helpers.ts | |||
@@ -1,14 +1,12 @@ | |||
1 | // This is taken from: https://benjamin-altpeter.de/shell-openexternal-dangers/ | 1 | // This is taken from: https://benjamin-altpeter.de/shell-openexternal-dangers/ |
2 | |||
3 | import { URL } from 'url'; | 2 | import { URL } from 'url'; |
4 | import { ensureDirSync, existsSync } from 'fs-extra'; | 3 | import { ensureDirSync, existsSync } from 'fs-extra'; |
5 | import { shell } from 'electron'; | 4 | import { shell } from 'electron'; |
6 | |||
7 | import { ALLOWED_PROTOCOLS } from '../config'; | 5 | import { ALLOWED_PROTOCOLS } from '../config'; |
8 | 6 | ||
9 | const debug = require('../preload-safe-debug')('Ferdium:Helpers:url'); | 7 | const debug = require('../preload-safe-debug')('Ferdium:Helpers:url'); |
10 | 8 | ||
11 | export function isValidExternalURL(url: string | URL) { | 9 | export function isValidExternalURL(url: string | URL): boolean { |
12 | let parsedUrl: URL; | 10 | let parsedUrl: URL; |
13 | try { | 11 | try { |
14 | parsedUrl = new URL(url.toString()); | 12 | parsedUrl = new URL(url.toString()); |
@@ -17,13 +15,12 @@ export function isValidExternalURL(url: string | URL) { | |||
17 | } | 15 | } |
18 | 16 | ||
19 | const isAllowed = ALLOWED_PROTOCOLS.includes(parsedUrl.protocol); | 17 | const isAllowed = ALLOWED_PROTOCOLS.includes(parsedUrl.protocol); |
20 | |||
21 | debug('protocol check is', isAllowed, 'for:', url); | 18 | debug('protocol check is', isAllowed, 'for:', url); |
22 | 19 | ||
23 | return isAllowed; | 20 | return isAllowed; |
24 | } | 21 | } |
25 | 22 | ||
26 | export function fixUrl(url: string | URL) { | 23 | export function fixUrl(url: string | URL): string { |
27 | return url | 24 | return url |
28 | .toString() | 25 | .toString() |
29 | .replaceAll('//', '/') | 26 | .replaceAll('//', '/') |
@@ -32,11 +29,11 @@ export function fixUrl(url: string | URL) { | |||
32 | .replaceAll('file:/', 'file://'); | 29 | .replaceAll('file:/', 'file://'); |
33 | } | 30 | } |
34 | 31 | ||
35 | export function isValidFileUrl(path: string) { | 32 | export function isValidFileUrl(path: string): boolean { |
36 | return path.startsWith('file') && existsSync(new URL(path)); | 33 | return path.startsWith('file') && existsSync(new URL(path)); |
37 | } | 34 | } |
38 | 35 | ||
39 | export async function openPath(folderName: string) { | 36 | export async function openPath(folderName: string): Promise<void> { |
40 | ensureDirSync(folderName); | 37 | ensureDirSync(folderName); |
41 | shell.openPath(folderName); | 38 | shell.openPath(folderName); |
42 | } | 39 | } |
@@ -45,7 +42,7 @@ export async function openPath(folderName: string) { | |||
45 | export function openExternalUrl( | 42 | export function openExternalUrl( |
46 | url: string | URL, | 43 | url: string | URL, |
47 | skipValidityCheck: boolean = false, | 44 | skipValidityCheck: boolean = false, |
48 | ) { | 45 | ): void { |
49 | const fixedUrl = fixUrl(url.toString()); | 46 | const fixedUrl = fixUrl(url.toString()); |
50 | debug('Open url:', fixedUrl, 'with skipValidityCheck:', skipValidityCheck); | 47 | debug('Open url:', fixedUrl, 'with skipValidityCheck:', skipValidityCheck); |
51 | if (skipValidityCheck || isValidExternalURL(fixedUrl)) { | 48 | if (skipValidityCheck || isValidExternalURL(fixedUrl)) { |
diff --git a/src/stores/FeaturesStore.ts b/src/stores/FeaturesStore.ts index 5f43ccf84..8584b6060 100644 --- a/src/stores/FeaturesStore.ts +++ b/src/stores/FeaturesStore.ts | |||
@@ -5,7 +5,6 @@ import { | |||
5 | observable, | 5 | observable, |
6 | runInAction, | 6 | runInAction, |
7 | } from 'mobx'; | 7 | } from 'mobx'; |
8 | |||
9 | import { Stores } from '../@types/stores.types'; | 8 | import { Stores } from '../@types/stores.types'; |
10 | import { ApiInterface } from '../api'; | 9 | import { ApiInterface } from '../api'; |
11 | import { Actions } from '../actions/lib/actions'; | 10 | import { Actions } from '../actions/lib/actions'; |
@@ -21,6 +20,14 @@ import appearance from '../features/appearance'; | |||
21 | import TypedStore from './lib/TypedStore'; | 20 | import TypedStore from './lib/TypedStore'; |
22 | 21 | ||
23 | export default class FeaturesStore extends TypedStore { | 22 | export default class FeaturesStore extends TypedStore { |
23 | @observable features = {}; | ||
24 | |||
25 | constructor(stores: Stores, api: ApiInterface, actions: Actions) { | ||
26 | super(stores, api, actions); | ||
27 | |||
28 | makeObservable(this); | ||
29 | } | ||
30 | |||
24 | @observable defaultFeaturesRequest = new CachedRequest( | 31 | @observable defaultFeaturesRequest = new CachedRequest( |
25 | this.api.features, | 32 | this.api.features, |
26 | 'default', | 33 | 'default', |
@@ -31,14 +38,6 @@ export default class FeaturesStore extends TypedStore { | |||
31 | 'features', | 38 | 'features', |
32 | ); | 39 | ); |
33 | 40 | ||
34 | @observable features = {}; | ||
35 | |||
36 | constructor(stores: Stores, api: ApiInterface, actions: Actions) { | ||
37 | super(stores, api, actions); | ||
38 | |||
39 | makeObservable(this); | ||
40 | } | ||
41 | |||
42 | async setup(): Promise<void> { | 41 | async setup(): Promise<void> { |
43 | this.registerReactions([ | 42 | this.registerReactions([ |
44 | this._updateFeatures, | 43 | this._updateFeatures, |