Add error description and button to copy stack trace to web UI (#12033)
This commit is contained in:
parent
66fda37fd0
commit
cbaea097be
3 changed files with 108 additions and 8 deletions
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import illustration from '../../images/elephant_ui_disappointed.svg';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { version, source_url } from 'mastodon/initial_state';
|
||||||
|
|
||||||
export default class ErrorBoundary extends React.PureComponent {
|
export default class ErrorBoundary extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -12,26 +13,53 @@ export default class ErrorBoundary extends React.PureComponent {
|
||||||
hasError: false,
|
hasError: false,
|
||||||
stackTrace: undefined,
|
stackTrace: undefined,
|
||||||
componentStack: undefined,
|
componentStack: undefined,
|
||||||
}
|
};
|
||||||
|
|
||||||
componentDidCatch(error, info) {
|
componentDidCatch (error, info) {
|
||||||
this.setState({
|
this.setState({
|
||||||
hasError: true,
|
hasError: true,
|
||||||
stackTrace: error.stack,
|
stackTrace: error.stack,
|
||||||
componentStack: info && info.componentStack,
|
componentStack: info && info.componentStack,
|
||||||
|
copied: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCopyStackTrace = () => {
|
||||||
|
const { stackTrace } = this.state;
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
|
||||||
|
textarea.textContent = stackTrace;
|
||||||
|
textarea.style.position = 'fixed';
|
||||||
|
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
|
||||||
|
try {
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ copied: true });
|
||||||
|
setTimeout(() => this.setState({ copied: false }), 700);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { hasError } = this.state;
|
const { hasError, copied } = this.state;
|
||||||
|
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='error-boundary'>
|
||||||
<img src={illustration} alt='' />
|
<div>
|
||||||
|
<p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p>
|
||||||
|
<p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p>
|
||||||
|
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied && 'copied'}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,7 @@
|
||||||
"getting_started.heading": "Getting started",
|
"getting_started.heading": "Getting started",
|
||||||
"getting_started.invite": "Invite people",
|
"getting_started.invite": "Invite people",
|
||||||
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
|
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
|
||||||
"getting_started.security": "Security",
|
"getting_started.security": "Account settings",
|
||||||
"getting_started.terms": "Terms of service",
|
"getting_started.terms": "Terms of service",
|
||||||
"hashtag.column_header.tag_mode.all": "and {additional}",
|
"hashtag.column_header.tag_mode.all": "and {additional}",
|
||||||
"hashtag.column_header.tag_mode.any": "or {additional}",
|
"hashtag.column_header.tag_mode.any": "or {additional}",
|
||||||
|
|
|
@ -135,13 +135,18 @@ button {
|
||||||
|
|
||||||
.app-holder {
|
.app-holder {
|
||||||
&,
|
&,
|
||||||
& > div {
|
& > div,
|
||||||
|
& > noscript {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
outline: 0 !important;
|
outline: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > noscript {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-single-column .app-holder {
|
.layout-single-column .app-holder {
|
||||||
|
@ -157,3 +162,70 @@ button {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-boundary,
|
||||||
|
.app-holder noscript {
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.7;
|
||||||
|
color: lighten($error-red, 4%);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: .85em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $highlight-text-color;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
color: $dark-text-color;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $dark-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: inline;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: $dark-text-color;
|
||||||
|
font: inherit;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
|
transition: color 300ms linear;
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.copied {
|
||||||
|
color: $valid-value-color;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue