2020-07-27 15:29:19 +03:00
|
|
|
import { loadLibraryFromBlob } from "./blob";
|
|
|
|
import { LibraryItems, LibraryItem } from "../types";
|
2022-04-14 16:20:35 +02:00
|
|
|
import { restoreLibraryItems } from "./restore";
|
2021-06-01 23:52:13 +05:30
|
|
|
import type App from "../components/App";
|
2022-04-20 14:40:03 +02:00
|
|
|
import { ImportedDataState } from "./types";
|
|
|
|
import { atom } from "jotai";
|
|
|
|
import { jotaiStore } from "../jotai";
|
|
|
|
import { isPromiseLike } from "../utils";
|
|
|
|
import { t } from "../i18n";
|
|
|
|
|
|
|
|
export const libraryItemsAtom = atom<
|
|
|
|
| { status: "loading"; libraryItems: null; promise: Promise<LibraryItems> }
|
|
|
|
| { status: "loaded"; libraryItems: LibraryItems }
|
|
|
|
>({ status: "loaded", libraryItems: [] });
|
|
|
|
|
|
|
|
const cloneLibraryItems = (libraryItems: LibraryItems): LibraryItems =>
|
|
|
|
JSON.parse(JSON.stringify(libraryItems));
|
|
|
|
|
|
|
|
/**
|
|
|
|
* checks if library item does not exist already in current library
|
|
|
|
*/
|
|
|
|
const isUniqueItem = (
|
|
|
|
existingLibraryItems: LibraryItems,
|
|
|
|
targetLibraryItem: LibraryItem,
|
|
|
|
) => {
|
|
|
|
return !existingLibraryItems.find((libraryItem) => {
|
|
|
|
if (libraryItem.elements.length !== targetLibraryItem.elements.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// detect z-index difference by checking the excalidraw elements
|
|
|
|
// are in order
|
|
|
|
return libraryItem.elements.every((libItemExcalidrawItem, idx) => {
|
|
|
|
return (
|
|
|
|
libItemExcalidrawItem.id === targetLibraryItem.elements[idx].id &&
|
|
|
|
libItemExcalidrawItem.versionNonce ===
|
|
|
|
targetLibraryItem.elements[idx].versionNonce
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
2020-07-27 15:29:19 +03:00
|
|
|
|
2021-04-21 23:38:24 +05:30
|
|
|
class Library {
|
2022-04-20 14:40:03 +02:00
|
|
|
/** cache for currently active promise when initializing/updating libaries
|
|
|
|
asynchronously */
|
|
|
|
private libraryItemsPromise: Promise<LibraryItems> | null = null;
|
|
|
|
/** last resolved libraryItems */
|
|
|
|
private lastLibraryItems: LibraryItems = [];
|
|
|
|
|
2021-04-21 23:38:24 +05:30
|
|
|
private app: App;
|
2020-10-30 21:01:41 +01:00
|
|
|
|
2021-04-21 23:38:24 +05:30
|
|
|
constructor(app: App) {
|
|
|
|
this.app = app;
|
|
|
|
}
|
|
|
|
|
|
|
|
resetLibrary = async () => {
|
2022-04-20 14:40:03 +02:00
|
|
|
this.saveLibrary([]);
|
2020-10-30 21:01:41 +01:00
|
|
|
};
|
|
|
|
|
2020-07-27 15:29:19 +03:00
|
|
|
/** imports library (currently merges, removing duplicates) */
|
2022-04-14 16:20:35 +02:00
|
|
|
async importLibrary(
|
2022-04-20 14:40:03 +02:00
|
|
|
library:
|
|
|
|
| Blob
|
|
|
|
| Required<ImportedDataState>["libraryItems"]
|
|
|
|
| Promise<Required<ImportedDataState>["libraryItems"]>,
|
2022-04-14 16:20:35 +02:00
|
|
|
defaultStatus: LibraryItem["status"] = "unpublished",
|
|
|
|
) {
|
2022-04-20 14:40:03 +02:00
|
|
|
return this.saveLibrary(
|
|
|
|
new Promise<LibraryItems>(async (resolve, reject) => {
|
|
|
|
try {
|
|
|
|
let libraryItems: LibraryItems;
|
|
|
|
if (library instanceof Blob) {
|
|
|
|
libraryItems = await loadLibraryFromBlob(library, defaultStatus);
|
|
|
|
} else {
|
|
|
|
libraryItems = restoreLibraryItems(await library, defaultStatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
const existingLibraryItems = this.lastLibraryItems;
|
|
|
|
|
|
|
|
const filteredItems = [];
|
|
|
|
for (const item of libraryItems) {
|
|
|
|
if (isUniqueItem(existingLibraryItems, item)) {
|
|
|
|
filteredItems.push(item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve([...filteredItems, ...existingLibraryItems]);
|
|
|
|
} catch (error) {
|
|
|
|
reject(new Error(t("errors.importLibraryError")));
|
2020-07-27 15:29:19 +03:00
|
|
|
}
|
2022-04-20 14:40:03 +02:00
|
|
|
}),
|
|
|
|
);
|
2020-07-27 15:29:19 +03:00
|
|
|
}
|
2020-10-30 21:01:41 +01:00
|
|
|
|
2021-04-21 23:38:24 +05:30
|
|
|
loadLibrary = (): Promise<LibraryItems> => {
|
2020-10-30 21:01:41 +01:00
|
|
|
return new Promise(async (resolve) => {
|
|
|
|
try {
|
2022-04-20 14:40:03 +02:00
|
|
|
resolve(
|
|
|
|
cloneLibraryItems(
|
|
|
|
await (this.libraryItemsPromise || this.lastLibraryItems),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
return resolve(this.lastLibraryItems);
|
2020-10-30 21:01:41 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-04-20 14:40:03 +02:00
|
|
|
saveLibrary = async (items: LibraryItems | Promise<LibraryItems>) => {
|
|
|
|
const prevLibraryItems = this.lastLibraryItems;
|
2020-10-30 21:01:41 +01:00
|
|
|
try {
|
2022-04-20 14:40:03 +02:00
|
|
|
let nextLibraryItems;
|
|
|
|
if (isPromiseLike(items)) {
|
|
|
|
const promise = items.then((items) => cloneLibraryItems(items));
|
|
|
|
this.libraryItemsPromise = promise;
|
|
|
|
jotaiStore.set(libraryItemsAtom, {
|
|
|
|
status: "loading",
|
|
|
|
promise,
|
|
|
|
libraryItems: null,
|
|
|
|
});
|
|
|
|
nextLibraryItems = await promise;
|
|
|
|
} else {
|
|
|
|
nextLibraryItems = cloneLibraryItems(items);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.lastLibraryItems = nextLibraryItems;
|
|
|
|
this.libraryItemsPromise = null;
|
|
|
|
|
|
|
|
jotaiStore.set(libraryItemsAtom, {
|
|
|
|
status: "loaded",
|
|
|
|
libraryItems: nextLibraryItems,
|
|
|
|
});
|
|
|
|
await this.app.props.onLibraryChange?.(
|
|
|
|
cloneLibraryItems(nextLibraryItems),
|
|
|
|
);
|
2021-11-02 14:24:16 +02:00
|
|
|
} catch (error: any) {
|
2022-04-20 14:40:03 +02:00
|
|
|
this.lastLibraryItems = prevLibraryItems;
|
|
|
|
this.libraryItemsPromise = null;
|
|
|
|
jotaiStore.set(libraryItemsAtom, {
|
|
|
|
status: "loaded",
|
|
|
|
libraryItems: prevLibraryItems,
|
|
|
|
});
|
2021-04-21 23:38:24 +05:30
|
|
|
throw error;
|
2020-10-30 21:01:41 +01:00
|
|
|
}
|
|
|
|
};
|
2020-07-27 15:29:19 +03:00
|
|
|
}
|
2021-04-21 23:38:24 +05:30
|
|
|
|
|
|
|
export default Library;
|