diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | bfc2cec7d43eb8eaa46dd3f91084932381257059 (patch) | |
| tree | 0857e3aac2cff922826d4871ff54536b26fad6fc /excalidraw-app/components/TopErrorBoundary.tsx | |
| parent | 225db4a7805befe009fe055fc2ef5daedd6c04f9 (diff) | |
refactor: excalidraw-app/
Diffstat (limited to 'excalidraw-app/components/TopErrorBoundary.tsx')
| -rw-r--r-- | excalidraw-app/components/TopErrorBoundary.tsx | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/excalidraw-app/components/TopErrorBoundary.tsx b/excalidraw-app/components/TopErrorBoundary.tsx new file mode 100644 index 0000000..e7e00be --- /dev/null +++ b/excalidraw-app/components/TopErrorBoundary.tsx @@ -0,0 +1,146 @@ +import React from "react"; +import * as Sentry from "@sentry/browser"; +import { t } from "@excalidraw/excalidraw/i18n"; +import Trans from "@excalidraw/excalidraw/components/Trans"; + +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> + ); + } +} |
