Add error boundary component to catch Web UI crashes
parent
39c8a71df8
commit
922d05864f
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1263,3 +1263,4 @@ noscript {
|
|||
@import 'lists';
|
||||
@import 'emoji_picker';
|
||||
@import 'local_settings';
|
||||
@import 'error_boundary';
|
||||
|
|
Loading…
Reference in New Issue