about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-10-01 20:48:49 +0200
committerGitHub <noreply@github.com>2019-10-01 20:48:49 +0200
commitcbaea097be807f8a6a28dbddb85c657ec3e2848c (patch)
tree5e01804f007e7b19fe60dbefc99a18ce4524234b /app
parent66fda37fd04de989d12f3f4c565ba5bfc6ee189d (diff)
Add error description and button to copy stack trace to web UI (#12033)
Diffstat (limited to 'app')
-rw-r--r--app/javascript/mastodon/components/error_boundary.js40
-rw-r--r--app/javascript/mastodon/locales/en.json2
-rw-r--r--app/javascript/styles/mastodon/basics.scss74
3 files changed, 108 insertions, 8 deletions
diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js
index d1ca5bf75..82543e118 100644
--- a/app/javascript/mastodon/components/error_boundary.js
+++ b/app/javascript/mastodon/components/error_boundary.js
@@ -1,6 +1,7 @@
 import React from 'react';
 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 {
 
@@ -12,26 +13,53 @@ export default class ErrorBoundary extends React.PureComponent {
     hasError: false,
     stackTrace: undefined,
     componentStack: undefined,
-  }
+  };
 
-  componentDidCatch(error, info) {
+  componentDidCatch (error, info) {
     this.setState({
       hasError: true,
       stackTrace: error.stack,
       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() {
-    const { hasError } = this.state;
+    const { hasError, copied } = this.state;
 
     if (!hasError) {
       return this.props.children;
     }
 
     return (
-      <div>
-        <img src={illustration} alt='' />
+      <div className='error-boundary'>
+        <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>
     );
   }
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index fc769a18f..6ed6b8bcb 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -160,7 +160,7 @@
   "getting_started.heading": "Getting started",
   "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.security": "Security",
+  "getting_started.security": "Account settings",
   "getting_started.terms": "Terms of service",
   "hashtag.column_header.tag_mode.all": "and {additional}",
   "hashtag.column_header.tag_mode.any": "or {additional}",
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index 1f3ef7da2..2b10b5ad3 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -135,13 +135,18 @@ button {
 
 .app-holder {
   &,
-  & > div {
+  & > div,
+  & > noscript {
     display: flex;
     width: 100%;
     align-items: center;
     justify-content: center;
     outline: 0 !important;
   }
+
+  & > noscript {
+    height: 100vh;
+  }
 }
 
 .layout-single-column .app-holder {
@@ -157,3 +162,70 @@ button {
     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;
+    }
+  }
+}