Browse Source

feat: library restoring changes (#4995)

* restore library items in all cases & refactor

* export `restoreLibraryItems` from package

* feat: rerender library menu when updating via API

* update readme & changelog

* fix changelog
David Luzar 3 years ago
parent
commit
70b3a9de49

+ 5 - 0
src/components/App.tsx

@@ -1700,6 +1700,11 @@ class App extends React.Component<AppProps, AppState> {
         this.library.saveLibrary(
           restoreLibraryItems(sceneData.libraryItems, "unpublished"),
         );
+        if (this.state.isLibraryOpen) {
+          this.setState({ isLibraryOpen: false }, () => {
+            this.setState({ isLibraryOpen: true });
+          });
+        }
       }
     },
   );

+ 9 - 24
src/data/library.ts

@@ -1,7 +1,6 @@
 import { loadLibraryFromBlob } from "./blob";
 import { LibraryItems, LibraryItem } from "../types";
-import { restoreElements, restoreLibraryItems } from "./restore";
-import { getNonDeletedElements } from "../element";
+import { restoreLibraryItems } from "./restore";
 import type App from "../components/App";
 
 class Library {
@@ -17,15 +16,11 @@ class Library {
     this.libraryCache = [];
   };
 
-  restoreLibraryItem = (libraryItem: LibraryItem): LibraryItem | null => {
-    const elements = getNonDeletedElements(
-      restoreElements(libraryItem.elements, null),
-    );
-    return elements.length ? { ...libraryItem, elements } : null;
-  };
-
   /** imports library (currently merges, removing duplicates) */
-  async importLibrary(blob: Blob, defaultStatus = "unpublished") {
+  async importLibrary(
+    blob: Blob,
+    defaultStatus: LibraryItem["status"] = "unpublished",
+  ) {
     const libraryFile = await loadLibraryFromBlob(blob);
     if (!libraryFile || !(libraryFile.libraryItems || libraryFile.library)) {
       return;
@@ -58,15 +53,11 @@ class Library {
     const existingLibraryItems = await this.loadLibrary();
 
     const library = libraryFile.libraryItems || libraryFile.library || [];
-    const restoredLibItems = restoreLibraryItems(
-      library,
-      defaultStatus as "published" | "unpublished",
-    );
+    const restoredLibItems = restoreLibraryItems(library, defaultStatus);
     const filteredItems = [];
     for (const item of restoredLibItems) {
-      const restoredItem = this.restoreLibraryItem(item as LibraryItem);
-      if (restoredItem && isUniqueitem(existingLibraryItems, restoredItem)) {
-        filteredItems.push(restoredItem);
+      if (isUniqueitem(existingLibraryItems, item)) {
+        filteredItems.push(item);
       }
     }
 
@@ -85,13 +76,7 @@ class Library {
           return resolve([]);
         }
 
-        const items = libraryItems.reduce((acc, item) => {
-          const restoredItem = this.restoreLibraryItem(item);
-          if (restoredItem) {
-            acc.push(item);
-          }
-          return acc;
-        }, [] as Mutable<LibraryItems>);
+        const items = restoreLibraryItems(libraryItems, "unpublished");
 
         // clone to ensure we don't mutate the cached library elements in the app
         this.libraryCache = JSON.parse(JSON.stringify(items));

+ 25 - 4
src/data/restore.ts

@@ -10,7 +10,11 @@ import {
   NormalizedZoomValue,
 } from "../types";
 import { ImportedDataState } from "./types";
-import { getNormalizedDimensions, isInvisiblySmallElement } from "../element";
+import {
+  getNonDeletedElements,
+  getNormalizedDimensions,
+  isInvisiblySmallElement,
+} from "../element";
 import { isLinearElementType } from "../element/typeChecks";
 import { randomId } from "../random";
 import {
@@ -293,6 +297,14 @@ export const restore = (
   };
 };
 
+const restoreLibraryItem = (libraryItem: LibraryItem) => {
+  const elements = restoreElements(
+    getNonDeletedElements(libraryItem.elements),
+    null,
+  );
+  return elements.length ? { ...libraryItem, elements } : null;
+};
+
 export const restoreLibraryItems = (
   libraryItems: NonOptional<ImportedDataState["libraryItems"]>,
   defaultStatus: LibraryItem["status"],
@@ -301,20 +313,29 @@ export const restoreLibraryItems = (
   for (const item of libraryItems) {
     // migrate older libraries
     if (Array.isArray(item)) {
-      restoredItems.push({
+      const restoredItem = restoreLibraryItem({
         status: defaultStatus,
         elements: item,
         id: randomId(),
         created: Date.now(),
       });
+      if (restoredItem) {
+        restoredItems.push(restoredItem);
+      }
     } else {
-      const _item = item as MarkOptional<LibraryItem, "id" | "status">;
-      restoredItems.push({
+      const _item = item as MarkOptional<
+        LibraryItem,
+        "id" | "status" | "created"
+      >;
+      const restoredItem = restoreLibraryItem({
         ..._item,
         id: _item.id || randomId(),
         status: _item.status || defaultStatus,
         created: _item.created || Date.now(),
       });
+      if (restoredItem) {
+        restoredItems.push(restoredItem);
+      }
     }
   }
   return restoredItems;

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

@@ -15,6 +15,14 @@ Please add the latest change on the top under the correct section.
 
 ### Excalidraw API
 
+#### Features
+
+- Exported [`restoreLibraryItems`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreLibraryItems) API useful when dealing with libraries [#4995](https://github.com/excalidraw/excalidraw/pull/4995).
+
+#### Fixes
+
+- Library menu now properly rerenders if open when library is updated using `updateScene({ libraryItems })` [#4995](https://github.com/excalidraw/excalidraw/pull/4995).
+
 #### Refactor
 
 - Rename `appState.elementLocked` to `appState.activeTool.locked` [#4983](https://github.com/excalidraw/excalidraw/pull/4983).

+ 18 - 0
src/packages/excalidraw/README_NEXT.md

@@ -802,6 +802,24 @@ import { restore } from "@excalidraw/excalidraw-next";
 
 This function makes sure elements and state is set to appropriate values and set to default value if not present. It is a combination of [restoreElements](#restoreElements) and [restoreAppState](#restoreAppState).
 
+#### `restoreLibraryItems`
+
+**_Signature_**
+
+<pre>
+restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L22">ImportedDataState["libraryItems"]</a>, defaultStatus: "published" | "unpublished")
+</pre>
+
+**_How to use_**
+
+```js
+import { restoreLibraryItems } from "@excalidraw/excalidraw-next";
+
+restoreLibraryItems(libraryItems, "unpublished");
+```
+
+This function normalizes library items elements, adding missing values when needed.
+
 ### Export utilities
 
 #### `exportToCanvas`

+ 6 - 1
src/packages/excalidraw/index.tsx

@@ -177,7 +177,12 @@ export {
   getNonDeletedElements,
 } from "../../element";
 export { defaultLang, languages } from "../../i18n";
-export { restore, restoreAppState, restoreElements } from "../../data/restore";
+export {
+  restore,
+  restoreAppState,
+  restoreElements,
+  restoreLibraryItems,
+} from "../../data/restore";
 export {
   exportToCanvas,
   exportToBlob,