2
0

TopErrorBoundary.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import React from "react";
  2. import * as Sentry from "@sentry/browser";
  3. import { t } from "@excalidraw/excalidraw/i18n";
  4. import Trans from "@excalidraw/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. "_blank",
  62. "noopener noreferrer",
  63. );
  64. }
  65. private errorSplash() {
  66. return (
  67. <div className="ErrorSplash excalidraw">
  68. <div className="ErrorSplash-messageContainer">
  69. <div className="ErrorSplash-paragraph bigger align-center">
  70. <Trans
  71. i18nKey="errorSplash.headingMain"
  72. button={(el) => (
  73. <button onClick={() => window.location.reload()}>{el}</button>
  74. )}
  75. />
  76. </div>
  77. <div className="ErrorSplash-paragraph align-center">
  78. <Trans
  79. i18nKey="errorSplash.clearCanvasMessage"
  80. button={(el) => (
  81. <button
  82. onClick={() => {
  83. try {
  84. localStorage.clear();
  85. window.location.reload();
  86. } catch (error: any) {
  87. console.error(error);
  88. }
  89. }}
  90. >
  91. {el}
  92. </button>
  93. )}
  94. />
  95. <br />
  96. <div className="smaller">
  97. <span role="img" aria-label="warning">
  98. ⚠️
  99. </span>
  100. {t("errorSplash.clearCanvasCaveat")}
  101. <span role="img" aria-hidden="true">
  102. ⚠️
  103. </span>
  104. </div>
  105. </div>
  106. <div>
  107. <div className="ErrorSplash-paragraph">
  108. {t("errorSplash.trackedToSentry", {
  109. eventId: this.state.sentryEventId,
  110. })}
  111. </div>
  112. <div className="ErrorSplash-paragraph">
  113. <Trans
  114. i18nKey="errorSplash.openIssueMessage"
  115. button={(el) => (
  116. <button onClick={() => this.createGithubIssue()}>{el}</button>
  117. )}
  118. />
  119. </div>
  120. <div className="ErrorSplash-paragraph">
  121. <div className="ErrorSplash-details">
  122. <label>{t("errorSplash.sceneContent")}</label>
  123. <textarea
  124. rows={5}
  125. onPointerDown={this.selectTextArea}
  126. readOnly={true}
  127. value={this.state.localStorage}
  128. />
  129. </div>
  130. </div>
  131. </div>
  132. </div>
  133. </div>
  134. );
  135. }
  136. }