From 0c9459e9e5a8c6758ddc0488e53254a28c8d46fc Mon Sep 17 00:00:00 2001 From: Kostas Bariotis Date: Fri, 3 Apr 2020 12:50:51 +0100 Subject: [PATCH] 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 * 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 Co-authored-by: Lipis --- src/actions/actionExport.tsx | 11 +++-- src/appState.ts | 2 + src/components/App.tsx | 8 +++- src/components/ErrorDialog.tsx | 36 ++++++++++++++++ src/components/LayerUI.tsx | 7 ++++ src/data/blob.ts | 5 ++- src/locales/en.json | 4 ++ .../regressionTests.test.tsx.snap | 41 +++++++++++++++++++ src/types.ts | 1 + 9 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 src/components/ErrorDialog.tsx diff --git a/src/actions/actionExport.tsx b/src/actions/actionExport.tsx index 41fd9dc6eb..f01df05f26 100644 --- a/src/actions/actionExport.tsx +++ b/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 }); + }); }} /> ), diff --git a/src/appState.ts b/src/appState.ts index 021e58e25c..1c39dd4485 100644 --- a/src/appState.ts +++ b/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; diff --git a/src/components/App.tsx b/src/components/App.tsx index a1f16436de..7a5c5e38c4 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -247,9 +247,13 @@ export class App extends React.Component { }), ) .catch((error) => { - console.error(error); - this.setState({ isLoading: false }); + this.setState({ isLoading: false, errorMessage: error }); }); + } else { + this.setState({ + isLoading: false, + errorMessage: t("alerts.couldNotLoadInvalidFile"), + }); } }} > diff --git a/src/components/ErrorDialog.tsx b/src/components/ErrorDialog.tsx new file mode 100644 index 0000000000..10ace9454a --- /dev/null +++ b/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 && ( + +
{message}
+
+ )} + + ); +} diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index e6d47d5b88..fce230a612 100644 --- a/src/components/LayerUI.tsx +++ b/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 && } + {appState.errorMessage && ( + setAppState({ errorMessage: null })} + /> + )}
diff --git a/src/data/blob.ts b/src/data/blob.ts index 62afd40b5c..53204de1dd 100644 --- a/src/data/blob.ts +++ b/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((resolve) => { resolve(restore(elements, appState, { scrollToContent: true })); diff --git a/src/locales/en.json b/src/locales/en.json index 2d7adce422..25ffb32ab6 100644 --- a/src/locales/en.json +++ b/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" } } diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 2909bf1df4..ab6a7cdbee 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/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, diff --git a/src/types.ts b/src/types.ts index 3d4ee82ff2..ec321f826c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export type Point = Readonly; export type AppState = { isLoading: boolean; + errorMessage: string | null; draggingElement: ExcalidrawElement | null; resizingElement: ExcalidrawElement | null; multiElement: ExcalidrawLinearElement | null;