From a051331680b21f20201a47601d69505a4cfa9e40 Mon Sep 17 00:00:00 2001 From: muhamedsalih-tw <104364298+muhamedsalih-tw@users.noreply.github.com> Date: Sat, 19 Nov 2022 15:21:09 +0530 Subject: Transform service components to ts (#778) --- .../services/content/ConnectionLostBanner.js | 104 ----------- .../services/content/ConnectionLostBanner.tsx | 99 +++++++++++ .../content/ErrorHandlers/WebviewErrorHandler.js | 75 -------- .../services/content/ErrorHandlers/styles.ts | 25 --- src/components/services/content/ServiceDisabled.js | 43 ----- .../services/content/ServiceDisabled.tsx | 41 +++++ src/components/services/content/ServiceView.js | 190 -------------------- src/components/services/content/ServiceView.tsx | 192 +++++++++++++++++++++ src/components/services/content/Services.tsx | 22 +-- .../services/content/WebviewCrashHandler.js | 84 --------- .../services/content/WebviewCrashHandler.tsx | 90 ++++++++++ .../services/content/WebviewErrorHandler.tsx | 96 +++++++++++ 12 files changed, 529 insertions(+), 532 deletions(-) delete mode 100644 src/components/services/content/ConnectionLostBanner.js create mode 100644 src/components/services/content/ConnectionLostBanner.tsx delete mode 100644 src/components/services/content/ErrorHandlers/WebviewErrorHandler.js delete mode 100644 src/components/services/content/ErrorHandlers/styles.ts delete mode 100644 src/components/services/content/ServiceDisabled.js create mode 100644 src/components/services/content/ServiceDisabled.tsx delete mode 100644 src/components/services/content/ServiceView.js create mode 100644 src/components/services/content/ServiceView.tsx delete mode 100644 src/components/services/content/WebviewCrashHandler.js create mode 100644 src/components/services/content/WebviewCrashHandler.tsx create mode 100644 src/components/services/content/WebviewErrorHandler.tsx (limited to 'src/components/services/content') diff --git a/src/components/services/content/ConnectionLostBanner.js b/src/components/services/content/ConnectionLostBanner.js deleted file mode 100644 index f2f70ca2e..000000000 --- a/src/components/services/content/ConnectionLostBanner.js +++ /dev/null @@ -1,104 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import injectSheet from 'react-jss'; -import { defineMessages, injectIntl } from 'react-intl'; - -import { mdiAlert } from '@mdi/js'; -import { LIVE_API_FERDIUM_WEBSITE } from '../../../config'; -import Icon from '../../ui/icon'; - -const messages = defineMessages({ - text: { - id: 'connectionLostBanner.message', - defaultMessage: 'Oh no! Ferdium lost the connection to {name}.', - }, - moreInformation: { - id: 'connectionLostBanner.informationLink', - defaultMessage: 'What happened?', - }, - cta: { - id: 'connectionLostBanner.cta', - defaultMessage: 'Reload Service', - }, -}); - -let buttonTransition = 'none'; - -if (window && window.matchMedia('(prefers-reduced-motion: no-preference)')) { - buttonTransition = 'opacity 0.25s'; -} - -const styles = theme => ({ - root: { - background: theme.colorBackground, - borderRadius: theme.borderRadius, - position: 'absolute', - zIndex: 300, - height: 50, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - bottom: 10, - right: 10, - justifyContent: 'center', - padding: 10, - fontSize: 12, - }, - link: { - display: 'inline-flex', - opacity: 0.7, - }, - button: { - transition: buttonTransition, - color: theme.colorText, - border: [1, 'solid', theme.colorText], - borderRadius: theme.borderRadiusSmall, - padding: 4, - fontSize: 12, - marginLeft: 15, - - '&:hover': { - opacity: 0.8, - }, - }, - icon: { - marginRight: 10, - fill: theme.styleTypes.danger.accent, - }, -}); - -class ConnectionLostBanner extends Component { - static propTypes = { - classes: PropTypes.object.isRequired, - name: PropTypes.string.isRequired, - reload: PropTypes.func.isRequired, - }; - - render() { - const { classes, name, reload } = this.props; - - const { intl } = this.props; - - return ( -
- -

- {intl.formatMessage(messages.text, { name })} -
- - {intl.formatMessage(messages.moreInformation)} - -

- -
- ); - } -} - -export default injectIntl(injectSheet(styles)(observer(ConnectionLostBanner))); diff --git a/src/components/services/content/ConnectionLostBanner.tsx b/src/components/services/content/ConnectionLostBanner.tsx new file mode 100644 index 000000000..88731f3b9 --- /dev/null +++ b/src/components/services/content/ConnectionLostBanner.tsx @@ -0,0 +1,99 @@ +import { Component, ReactElement } from 'react'; +import { observer } from 'mobx-react'; +import withStyles, { WithStylesProps } from 'react-jss'; +import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import { mdiAlert } from '@mdi/js'; +import { LIVE_API_FERDIUM_WEBSITE } from '../../../config'; +import Icon from '../../ui/icon'; + +const messages = defineMessages({ + text: { + id: 'connectionLostBanner.message', + defaultMessage: 'Oh no! Ferdium lost the connection to {name}.', + }, + moreInformation: { + id: 'connectionLostBanner.informationLink', + defaultMessage: 'What happened?', + }, + cta: { + id: 'connectionLostBanner.cta', + defaultMessage: 'Reload Service', + }, +}); + +const buttonTransition = + window && window.matchMedia('(prefers-reduced-motion: no-preference)') + ? 'opacity 0.25s' + : 'none'; + +const styles = theme => ({ + root: { + background: theme.colorBackground, + borderRadius: theme.borderRadius, + position: 'absolute', + zIndex: 300, + height: 50, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + bottom: 10, + right: 10, + justifyContent: 'center', + padding: 10, + fontSize: 12, + }, + link: { + display: 'inline-flex', + opacity: 0.7, + }, + button: { + transition: buttonTransition, + color: theme.colorText, + border: [1, 'solid', theme.colorText], + borderRadius: theme.borderRadiusSmall, + padding: 4, + fontSize: 12, + marginLeft: 15, + + '&:hover': { + opacity: 0.8, + }, + }, + icon: { + marginRight: 10, + fill: theme.styleTypes.danger.accent, + }, +}); + +interface IProps extends WithStylesProps, WrappedComponentProps { + name: string; + reload: () => void; +} + +@observer +class ConnectionLostBanner extends Component { + render(): ReactElement { + const { classes, name, reload, intl } = this.props; + + return ( +
+ +

+ {intl.formatMessage(messages.text, { name })} +
+ + {intl.formatMessage(messages.moreInformation)} + +

+ +
+ ); + } +} + +export default injectIntl(withStyles(styles)(ConnectionLostBanner)); diff --git a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js deleted file mode 100644 index a658bec8b..000000000 --- a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js +++ /dev/null @@ -1,75 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import { defineMessages, injectIntl } from 'react-intl'; -import injectSheet from 'react-jss'; - -import Button from '../../../ui/button'; - -import styles from './styles'; -import { H1 } from '../../../ui/headline'; - -const messages = defineMessages({ - headline: { - id: 'service.errorHandler.headline', - defaultMessage: 'Oh no!', - }, - text: { - id: 'service.errorHandler.text', - defaultMessage: '{name} has failed to load.', - }, - action: { - id: 'service.errorHandler.action', - defaultMessage: 'Reload {name}', - }, - editAction: { - id: 'service.errorHandler.editAction', - defaultMessage: 'Edit {name}', - }, - errorMessage: { - id: 'service.errorHandler.message', - defaultMessage: 'Error', - }, -}); - -class WebviewErrorHandler extends Component { - static propTypes = { - name: PropTypes.string.isRequired, - reload: PropTypes.func.isRequired, - edit: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, - classes: PropTypes.object.isRequired, - }; - - render() { - const { name, reload, edit, errorMessage, classes } = this.props; - const { intl } = this.props; - - return ( -
-

{intl.formatMessage(messages.headline)}

-

{intl.formatMessage(messages.text, { name })}

-

- {intl.formatMessage(messages.errorMessage)}:{' '} - {errorMessage} -

-
-
-
- ); - } -} - -export default injectIntl( - injectSheet(styles, { injectTheme: true })(observer(WebviewErrorHandler)), -); diff --git a/src/components/services/content/ErrorHandlers/styles.ts b/src/components/services/content/ErrorHandlers/styles.ts deleted file mode 100644 index 9e2509ee5..000000000 --- a/src/components/services/content/ErrorHandlers/styles.ts +++ /dev/null @@ -1,25 +0,0 @@ -export default theme => ({ - component: { - left: 0, - position: 'absolute', - top: 0, - width: '100%', - zIndex: 0, - alignItems: 'center', - background: theme.colorWebviewErrorHandlerBackground, - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - textAlign: 'center', - }, - buttonContainer: { - display: 'flex', - flexDirection: 'row', - height: 'auto', - margin: [40, 0, 20], - - '& button': { - margin: [0, 10, 0, 10], - }, - }, -}); diff --git a/src/components/services/content/ServiceDisabled.js b/src/components/services/content/ServiceDisabled.js deleted file mode 100644 index d874a354e..000000000 --- a/src/components/services/content/ServiceDisabled.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import { defineMessages, injectIntl } from 'react-intl'; - -import Button from '../../ui/button'; -import { H1 } from '../../ui/headline'; - -const messages = defineMessages({ - headline: { - id: 'service.disabledHandler.headline', - defaultMessage: '{name} is disabled', - }, - action: { - id: 'service.disabledHandler.action', - defaultMessage: 'Enable {name}', - }, -}); - -class ServiceDisabled extends Component { - static propTypes = { - name: PropTypes.string.isRequired, - enable: PropTypes.func.isRequired, - }; - - render() { - const { name, enable } = this.props; - const { intl } = this.props; - - return ( -
-

{intl.formatMessage(messages.headline, { name })}

-
- ); - } -} - -export default injectIntl(observer(ServiceDisabled)); diff --git a/src/components/services/content/ServiceDisabled.tsx b/src/components/services/content/ServiceDisabled.tsx new file mode 100644 index 000000000..2f0d439ec --- /dev/null +++ b/src/components/services/content/ServiceDisabled.tsx @@ -0,0 +1,41 @@ +import { Component, ReactElement } from 'react'; +import { observer } from 'mobx-react'; +import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import Button from '../../ui/button'; +import { H1 } from '../../ui/headline'; + +const messages = defineMessages({ + headline: { + id: 'service.disabledHandler.headline', + defaultMessage: '{name} is disabled', + }, + action: { + id: 'service.disabledHandler.action', + defaultMessage: 'Enable {name}', + }, +}); + +interface IProps extends WrappedComponentProps { + name: string; + enable: () => void; +} + +@observer +class ServiceDisabled extends Component { + render(): ReactElement { + const { name, enable, intl } = this.props; + + return ( +
+

{intl.formatMessage(messages.headline, { name })}

+
+ ); + } +} + +export default injectIntl(ServiceDisabled); diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js deleted file mode 100644 index cae16ef49..000000000 --- a/src/components/services/content/ServiceView.js +++ /dev/null @@ -1,190 +0,0 @@ -/* eslint-disable react/jsx-no-useless-fragment */ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { autorun } from 'mobx'; -import { observer, inject } from 'mobx-react'; -import classnames from 'classnames'; -import TopBarProgress from 'react-topbar-progress-indicator'; - -import ServiceModel from '../../../models/Service'; -import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; -import WebviewLoader from '../../ui/WebviewLoader'; -import WebviewCrashHandler from './WebviewCrashHandler'; -import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; -import ServiceDisabled from './ServiceDisabled'; -import ServiceWebview from './ServiceWebview'; -import SettingsStore from '../../../stores/SettingsStore'; -import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; -import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config'; - -class ServiceView extends Component { - static propTypes = { - service: PropTypes.instanceOf(ServiceModel).isRequired, - setWebviewReference: PropTypes.func.isRequired, - detachService: PropTypes.func.isRequired, - reload: PropTypes.func.isRequired, - edit: PropTypes.func.isRequired, - enable: PropTypes.func.isRequired, - isActive: PropTypes.bool, - stores: PropTypes.shape({ - settings: PropTypes.instanceOf(SettingsStore).isRequired, - }).isRequired, - isSpellcheckerEnabled: PropTypes.bool.isRequired, - }; - - static defaultProps = { - isActive: false, - }; - - state = { - forceRepaint: false, - targetUrl: '', - statusBarVisible: false, - }; - - hibernationTimer = null; - - autorunDisposer = null; - - forceRepaintTimeout = null; - - componentDidMount() { - this.autorunDisposer = autorun(() => { - if (this.props.service.isActive) { - this.setState({ forceRepaint: true }); - this.forceRepaintTimeout = setTimeout(() => { - this.setState({ forceRepaint: false }); - }, 100); - } - }); - } - - componentWillUnmount() { - this.autorunDisposer(); - clearTimeout(this.forceRepaintTimeout); - clearTimeout(this.hibernationTimer); - } - - render() { - const { - detachService, - service, - setWebviewReference, - reload, - edit, - enable, - stores, - isSpellcheckerEnabled, - } = this.props; - - const { navigationBarBehaviour, navigationBarManualActive } = - stores.settings.app; - - const showNavBar = - navigationBarBehaviour === 'always' || - (navigationBarBehaviour === 'custom' && - service.recipe.id === CUSTOM_WEBSITE_RECIPE_ID) || - navigationBarManualActive; - - const webviewClasses = classnames({ - services__webview: true, - 'services__webview-wrapper': true, - 'is-active': service.isActive, - 'services__webview--force-repaint': this.state.forceRepaint, - }); - - let statusBar = null; - if (this.state.statusBarVisible) { - statusBar = ; - } - - return ( -
- {service.isActive && service.isEnabled && ( - <> - {service.hasCrashed && ( - - )} - {service.isEnabled && - service.isLoading && - service.isFirstLoad && - !service.isHibernating && - !service.isServiceAccessRestricted && ( - - )} - {service.isProgressbarEnabled && - service.isLoadingPage && - !service.isFirstLoad && } - {service.isError && ( - - )} - - )} - {!service.isEnabled ? ( - <> - {service.isActive && ( - - )} - - ) : ( - <> - {!service.isHibernating ? ( - <> - {showNavBar && } - - - ) : ( -
- - 😴 - -
-
- This service is currently hibernating. -
- Try switching services or reloading Ferdium. -
- )} - - )} - {statusBar} -
- ); - } -} - -export default inject('stores', 'actions')(observer(ServiceView)); diff --git a/src/components/services/content/ServiceView.tsx b/src/components/services/content/ServiceView.tsx new file mode 100644 index 000000000..e41184431 --- /dev/null +++ b/src/components/services/content/ServiceView.tsx @@ -0,0 +1,192 @@ +import { Component } from 'react'; +import { autorun, IReactionDisposer } from 'mobx'; +import { observer, inject } from 'mobx-react'; +import classnames from 'classnames'; +import TopBarProgress from 'react-topbar-progress-indicator'; +import ServiceModel from '../../../models/Service'; +import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl'; +import WebviewLoader from '../../ui/WebviewLoader'; +import WebviewCrashHandler from './WebviewCrashHandler'; +import WebviewErrorHandler from './WebviewErrorHandler'; +import ServiceDisabled from './ServiceDisabled'; +import ServiceWebview from './ServiceWebview'; +import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; +import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config'; +import { RealStores } from '../../../stores'; + +interface IProps { + service: ServiceModel; + setWebviewRef: () => void; + detachService: () => void; + reload: () => void; + edit: () => void; + enable: () => void; + // isActive?: boolean; // TODO - [TECH DEBT][PROP NOT USED IN COMPONENT] check it + stores?: RealStores; + isSpellcheckerEnabled: boolean; +} + +interface IState { + forceRepaint: boolean; + targetUrl: string; + statusBarVisible: boolean; +} + +@inject('stores', 'actions') +@observer +class ServiceView extends Component { + // hibernationTimer = null; // TODO - [TS DEBT] class property not reassigned, need to find its purpose + + autorunDisposer: IReactionDisposer | undefined; + + forceRepaintTimeout: NodeJS.Timeout | undefined; + + constructor(props: IProps) { + super(props); + + this.state = { + forceRepaint: false, + targetUrl: '', + statusBarVisible: false, + }; + } + + componentDidMount() { + this.autorunDisposer = autorun(() => { + if (this.props.service.isActive) { + this.setState({ forceRepaint: true }); + this.forceRepaintTimeout = setTimeout(() => { + this.setState({ forceRepaint: false }); + }, 100); + } + }); + } + + componentWillUnmount() { + this.autorunDisposer!(); + clearTimeout(this.forceRepaintTimeout!); + // clearTimeout(this.hibernationTimer); // TODO - [TS DEBT] class property not reassigned, need to find its purpose + } + + render() { + const { + detachService, + service, + setWebviewRef, + reload, + edit, + enable, + stores, + isSpellcheckerEnabled, + } = this.props; + + const { navigationBarBehaviour, navigationBarManualActive } = + stores!.settings.app; + + const showNavBar = + navigationBarBehaviour === 'always' || + (navigationBarBehaviour === 'custom' && + service.recipe.id === CUSTOM_WEBSITE_RECIPE_ID) || + navigationBarManualActive; + + const webviewClasses = classnames({ + services__webview: true, + 'services__webview-wrapper': true, + 'is-active': service.isActive, + 'services__webview--force-repaint': this.state.forceRepaint, + }); + + const statusBar = this.state.statusBarVisible ? ( + + ) : null; + + return ( +
+ {service.isActive && service.isEnabled && ( + <> + {service.hasCrashed && ( + + )} + {service.isEnabled && + service.isLoading && + service.isFirstLoad && + !service.isHibernating && + !service.isServiceAccessRestricted && ( + + )} + {service.isProgressbarEnabled && + service.isLoadingPage && + !service.isFirstLoad && } + {service.isError && ( + + )} + + )} + {!service.isEnabled ? ( + <> + {service.isActive && ( + + )} + + ) : ( + <> + {!service.isHibernating ? ( + <> + {showNavBar && } + + + ) : ( +
+ + 😴 + +
+
+ This service is currently hibernating. +
+ Try switching services or reloading Ferdium. +
+ )} + + )} + {statusBar} +
+ ); + } +} + +export default ServiceView; diff --git a/src/components/services/content/Services.tsx b/src/components/services/content/Services.tsx index 53cddd907..fa26edaa6 100644 --- a/src/components/services/content/Services.tsx +++ b/src/components/services/content/Services.tsx @@ -42,8 +42,8 @@ interface IProps extends WrappedComponentProps, WithStylesProps { services?: Service[]; setWebviewReference: () => void; detachService: () => void; - handleIPCMessage: () => void; - openWindow: () => void; + // handleIPCMessage: () => void; // TODO - [TECH DEBT] later check it + // openWindow: () => void; // TODO - [TECH DEBT] later check it reload: (options: { serviceId: string }) => void; openSettings: (options: { path: string }) => void; update: (options: { @@ -61,7 +61,7 @@ interface IState { @observer class Services extends Component { - _confettiTimeout: number | null = null; + confettiTimeout: number | null = null; constructor(props: IProps) { super(props); @@ -72,7 +72,7 @@ class Services extends Component { } componentDidMount(): void { - this._confettiTimeout = window.setTimeout(() => { + this.confettiTimeout = window.setTimeout(() => { this.setState({ showConfetti: false, }); @@ -80,18 +80,18 @@ class Services extends Component { } componentWillUnmount(): void { - if (this._confettiTimeout) { - clearTimeout(this._confettiTimeout); + if (this.confettiTimeout) { + clearTimeout(this.confettiTimeout); } } render(): ReactElement { const { services = [], - handleIPCMessage, + // handleIPCMessage, // TODO - [TECH DEBT] later check it setWebviewReference, detachService, - openWindow, + // openWindow, // TODO - [TECH DEBT] later check it reload, openSettings, update, @@ -136,10 +136,10 @@ class Services extends Component { reload({ serviceId: service.id })} edit={() => openSettings({ path: `services/edit/${service.id}` })} enable={() => diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js deleted file mode 100644 index 0e6e61be8..000000000 --- a/src/components/services/content/WebviewCrashHandler.js +++ /dev/null @@ -1,84 +0,0 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; -import { observer } from 'mobx-react'; -import { defineMessages, injectIntl } from 'react-intl'; -import ms from 'ms'; - -import Button from '../../ui/button'; -import { H1 } from '../../ui/headline'; - -const messages = defineMessages({ - headline: { - id: 'service.crashHandler.headline', - defaultMessage: 'Oh no!', - }, - text: { - id: 'service.crashHandler.text', - defaultMessage: '{name} has caused an error.', - }, - action: { - id: 'service.crashHandler.action', - defaultMessage: 'Reload {name}', - }, - autoReload: { - id: 'service.crashHandler.autoReload', - defaultMessage: - 'Trying to automatically restore {name} in {seconds} seconds', - }, -}); - -class WebviewCrashHandler extends Component { - static propTypes = { - name: PropTypes.string.isRequired, - reload: PropTypes.func.isRequired, - }; - - state = { - countdown: ms('10s'), - }; - - countdownInterval = null; - - countdownIntervalTimeout = ms('1s'); - - componentDidMount() { - const { reload } = this.props; - - this.countdownInterval = setInterval(() => { - this.setState(prevState => ({ - countdown: prevState.countdown - this.countdownIntervalTimeout, - })); - - if (this.state.countdown <= 0) { - reload(); - clearInterval(this.countdownInterval); - } - }, this.countdownIntervalTimeout); - } - - render() { - const { name, reload } = this.props; - const { intl } = this.props; - - return ( -
-

{intl.formatMessage(messages.headline)}

-

{intl.formatMessage(messages.text, { name })}

-
- ); - } -} - -export default injectIntl(observer(WebviewCrashHandler)); diff --git a/src/components/services/content/WebviewCrashHandler.tsx b/src/components/services/content/WebviewCrashHandler.tsx new file mode 100644 index 000000000..e9b17e8aa --- /dev/null +++ b/src/components/services/content/WebviewCrashHandler.tsx @@ -0,0 +1,90 @@ +import { Component, ReactElement } from 'react'; +import { observer } from 'mobx-react'; +import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import ms from 'ms'; +import Button from '../../ui/button'; +import { H1 } from '../../ui/headline'; + +const messages = defineMessages({ + headline: { + id: 'service.crashHandler.headline', + defaultMessage: 'Oh no!', + }, + text: { + id: 'service.crashHandler.text', + defaultMessage: '{name} has caused an error.', + }, + action: { + id: 'service.crashHandler.action', + defaultMessage: 'Reload {name}', + }, + autoReload: { + id: 'service.crashHandler.autoReload', + defaultMessage: + 'Trying to automatically restore {name} in {seconds} seconds', + }, +}); + +interface IProps extends WrappedComponentProps { + name: string; + reload: () => void; +} + +interface IState { + countdown: number; +} + +@observer +class WebviewCrashHandler extends Component { + countdownInterval: NodeJS.Timer | undefined; + + countdownIntervalTimeout = ms('1s'); + + constructor(props: IProps) { + super(props); + + this.state = { + countdown: ms('10s'), + }; + } + + componentDidMount(): void { + const { reload } = this.props; + + this.countdownInterval = setInterval(() => { + this.setState(prevState => ({ + countdown: prevState.countdown - this.countdownIntervalTimeout, + })); + + if (this.state.countdown <= 0) { + reload(); + clearInterval(this.countdownInterval!); + } + }, this.countdownIntervalTimeout); + } + + render(): ReactElement { + const { name, reload, intl } = this.props; + + return ( +
+

{intl.formatMessage(messages.headline)}

+

{intl.formatMessage(messages.text, { name })}

+
+ ); + } +} + +export default injectIntl(WebviewCrashHandler); diff --git a/src/components/services/content/WebviewErrorHandler.tsx b/src/components/services/content/WebviewErrorHandler.tsx new file mode 100644 index 000000000..b99c15006 --- /dev/null +++ b/src/components/services/content/WebviewErrorHandler.tsx @@ -0,0 +1,96 @@ +import { Component, ReactElement } from 'react'; +import { observer } from 'mobx-react'; +import { defineMessages, injectIntl, WrappedComponentProps } from 'react-intl'; +import withStyles, { WithStylesProps } from 'react-jss'; +import Button from '../../ui/button'; +import { H1 } from '../../ui/headline'; + +const messages = defineMessages({ + headline: { + id: 'service.errorHandler.headline', + defaultMessage: 'Oh no!', + }, + text: { + id: 'service.errorHandler.text', + defaultMessage: '{name} has failed to load.', + }, + action: { + id: 'service.errorHandler.action', + defaultMessage: 'Reload {name}', + }, + editAction: { + id: 'service.errorHandler.editAction', + defaultMessage: 'Edit {name}', + }, + errorMessage: { + id: 'service.errorHandler.message', + defaultMessage: 'Error', + }, +}); + +const styles = theme => ({ + component: { + left: 0, + position: 'absolute', + top: 0, + width: '100%', + zIndex: 0, + alignItems: 'center', + background: theme.colorWebviewErrorHandlerBackground, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + textAlign: 'center', + }, + buttonContainer: { + display: 'flex', + flexDirection: 'row', + height: 'auto', + margin: [40, 0, 20], + + '& button': { + margin: [0, 10, 0, 10], + }, + }, +}); + +interface IProps extends WithStylesProps, WrappedComponentProps { + name: string; + reload: () => void; + edit: () => void; + errorMessage: string; +} + +@observer +class WebviewErrorHandler extends Component { + render(): ReactElement { + const { name, reload, edit, errorMessage, classes, intl } = this.props; + + return ( +
+

{intl.formatMessage(messages.headline)}

+

{intl.formatMessage(messages.text, { name })}

+

+ {intl.formatMessage(messages.errorMessage)}:{' '} + {errorMessage} +

+
+
+
+ ); + } +} + +export default injectIntl( + withStyles(styles, { injectTheme: true })(WebviewErrorHandler), +); -- cgit v1.2.3-70-g09d2