| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 | import Trans from "@excalidraw/excalidraw/components/Trans";import { t } from "@excalidraw/excalidraw/i18n";import * as Sentry from "@sentry/browser";import React from "react";interface TopErrorBoundaryState {  hasError: boolean;  sentryEventId: string;  localStorage: string;}export class TopErrorBoundary extends React.Component<  any,  TopErrorBoundaryState> {  state: TopErrorBoundaryState = {    hasError: false,    sentryEventId: "",    localStorage: "",  };  render() {    return this.state.hasError ? this.errorSplash() : this.props.children;  }  componentDidCatch(error: Error, errorInfo: any) {    const _localStorage: any = {};    for (const [key, value] of Object.entries({ ...localStorage })) {      try {        _localStorage[key] = JSON.parse(value);      } catch (error: any) {        _localStorage[key] = value;      }    }    Sentry.withScope((scope) => {      scope.setExtras(errorInfo);      const eventId = Sentry.captureException(error);      this.setState((state) => ({        hasError: true,        sentryEventId: eventId,        localStorage: JSON.stringify(_localStorage),      }));    });  }  private selectTextArea(event: React.MouseEvent<HTMLTextAreaElement>) {    if (event.target !== document.activeElement) {      event.preventDefault();      (event.target as HTMLTextAreaElement).select();    }  }  private async createGithubIssue() {    let body = "";    try {      const templateStrFn = (        await import(          /* webpackChunkName: "bug-issue-template" */ "../bug-issue-template"        )      ).default;      body = encodeURIComponent(templateStrFn(this.state.sentryEventId));    } catch (error: any) {      console.error(error);    }    window.open(      `https://github.com/excalidraw/excalidraw/issues/new?body=${body}`,      "_blank",      "noopener noreferrer",    );  }  private errorSplash() {    return (      <div className="ErrorSplash excalidraw">        <div className="ErrorSplash-messageContainer">          <div className="ErrorSplash-paragraph bigger align-center">            <Trans              i18nKey="errorSplash.headingMain"              button={(el) => (                <button onClick={() => window.location.reload()}>{el}</button>              )}            />          </div>          <div className="ErrorSplash-paragraph align-center">            <Trans              i18nKey="errorSplash.clearCanvasMessage"              button={(el) => (                <button                  onClick={() => {                    try {                      localStorage.clear();                      window.location.reload();                    } catch (error: any) {                      console.error(error);                    }                  }}                >                  {el}                </button>              )}            />            <br />            <div className="smaller">              <span role="img" aria-label="warning">                ⚠️              </span>              {t("errorSplash.clearCanvasCaveat")}              <span role="img" aria-hidden="true">                ⚠️              </span>            </div>          </div>          <div>            <div className="ErrorSplash-paragraph">              {t("errorSplash.trackedToSentry", {                eventId: this.state.sentryEventId,              })}            </div>            <div className="ErrorSplash-paragraph">              <Trans                i18nKey="errorSplash.openIssueMessage"                button={(el) => (                  <button onClick={() => this.createGithubIssue()}>{el}</button>                )}              />            </div>            <div className="ErrorSplash-paragraph">              <div className="ErrorSplash-details">                <label>{t("errorSplash.sceneContent")}</label>                <textarea                  rows={5}                  onPointerDown={this.selectTextArea}                  readOnly={true}                  value={this.state.localStorage}                />              </div>            </div>          </div>        </div>      </div>    );  }}
 |