excalidraw/excalidraw-app/components/TopErrorBoundary.tsx

145 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from "react";
import * as Sentry from "@sentry/browser";
import { t } from "../../packages/excalidraw/i18n";
import Trans from "../../packages/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}`,
);
}
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>
);
}
}