From d5e7d08586c0ee79a3ec717ea92be1c58ff5fc2d Mon Sep 17 00:00:00 2001 From: David Luzar Date: Wed, 8 Jul 2020 22:55:26 +0200 Subject: [PATCH] prompt when loading external scene before overriding local one (#1862) --- src/components/App.tsx | 78 +++++++++++++++++++++++++++++++++++------- src/constants.ts | 2 ++ src/data/index.ts | 1 - src/locales/en.json | 3 +- 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index f5af781895..335ba51879 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -121,6 +121,7 @@ import { CANVAS_ONLY_ACTIONS, DEFAULT_VERTICAL_ALIGN, GRID_SIZE, + LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG, } from "../constants"; import { INITAL_SCENE_UPDATE_TIMEOUT, @@ -356,6 +357,39 @@ class App extends React.Component { this.onSceneUpdated(); }; + private shouldForceLoadScene( + scene: ResolutionType, + ): boolean { + if (!scene.elements.length) { + return true; + } + + const roomMatch = getCollaborationLinkData(window.location.href); + + if (!roomMatch) { + return false; + } + const collabForceLoadFlag = localStorage.getItem( + LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG, + ); + if (collabForceLoadFlag) { + try { + const { + room: previousRoom, + timestamp, + }: { room: string; timestamp: number } = JSON.parse( + collabForceLoadFlag, + ); + // if loading same room as the one previously unloaded within 15sec + // force reload without prompting + if (previousRoom === roomMatch[1] && Date.now() - timestamp < 15000) { + return true; + } + } catch {} + } + return false; + } + private initializeScene = async () => { const searchParams = new URLSearchParams(window.location.search); const id = searchParams.get("id"); @@ -363,20 +397,28 @@ class App extends React.Component { /^#json=([0-9]+),([a-zA-Z0-9_-]+)$/, ); - const isCollaborationScene = getCollaborationLinkData(window.location.href); + let scene = await loadScene(null); + + let isCollaborationScene = !!getCollaborationLinkData(window.location.href); + const isExternalScene = !!(id || jsonMatch || isCollaborationScene); - if (!isCollaborationScene) { - let scene: ResolutionType | undefined; - // Backwards compatibility with legacy url format - if (id) { - scene = await loadScene(id); - } else if (jsonMatch) { - scene = await loadScene(jsonMatch[1], jsonMatch[2]); + if (isExternalScene) { + if ( + this.shouldForceLoadScene(scene) || + window.confirm(t("alerts.loadSceneOverridePrompt")) + ) { + // Backwards compatibility with legacy url format + if (id) { + scene = await loadScene(id); + } else if (jsonMatch) { + scene = await loadScene(jsonMatch[1], jsonMatch[2]); + } + if (!isCollaborationScene) { + window.history.replaceState({}, "Excalidraw", window.location.origin); + } } else { - scene = await loadScene(null); - } - if (scene) { - this.syncActionResult(scene); + isCollaborationScene = false; + window.history.replaceState({}, "Excalidraw", window.location.origin); } } @@ -384,9 +426,10 @@ class App extends React.Component { this.setState({ isLoading: false }); } - // run this last else the `isLoading` state if (isCollaborationScene) { this.initializeSocketClient({ showLoadingState: true }); + } else if (scene) { + this.syncActionResult(scene); } }; @@ -515,6 +558,15 @@ class App extends React.Component { } private beforeUnload = withBatchedUpdates((event: BeforeUnloadEvent) => { + if (this.state.isCollaborating && this.portal.roomID) { + localStorage.setItem( + LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG, + JSON.stringify({ + timestamp: Date.now(), + room: this.portal.roomID, + }), + ); + } if ( this.state.isCollaborating && globalSceneState.getElements().length > 0 diff --git a/src/constants.ts b/src/constants.ts index 57e067f9d3..f7f5889c25 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -77,3 +77,5 @@ export const DEFAULT_VERTICAL_ALIGN = "top"; export const CANVAS_ONLY_ACTIONS = ["selectAll"]; export const GRID_SIZE = 20; // TODO make it configurable? + +export const LOCAL_STORAGE_KEY_COLLAB_FORCE_FLAG = "collabLinkForceLoadFlag"; diff --git a/src/data/index.ts b/src/data/index.ts index 129f6b434a..946822dd6c 100644 --- a/src/data/index.ts +++ b/src/data/index.ts @@ -367,7 +367,6 @@ export const loadScene = async (id: string | null, privateKey?: string) => { // the private key is used to decrypt the content from the server, take // extra care not to leak it data = await importFromBackend(id, privateKey); - window.history.replaceState({}, "Excalidraw", window.location.origin); } else { data = restoreFromLocalStorage(); } diff --git a/src/locales/en.json b/src/locales/en.json index f7f6fdb386..0b639f1085 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -103,7 +103,8 @@ "cannotExportEmptyCanvas": "Cannot export empty canvas.", "couldNotCopyToClipboard": "Couldn't copy to clipboard. Try using Chrome browser.", "decryptFailed": "Couldn't decrypt data.", - "uploadedSecurly": "The upload has been secured with end-to-end encryption, which means that Excalidraw server and third parties can't read the content." + "uploadedSecurly": "The upload has been secured with end-to-end encryption, which means that Excalidraw server and third parties can't read the content.", + "loadSceneOverridePrompt": "Loading external drawing will replace your existing content. Do you wish to continue?" }, "toolBar": { "selection": "Selection",