TopErrorBoundary.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import React from "react";
  2. import * as Sentry from "@sentry/browser";
  3. import { t } from "../../packages/excalidraw/i18n";
  4. import Trans from "../../packages/excalidraw/components/Trans";
  5. interface TopErrorBoundaryState {
  6. hasError: boolean;
  7. sentryEventId: string;
  8. localStorage: string;
  9. }
  10. export class TopErrorBoundary extends React.Component<
  11. any,
  12. TopErrorBoundaryState
  13. > {
  14. state: TopErrorBoundaryState = {
  15. hasError: false,
  16. sentryEventId: "",
  17. localStorage: "",
  18. };
  19. render() {
  20. return this.state.hasError ? this.errorSplash() : this.props.children;
  21. }
  22. componentDidCatch(error: Error, errorInfo: any) {
  23. const _localStorage: any = {};
  24. for (const [key, value] of Object.entries({ ...localStorage })) {
  25. try {
  26. _localStorage[key] = JSON.parse(value);
  27. } catch (error: any) {
  28. _localStorage[key] = value;
  29. }
  30. }
  31. Sentry.withScope((scope) => {
  32. scope.setExtras(errorInfo);
  33. const eventId = Sentry.captureException(error);
  34. this.setState((state) => ({
  35. hasError: true,
  36. sentryEventId: eventId,
  37. localStorage: JSON.stringify(_localStorage),
  38. }));
  39. });
  40. }
  41. private selectTextArea(event: React.MouseEvent<HTMLTextAreaElement>) {
  42. if (event.target !== document.activeElement) {
  43. event.preventDefault();
  44. (event.target as HTMLTextAreaElement).select();
  45. }
  46. }
  47. private async createGithubIssue() {
  48. let body = "";
  49. try {
  50. const templateStrFn = (
  51. await import(
  52. /* webpackChunkName: "bug-issue-template" */ "../bug-issue-template"
  53. )
  54. ).default;
  55. body = encodeURIComponent(templateStrFn(this.state.sentryEventId));
  56. } catch (error: any) {
  57. console.error(error);
  58. }
  59. window.open(
  60. `https://github.com/excalidraw/excalidraw/issues/new?body=${body}`,
  61. );
  62. }
  63. private errorSplash() {
  64. return (
  65. <div className="ErrorSplash excalidraw">
  66. <div className="ErrorSplash-messageContainer">
  67. <div className="ErrorSplash-paragraph bigger align-center">
  68. <Trans
  69. i18nKey="errorSplash.headingMain"
  70. button={(el) => (
  71. <button onClick={() => window.location.reload()}>{el}</button>
  72. )}
  73. />
  74. </div>
  75. <div className="ErrorSplash-paragraph align-center">
  76. <Trans
  77. i18nKey="errorSplash.clearCanvasMessage"
  78. button={(el) => (
  79. <button
  80. onClick={() => {
  81. try {
  82. localStorage.clear();
  83. window.location.reload();
  84. } catch (error: any) {
  85. console.error(error);
  86. }
  87. }}
  88. >
  89. {el}
  90. </button>
  91. )}
  92. />
  93. <br />
  94. <div className="smaller">
  95. <span role="img" aria-label="warning">
  96. ⚠️
  97. </span>
  98. {t("errorSplash.clearCanvasCaveat")}
  99. <span role="img" aria-hidden="true">
  100. ⚠️
  101. </span>
  102. </div>
  103. </div>
  104. <div>
  105. <div className="ErrorSplash-paragraph">
  106. {t("errorSplash.trackedToSentry", {
  107. eventId: this.state.sentryEventId,
  108. })}
  109. </div>
  110. <div className="ErrorSplash-paragraph">
  111. <Trans
  112. i18nKey="errorSplash.openIssueMessage"
  113. button={(el) => (
  114. <button onClick={() => this.createGithubIssue()}>{el}</button>
  115. )}
  116. />
  117. </div>
  118. <div className="ErrorSplash-paragraph">
  119. <div className="ErrorSplash-details">
  120. <label>{t("errorSplash.sceneContent")}</label>
  121. <textarea
  122. rows={5}
  123. onPointerDown={this.selectTextArea}
  124. readOnly={true}
  125. value={this.state.localStorage}
  126. />
  127. </div>
  128. </div>
  129. </div>
  130. </div>
  131. </div>
  132. );
  133. }
  134. }