Parcourir la source

feat: show error message when not connected to internet while collabo… (#6165)

Co-authored-by: dwelle <luzar.david@gmail.com>
Resolves https://github.com/excalidraw/excalidraw/issues/5994
Matthieu Rossignon il y a 2 ans
Parent
commit
a8e6028c33

+ 0 - 1
src/components/LayerUI.tsx

@@ -124,7 +124,6 @@ const LayerUI = ({
   children,
 }: LayerUIProps) => {
   const device = useDevice();
-
   const tunnels = useInitializeTunnels();
 
   const renderJSONExportDialog = () => {

+ 5 - 0
src/css/theme.scss

@@ -95,6 +95,9 @@
   --color-gray-90: #1e1e1e;
   --color-gray-100: #121212;
 
+  --color-warning: #fceeca;
+  --color-text-warning: var(--text-primary-color);
+
   --color-danger: #db6965;
   --color-promo: #e70078;
 
@@ -163,6 +166,8 @@
     --color-primary-darkest: #beb9ff;
     --color-primary-light: #4f4d6f;
 
+    --color-text-warning: var(--color-gray-80);
+
     --color-danger: #ffa8a5;
     --color-promo: #d297ff;
   }

+ 10 - 0
src/excalidraw-app/collab/Collab.tsx

@@ -75,6 +75,7 @@ import { jotaiStore } from "../../jotai";
 export const collabAPIAtom = atom<CollabAPI | null>(null);
 export const collabDialogShownAtom = atom(false);
 export const isCollaboratingAtom = atom(false);
+export const isOfflineAtom = atom(false);
 
 interface CollabState {
   errorMessage: string;
@@ -152,6 +153,8 @@ class Collab extends PureComponent<Props, CollabState> {
 
   componentDidMount() {
     window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
+    window.addEventListener("online", this.onOfflineStatusToggle);
+    window.addEventListener("offline", this.onOfflineStatusToggle);
     window.addEventListener(EVENT.UNLOAD, this.onUnload);
 
     const collabAPI: CollabAPI = {
@@ -165,6 +168,7 @@ class Collab extends PureComponent<Props, CollabState> {
     };
 
     jotaiStore.set(collabAPIAtom, collabAPI);
+    this.onOfflineStatusToggle();
 
     if (
       process.env.NODE_ENV === ENV.TEST ||
@@ -180,7 +184,13 @@ class Collab extends PureComponent<Props, CollabState> {
     }
   }
 
+  onOfflineStatusToggle = () => {
+    jotaiStore.set(isOfflineAtom, !window.navigator.onLine);
+  };
+
   componentWillUnmount() {
+    window.removeEventListener("online", this.onOfflineStatusToggle);
+    window.removeEventListener("offline", this.onOfflineStatusToggle);
     window.removeEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
     window.removeEventListener(EVENT.UNLOAD, this.onUnload);
     window.removeEventListener(EVENT.POINTER_MOVE, this.onPointerMove);

+ 17 - 0
src/excalidraw-app/index.scss

@@ -45,6 +45,23 @@
       }
     }
   }
+
+  .collab-offline-warning {
+    pointer-events: none;
+    position: absolute;
+    top: 6.5rem;
+    left: 50%;
+    transform: translateX(-50%);
+    padding: 0.5rem 1rem;
+    font-size: 0.875rem;
+    text-align: center;
+    line-height: 1.5;
+    border-radius: var(--border-radius-md);
+    background-color: var(--color-warning);
+    color: var(--color-text-warning);
+    z-index: 6;
+    white-space: pre;
+  }
 }
 
 .excalidraw-app.is-collaborating {

+ 11 - 4
src/excalidraw-app/index.tsx

@@ -52,6 +52,7 @@ import Collab, {
   collabAPIAtom,
   collabDialogShownAtom,
   isCollaboratingAtom,
+  isOfflineAtom,
 } from "./collab/Collab";
 import {
   exportToBackend,
@@ -66,10 +67,7 @@ import {
 } from "./data/localStorage";
 import CustomStats from "./CustomStats";
 import { restore, restoreAppState, RestoredDataState } from "../data/restore";
-
-import "./index.scss";
 import { ExportToExcalidrawPlus } from "./components/ExportToExcalidrawPlus";
-
 import { updateStaleImageStatuses } from "./data/FileManager";
 import { newElementWith } from "../element/mutateElement";
 import { isInitializedImageElement } from "../element/typeChecks";
@@ -77,7 +75,7 @@ import { loadFilesFromFirebase } from "./data/firebase";
 import { LocalData } from "./data/LocalData";
 import { isBrowserStorageStateNewer } from "./data/tabSync";
 import clsx from "clsx";
-import { atom, Provider, useAtom } from "jotai";
+import { atom, Provider, useAtom, useAtomValue } from "jotai";
 import { jotaiStore, useAtomWithInitialValue } from "../jotai";
 import { reconcileElements } from "./collab/reconciliation";
 import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
@@ -85,6 +83,8 @@ import { AppMainMenu } from "./components/AppMainMenu";
 import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
 import { AppFooter } from "./components/AppFooter";
 
+import "./index.scss";
+
 polyfill();
 
 window.EXCALIDRAW_THROTTLE_RENDER = true;
@@ -599,6 +599,8 @@ const ExcalidrawWrapper = () => {
     localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY, serializedItems);
   };
 
+  const isOffline = useAtomValue(isOfflineAtom);
+
   return (
     <div
       style={{ height: "100%" }}
@@ -661,6 +663,11 @@ const ExcalidrawWrapper = () => {
         />
         <AppWelcomeScreen setCollabDialogShown={setCollabDialogShown} />
         <AppFooter />
+        {isCollaborating && isOffline && (
+          <div className="collab-offline-warning">
+            {t("alerts.collabOfflineWarning")}
+          </div>
+        )}
       </Excalidraw>
       {excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
       {errorMessage && (

+ 2 - 1
src/locales/en.json

@@ -193,7 +193,8 @@
     "invalidSceneUrl": "Couldn't import scene from the supplied URL. It's either malformed, or doesn't contain valid Excalidraw JSON data.",
     "resetLibrary": "This will clear your library. Are you sure?",
     "removeItemsFromsLibrary": "Delete {{count}} item(s) from library?",
-    "invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled."
+    "invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.",
+    "collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!"
   },
   "errors": {
     "unsupportedFileType": "Unsupported file type.",