diff options
author | Thibaut Girka <thib@sitedethib.com> | 2018-11-28 15:01:40 +0100 |
---|---|---|
committer | ThibG <thib@sitedethib.com> | 2018-11-28 22:36:01 +0100 |
commit | 922d05864f4ba37a4026cd5f7e6e6ed5417a7404 (patch) | |
tree | a157d4c4e3188d3e7de34793263ce1dce8dedefa /app/javascript/flavours | |
parent | 39c8a71df80d303ee08d24b3ce3a66400a1dcc0b (diff) |
Add error boundary component to catch Web UI crashes
Diffstat (limited to 'app/javascript/flavours')
4 files changed, 133 insertions, 5 deletions
diff --git a/app/javascript/flavours/glitch/components/error_boundary.js b/app/javascript/flavours/glitch/components/error_boundary.js new file mode 100644 index 000000000..fd37383f2 --- /dev/null +++ b/app/javascript/flavours/glitch/components/error_boundary.js @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; + +export default class ErrorBoundary extends React.PureComponent { + + static propTypes = { + children: PropTypes.node, + }; + + state = { + hasError: false, + stackTrace: undefined, + componentStack: undefined, + } + + componentDidCatch(error, info) { + this.setState({ + hasError: true, + stackTrace: error.stack, + componentStack: info && info.componentStack, + }); + } + + handleReload(e) { + e.preventDefault(); + window.location.reload(); + } + + render() { + const { hasError, stackTrace, componentStack } = this.state; + + if (!hasError) return this.props.children; + + let debugInfo = ''; + if (stackTrace) { + debugInfo += 'Stack trace\n-----------\n\n```\n' + stackTrace.toString() + '\n```'; + } + if (componentStack) { + if (debugInfo) { + debugInfo += '\n\n\n'; + } + debugInfo += 'React component stack\n---------------------\n\n```\n' + componentStack.toString() + '\n```'; + } + + return ( + <div tabIndex='-1'> + <div className='error-boundary'> + <h1><FormattedMessage id='web_app_crash.title' defaultMessage="We're sorry, but something went wrong with the Mastodon app." /></h1> + <p> + <FormattedMessage id='web_app_crash.content' defaultMessage='You could try any of the following:' /> + <ul> + <li> + <FormattedMessage + id='web_app_crash.report_issue' + defaultMessage='Report a bug in the {issuetracker}' + values={{ issuetracker: <a href='https://github.com/glitch-soc/mastodon/issues' rel='noopener' target='_blank'><FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /></a> }} + /> + { debugInfo !== '' && ( + <details> + <summary><FormattedMessage id='web_app_crash.debug_info' defaultMessage='Debug information' /></summary> + <textarea + className='web_app_crash-stacktrace' + value={debugInfo} + rows='10' + readOnly + /> + </details> + )} + </li> + <li> + <FormattedMessage + id='web_app_crash.reload_page' + defaultMessage='{reload} the current page' + values={{ reload: <a href='#' onClick={this.handleReload}><FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /></a> }} + /> + </li> + <li> + <FormattedMessage + id='web_app_crash.change_your_settings' + defaultMessage='Change your {settings}' + values={{ settings: <a href='/settings/preferences'><FormattedMessage id='web_app_crash.settings' defaultMessage='settings' /></a> }} + /> + </li> + </ul> + </p> + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js index 4bd9cb75e..4fb6be476 100644 --- a/app/javascript/flavours/glitch/containers/mastodon.js +++ b/app/javascript/flavours/glitch/containers/mastodon.js @@ -12,6 +12,7 @@ import { connectUserStream } from 'flavours/glitch/actions/streaming'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from 'locales'; import initialState from 'flavours/glitch/util/initial_state'; +import ErrorBoundary from 'flavours/glitch/components/error_boundary'; const { localeData, messages } = getLocale(); addLocaleData(localeData); @@ -61,11 +62,13 @@ export default class Mastodon extends React.PureComponent { return ( <IntlProvider locale={locale} messages={messages}> <Provider store={store}> - <BrowserRouter basename='/web'> - <ScrollContext> - <Route path='/' component={UI} /> - </ScrollContext> - </BrowserRouter> + <ErrorBoundary> + <BrowserRouter basename='/web'> + <ScrollContext> + <Route path='/' component={UI} /> + </ScrollContext> + </BrowserRouter> + </ErrorBoundary> </Provider> </IntlProvider> ); diff --git a/app/javascript/flavours/glitch/styles/components/error_boundary.scss b/app/javascript/flavours/glitch/styles/components/error_boundary.scss new file mode 100644 index 000000000..f9bf425f8 --- /dev/null +++ b/app/javascript/flavours/glitch/styles/components/error_boundary.scss @@ -0,0 +1,32 @@ +.error-boundary { + h1 { + font-size: 26px; + line-height: 36px; + font-weight: 400; + margin-bottom: 8px; + } + + p { + color: $primary-text-color; + font-size: 15px; + line-height: 20px; + + a { + color: $primary-text-color; + text-decoration: underline; + } + + ul { + list-style: disc; + margin-left: 0; + padding-left: 1em; + } + + textarea.web_app_crash-stacktrace { + width: 100%; + resize: none; + white-space: pre; + font-family: $font-monospace, monospace; + } + } +} diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 3e3ef6b52..873dfa98d 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -1263,3 +1263,4 @@ noscript { @import 'lists'; @import 'emoji_picker'; @import 'local_settings'; +@import 'error_boundary'; |