mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-10-26 08:24:20 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			147 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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}`,
 | ||
|       "_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>
 | ||
|     );
 | ||
|   }
 | ||
| }
 | 
