Quellcode durchsuchen

feat: prefer hash when importing libraries & expose importLibrary (#3320)

David Luzar vor 4 Jahren
Ursprung
Commit
cf9e29834d

+ 30 - 9
src/components/App.tsx

@@ -58,6 +58,8 @@ import {
   TAP_TWICE_TIMEOUT,
   TEXT_TO_CENTER_SNAP_THRESHOLD,
   TOUCH_CTX_MENU_TIMEOUT,
+  URL_HASH_KEYS,
+  URL_QUERY_KEYS,
   ZOOM_STEP,
 } from "../constants";
 import { loadFromBlob } from "../data";
@@ -278,6 +280,7 @@ export type ExcalidrawImperativeAPI = {
   getSceneElements: InstanceType<typeof App>["getSceneElements"];
   getAppState: () => InstanceType<typeof App>["state"];
   setCanvasOffsets: InstanceType<typeof App>["setCanvasOffsets"];
+  importLibrary: InstanceType<typeof App>["importLibraryFromUrl"];
   readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
   ready: true;
 };
@@ -338,6 +341,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         getSceneElements: this.getSceneElements,
         getAppState: () => this.state,
         setCanvasOffsets: this.setCanvasOffsets,
+        importLibrary: this.importLibraryFromUrl,
       } as const;
       if (typeof excalidrawRef === "function") {
         excalidrawRef(api);
@@ -606,7 +610,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
   };
 
   private importLibraryFromUrl = async (url: string) => {
-    window.history.replaceState({}, APP_NAME, window.location.origin);
+    if (window.location.hash.includes(URL_HASH_KEYS.addLibrary)) {
+      const hash = new URLSearchParams(window.location.hash.slice(1));
+      hash.delete(URL_HASH_KEYS.addLibrary);
+      window.history.replaceState({}, APP_NAME, `#${hash.toString()}`);
+    } else if (window.location.search.includes(URL_QUERY_KEYS.addLibrary)) {
+      const query = new URLSearchParams(window.location.search);
+      query.delete(URL_QUERY_KEYS.addLibrary);
+      window.history.replaceState({}, APP_NAME, `?${query.toString()}`);
+    }
+
     try {
       const request = await fetch(decodeURIComponent(url));
       const blob = await request.blob();
@@ -620,9 +633,11 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         )
       ) {
         await Library.importLibrary(blob);
-        this.setState({
-          isLibraryOpen: true,
-        });
+        // hack to rerender the library items after import
+        if (this.state.isLibraryOpen) {
+          this.setState({ isLibraryOpen: false });
+        }
+        this.setState({ isLibraryOpen: true });
       }
     } catch (error) {
       window.alert(t("alerts.errorLoadingLibrary"));
@@ -718,12 +733,18 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       commitToHistory: true,
     });
 
-    const addToLibraryUrl = new URLSearchParams(window.location.search).get(
-      "addLibrary",
-    );
+    const libraryUrl =
+      // current
+      new URLSearchParams(window.location.hash.slice(1)).get(
+        URL_HASH_KEYS.addLibrary,
+      ) ||
+      // legacy, kept for compat reasons
+      new URLSearchParams(window.location.search).get(
+        URL_QUERY_KEYS.addLibrary,
+      );
 
-    if (addToLibraryUrl) {
-      await this.importLibraryFromUrl(addToLibraryUrl);
+    if (libraryUrl) {
+      await this.importLibraryFromUrl(libraryUrl);
     }
   };
 

+ 1 - 1
src/components/LayerUI.tsx

@@ -179,7 +179,7 @@ const LibraryMenuItems = ({
       <a
         href={`https://libraries.excalidraw.com?target=${
           window.name || "_blank"
-        }&referrer=${referrer}`}
+        }&referrer=${referrer}&useHash=true`}
         target="_excalidraw_libraries"
       >
         {t("labels.libraries")}

+ 8 - 0
src/constants.ts

@@ -116,3 +116,11 @@ export const MODES = {
 };
 
 export const THEME_FILTER = cssVariables.themeFilter;
+
+export const URL_QUERY_KEYS = {
+  addLibrary: "addLibrary",
+} as const;
+
+export const URL_HASH_KEYS = {
+  addLibrary: "addLibrary",
+} as const;

+ 26 - 7
src/excalidraw-app/index.tsx

@@ -12,7 +12,13 @@ import { getDefaultAppState } from "../appState";
 import { ExcalidrawImperativeAPI } from "../components/App";
 import { ErrorDialog } from "../components/ErrorDialog";
 import { TopErrorBoundary } from "../components/TopErrorBoundary";
-import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
+import {
+  APP_NAME,
+  EVENT,
+  TITLE_TIMEOUT,
+  URL_HASH_KEYS,
+  VERSION_TIMEOUT,
+} from "../constants";
 import { loadFromBlob } from "../data/blob";
 import { DataState, ImportedDataState } from "../data/types";
 import {
@@ -213,12 +219,25 @@ function ExcalidrawWrapper() {
       initialStatePromiseRef.current.promise.resolve(scene);
     });
 
-    const onHashChange = (_: HashChangeEvent) => {
-      initializeScene({ collabAPI }).then((scene) => {
-        if (scene) {
-          excalidrawAPI.updateScene(scene);
-        }
-      });
+    const onHashChange = (event: HashChangeEvent) => {
+      event.preventDefault();
+      const libraryUrl = new URLSearchParams(window.location.hash.slice(1)).get(
+        URL_HASH_KEYS.addLibrary,
+      );
+      if (libraryUrl) {
+        // If hash changed and it contains library url, import it and replace
+        // the url to its previous state (important in case of collaboration
+        // and similar).
+        // Using history API won't trigger another hashchange.
+        window.history.replaceState({}, "", event.oldURL);
+        excalidrawAPI.importLibrary(libraryUrl);
+      } else {
+        initializeScene({ collabAPI }).then((scene) => {
+          if (scene) {
+            excalidrawAPI.updateScene(scene);
+          }
+        });
+      }
     };
 
     const titleTimeout = setTimeout(

+ 2 - 0
src/packages/excalidraw/CHANGELOG.md

@@ -18,6 +18,8 @@ Please add the latest change on the top under the correct section.
 
 ### Features
 
+- #### BREAKING CHANGE
+  Use `location.hash` when importing libraries to fix installation issues. This will require host apps to add a `hashchange` listener and call the newly exposed `excalidrawAPI.importLibrary(url)` API when applicable [#3320](https://github.com/excalidraw/excalidraw/pull/3320).
 - Append `location.pathname` to `libraryReturnUrl` default url [#3325](https://github.com/excalidraw/excalidraw/pull/3325).
 
 ## 0.5.0 (2021-03-21)

+ 1 - 0
src/packages/excalidraw/README.md

@@ -473,6 +473,7 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
 | history | `{ clear: () => void }` | This is the history API. `history.clear()` will clear the history |
 | setScrollToContent | <pre> (<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>) => void </pre> | Scroll to the nearest element to center |
 | setCanvasOffsets | `() => void` | Updates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You should call this API when your app changes the dimensions/position of the Excalidraw container, such as when toggling a sidebar. You don't have to call this when the position is changed on page scroll (we handled that ourselves). |
+| importLibrary | `(url: string) => void` | Imports library from given URL. You should call this on `hashchange`, passing the `addLibrary` value if you detect it. |
 
 #### `readyPromise`