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
This commit is contained in:
parent
bf6d0eeef7
commit
70b3a9de49
@ -1700,6 +1700,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.library.saveLibrary(
|
this.library.saveLibrary(
|
||||||
restoreLibraryItems(sceneData.libraryItems, "unpublished"),
|
restoreLibraryItems(sceneData.libraryItems, "unpublished"),
|
||||||
);
|
);
|
||||||
|
if (this.state.isLibraryOpen) {
|
||||||
|
this.setState({ isLibraryOpen: false }, () => {
|
||||||
|
this.setState({ isLibraryOpen: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { loadLibraryFromBlob } from "./blob";
|
import { loadLibraryFromBlob } from "./blob";
|
||||||
import { LibraryItems, LibraryItem } from "../types";
|
import { LibraryItems, LibraryItem } from "../types";
|
||||||
import { restoreElements, restoreLibraryItems } from "./restore";
|
import { restoreLibraryItems } from "./restore";
|
||||||
import { getNonDeletedElements } from "../element";
|
|
||||||
import type App from "../components/App";
|
import type App from "../components/App";
|
||||||
|
|
||||||
class Library {
|
class Library {
|
||||||
@ -17,15 +16,11 @@ class Library {
|
|||||||
this.libraryCache = [];
|
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) */
|
/** 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);
|
const libraryFile = await loadLibraryFromBlob(blob);
|
||||||
if (!libraryFile || !(libraryFile.libraryItems || libraryFile.library)) {
|
if (!libraryFile || !(libraryFile.libraryItems || libraryFile.library)) {
|
||||||
return;
|
return;
|
||||||
@ -58,15 +53,11 @@ class Library {
|
|||||||
const existingLibraryItems = await this.loadLibrary();
|
const existingLibraryItems = await this.loadLibrary();
|
||||||
|
|
||||||
const library = libraryFile.libraryItems || libraryFile.library || [];
|
const library = libraryFile.libraryItems || libraryFile.library || [];
|
||||||
const restoredLibItems = restoreLibraryItems(
|
const restoredLibItems = restoreLibraryItems(library, defaultStatus);
|
||||||
library,
|
|
||||||
defaultStatus as "published" | "unpublished",
|
|
||||||
);
|
|
||||||
const filteredItems = [];
|
const filteredItems = [];
|
||||||
for (const item of restoredLibItems) {
|
for (const item of restoredLibItems) {
|
||||||
const restoredItem = this.restoreLibraryItem(item as LibraryItem);
|
if (isUniqueitem(existingLibraryItems, item)) {
|
||||||
if (restoredItem && isUniqueitem(existingLibraryItems, restoredItem)) {
|
filteredItems.push(item);
|
||||||
filteredItems.push(restoredItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,13 +76,7 @@ class Library {
|
|||||||
return resolve([]);
|
return resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = libraryItems.reduce((acc, item) => {
|
const items = restoreLibraryItems(libraryItems, "unpublished");
|
||||||
const restoredItem = this.restoreLibraryItem(item);
|
|
||||||
if (restoredItem) {
|
|
||||||
acc.push(item);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, [] as Mutable<LibraryItems>);
|
|
||||||
|
|
||||||
// clone to ensure we don't mutate the cached library elements in the app
|
// clone to ensure we don't mutate the cached library elements in the app
|
||||||
this.libraryCache = JSON.parse(JSON.stringify(items));
|
this.libraryCache = JSON.parse(JSON.stringify(items));
|
||||||
|
@ -10,7 +10,11 @@ import {
|
|||||||
NormalizedZoomValue,
|
NormalizedZoomValue,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { ImportedDataState } from "./types";
|
import { ImportedDataState } from "./types";
|
||||||
import { getNormalizedDimensions, isInvisiblySmallElement } from "../element";
|
import {
|
||||||
|
getNonDeletedElements,
|
||||||
|
getNormalizedDimensions,
|
||||||
|
isInvisiblySmallElement,
|
||||||
|
} from "../element";
|
||||||
import { isLinearElementType } from "../element/typeChecks";
|
import { isLinearElementType } from "../element/typeChecks";
|
||||||
import { randomId } from "../random";
|
import { randomId } from "../random";
|
||||||
import {
|
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 = (
|
export const restoreLibraryItems = (
|
||||||
libraryItems: NonOptional<ImportedDataState["libraryItems"]>,
|
libraryItems: NonOptional<ImportedDataState["libraryItems"]>,
|
||||||
defaultStatus: LibraryItem["status"],
|
defaultStatus: LibraryItem["status"],
|
||||||
@ -301,20 +313,29 @@ export const restoreLibraryItems = (
|
|||||||
for (const item of libraryItems) {
|
for (const item of libraryItems) {
|
||||||
// migrate older libraries
|
// migrate older libraries
|
||||||
if (Array.isArray(item)) {
|
if (Array.isArray(item)) {
|
||||||
restoredItems.push({
|
const restoredItem = restoreLibraryItem({
|
||||||
status: defaultStatus,
|
status: defaultStatus,
|
||||||
elements: item,
|
elements: item,
|
||||||
id: randomId(),
|
id: randomId(),
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
});
|
});
|
||||||
|
if (restoredItem) {
|
||||||
|
restoredItems.push(restoredItem);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const _item = item as MarkOptional<LibraryItem, "id" | "status">;
|
const _item = item as MarkOptional<
|
||||||
restoredItems.push({
|
LibraryItem,
|
||||||
|
"id" | "status" | "created"
|
||||||
|
>;
|
||||||
|
const restoredItem = restoreLibraryItem({
|
||||||
..._item,
|
..._item,
|
||||||
id: _item.id || randomId(),
|
id: _item.id || randomId(),
|
||||||
status: _item.status || defaultStatus,
|
status: _item.status || defaultStatus,
|
||||||
created: _item.created || Date.now(),
|
created: _item.created || Date.now(),
|
||||||
});
|
});
|
||||||
|
if (restoredItem) {
|
||||||
|
restoredItems.push(restoredItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return restoredItems;
|
return restoredItems;
|
||||||
|
@ -15,6 +15,14 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Excalidraw API
|
### 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
|
#### Refactor
|
||||||
|
|
||||||
- Rename `appState.elementLocked` to `appState.activeTool.locked` [#4983](https://github.com/excalidraw/excalidraw/pull/4983).
|
- Rename `appState.elementLocked` to `appState.activeTool.locked` [#4983](https://github.com/excalidraw/excalidraw/pull/4983).
|
||||||
|
@ -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).
|
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
|
### Export utilities
|
||||||
|
|
||||||
#### `exportToCanvas`
|
#### `exportToCanvas`
|
||||||
|
@ -177,7 +177,12 @@ export {
|
|||||||
getNonDeletedElements,
|
getNonDeletedElements,
|
||||||
} from "../../element";
|
} from "../../element";
|
||||||
export { defaultLang, languages } from "../../i18n";
|
export { defaultLang, languages } from "../../i18n";
|
||||||
export { restore, restoreAppState, restoreElements } from "../../data/restore";
|
export {
|
||||||
|
restore,
|
||||||
|
restoreAppState,
|
||||||
|
restoreElements,
|
||||||
|
restoreLibraryItems,
|
||||||
|
} from "../../data/restore";
|
||||||
export {
|
export {
|
||||||
exportToCanvas,
|
exportToCanvas,
|
||||||
exportToBlob,
|
exportToBlob,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user