Преглед на файлове

prompt when loading external scene before overriding local one (#1862)

David Luzar преди 5 години
родител
ревизия
d5e7d08586
променени са 4 файла, в които са добавени 69 реда и са изтрити 15 реда
  1. 65 13
      src/components/App.tsx
  2. 2 0
      src/constants.ts
  3. 0 1
      src/data/index.ts
  4. 2 1
      src/locales/en.json

+ 65 - 13
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<ExcalidrawProps, AppState> {
     this.onSceneUpdated();
   };
 
+  private shouldForceLoadScene(
+    scene: ResolutionType<typeof loadScene>,
+  ): 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<ExcalidrawProps, AppState> {
       /^#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<typeof loadScene> | 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<ExcalidrawProps, AppState> {
       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<ExcalidrawProps, AppState> {
   }
 
   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

+ 2 - 0
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";

+ 0 - 1
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();
   }

+ 2 - 1
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",