diff --git a/src/actions/actionExport.tsx b/src/actions/actionExport.tsx index 41fd9dc6..f01df05f 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 021e58e2..1c39dd44 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 a1f16436..7a5c5e38 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 00000000..10ace945 --- /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 e6d47d5b..fce230a6 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 62afd40b..53204de1 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 2d7adce4..25ffb32a 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 2909bf1d..ab6a7cdb 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 3d4ee82f..ec321f82 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;