Jelajahi Sumber

Warn on invalid JSON file (#1159)

* add error dialog

* show error modal on file dnd

* add locales

* Update src/locales/en.json

Co-Authored-By: Lipis <lipiridis@gmail.com>

* Update src/data/blob.ts

* Update src/data/blob.ts

* fix titles, update snapshots

* make modal smaller

* fix dnd wrong file type

* reset errorMessage

Co-authored-by: Faustino Kialungila <faustino.kialungila@gmail.com>
Co-authored-by: Lipis <lipiridis@gmail.com>
Kostas Bariotis 5 tahun lalu
induk
melakukan
0c9459e9e5

+ 8 - 3
src/actions/actionExport.tsx

@@ -64,11 +64,14 @@ export const actionLoadScene = register({
   perform: (
     elements,
     appState,
-    { elements: loadedElements, appState: loadedAppState },
+    { elements: loadedElements, appState: loadedAppState, error },
   ) => {
     return {
       elements: loadedElements,
-      appState: loadedAppState,
+      appState: {
+        ...loadedAppState,
+        errorMessage: error,
+      },
       commitToHistory: false,
     };
   },
@@ -84,7 +87,9 @@ export const actionLoadScene = register({
           .then(({ elements, appState }) => {
             updateData({ elements: elements, appState: appState });
           })
-          .catch((error) => console.error(error));
+          .catch((error) => {
+            updateData({ error: error });
+          });
       }}
     />
   ),

+ 2 - 0
src/appState.ts

@@ -6,6 +6,7 @@ export const DEFAULT_FONT = "20px Virgil";
 export function getDefaultAppState(): AppState {
   return {
     isLoading: false,
+    errorMessage: null,
     draggingElement: null,
     resizingElement: null,
     multiElement: null,
@@ -52,6 +53,7 @@ export function clearAppStateForLocalStorage(appState: AppState) {
     collaborators,
     isCollaborating,
     isLoading,
+    errorMessage,
     ...exportedState
   } = appState;
   return exportedState;

+ 6 - 2
src/components/App.tsx

@@ -247,9 +247,13 @@ export class App extends React.Component<any, AppState> {
                     }),
                   )
                   .catch((error) => {
-                    console.error(error);
-                    this.setState({ isLoading: false });
+                    this.setState({ isLoading: false, errorMessage: error });
                   });
+              } else {
+                this.setState({
+                  isLoading: false,
+                  errorMessage: t("alerts.couldNotLoadInvalidFile"),
+                });
               }
             }}
           >

+ 36 - 0
src/components/ErrorDialog.tsx

@@ -0,0 +1,36 @@
+import React, { useState } from "react";
+import { t } from "../i18n";
+
+import { Dialog } from "./Dialog";
+
+export function ErrorDialog({
+  message,
+  onClose,
+}: {
+  message: string;
+  onClose?: () => void;
+}) {
+  const [modalIsShown, setModalIsShown] = useState(!!message);
+
+  const handleClose = React.useCallback(() => {
+    setModalIsShown(false);
+
+    if (onClose) {
+      onClose();
+    }
+  }, [onClose]);
+
+  return (
+    <>
+      {modalIsShown && (
+        <Dialog
+          maxWidth={500}
+          onCloseRequest={handleClose}
+          title={t("errorDialog.title")}
+        >
+          <div>{message}</div>
+        </Dialog>
+      )}
+    </>
+  );
+}

+ 7 - 0
src/components/LayerUI.tsx

@@ -22,6 +22,7 @@ import { MobileMenu } from "./MobileMenu";
 import { ZoomActions, SelectedShapeActions, ShapesSwitcher } from "./Actions";
 import { Section } from "./Section";
 import { RoomDialog } from "./RoomDialog";
+import { ErrorDialog } from "./ErrorDialog";
 import { LoadingMessage } from "./LoadingMessage";
 
 interface LayerUIProps {
@@ -105,6 +106,12 @@ export const LayerUI = React.memo(
     ) : (
       <>
         {appState.isLoading && <LoadingMessage />}
+        {appState.errorMessage && (
+          <ErrorDialog
+            message={appState.errorMessage}
+            onClose={() => setAppState({ errorMessage: null })}
+          />
+        )}
         <FixedSideContainer side="top">
           <HintViewer appState={appState} elements={elements} />
           <div className="App-menu App-menu_top">

+ 3 - 2
src/data/blob.ts

@@ -1,6 +1,7 @@
 import { getDefaultAppState } from "../appState";
 import { DataState } from "./types";
 import { restore } from "./restore";
+import { t } from "../i18n";
 
 export async function loadFromBlob(blob: any) {
   const updateAppState = (contents: string) => {
@@ -10,7 +11,7 @@ export async function loadFromBlob(blob: any) {
     try {
       const data = JSON.parse(contents);
       if (data.type !== "excalidraw") {
-        throw new Error("Cannot load invalid json");
+        throw new Error(t("alerts.couldNotLoadInvalidFile"));
       }
       elements = data.elements || [];
       appState = { ...defaultAppState, ...data.appState };
@@ -39,7 +40,7 @@ export async function loadFromBlob(blob: any) {
   }
   const { elements, appState } = updateAppState(contents);
   if (!elements.length) {
-    return Promise.reject("Cannot load invalid json");
+    return Promise.reject(t("alerts.couldNotLoadInvalidFile"));
   }
   return new Promise<DataState>((resolve) => {
     resolve(restore(elements, appState, { scrollToContent: true }));

+ 4 - 0
src/locales/en.json

@@ -74,6 +74,7 @@
   "alerts": {
     "clearReset": "This will clear the whole canvas. Are you sure?",
     "couldNotCreateShareableLink": "Couldn't create shareable link.",
+    "couldNotLoadInvalidFile": "Couldn't load invalid file",
     "importBackendFailed": "Importing from backend failed.",
     "cannotExportEmptyCanvas": "Cannot export empty canvas.",
     "couldNotCopyToClipboard": "Couldn't copy to clipboard. Try using Chrome browser.",
@@ -123,5 +124,8 @@
     "desc_persistenceWarning": "Note that the scene data is shared across collaborators in a P2P fashion, and not persisted to our server. Thus, if all of you disconnect, you will loose the data unless you export it to a file or a shareable link.",
     "desc_shareLink": "Share this link with anyone you want to collaborate with:",
     "desc_exitSession": "Stopping the session will disconnect your from the room, but you'll be able to continue working with the scene, locally. Note that this won't affect other people, and they'll still be able to collaborate on their version."
+  },
+  "errorDialog": {
+    "title": "Error"
   }
 }

+ 41 - 0
src/tests/__snapshots__/regressionTests.test.tsx.snap

@@ -16,6 +16,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -202,6 +203,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -311,6 +313,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -560,6 +563,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -705,6 +709,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -886,6 +891,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -1072,6 +1078,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -1354,6 +1361,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -1949,6 +1957,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -2058,6 +2067,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -2167,6 +2177,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -2276,6 +2287,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -2407,6 +2419,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -2538,6 +2551,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -2669,6 +2683,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -2778,6 +2793,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -2887,6 +2903,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -3018,6 +3035,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -3127,6 +3145,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -3178,6 +3197,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -3863,6 +3883,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -4224,6 +4245,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -4513,6 +4535,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -4730,6 +4753,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -4875,6 +4899,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -5524,6 +5549,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -6101,6 +6127,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -6606,6 +6633,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -7039,6 +7067,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -7436,6 +7465,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -7761,6 +7791,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -8014,6 +8045,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -8195,6 +8227,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -8880,6 +8913,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -9493,6 +9527,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -10034,6 +10069,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -10503,6 +10539,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -10746,6 +10783,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -10795,6 +10833,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -10846,6 +10885,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,
@@ -11127,6 +11167,7 @@ Object {
   "editingElement": null,
   "elementLocked": false,
   "elementType": "selection",
+  "errorMessage": null,
   "exportBackground": true,
   "isCollaborating": false,
   "isLoading": false,

+ 1 - 0
src/types.ts

@@ -11,6 +11,7 @@ export type Point = Readonly<RoughPoint>;
 
 export type AppState = {
   isLoading: boolean;
+  errorMessage: string | null;
   draggingElement: ExcalidrawElement | null;
   resizingElement: ExcalidrawElement | null;
   multiElement: ExcalidrawLinearElement | null;