library.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import { loadLibraryFromBlob } from "./blob";
  2. import { LibraryItems, LibraryItem } from "../types";
  3. import { restoreElements, restoreLibraryItems } from "./restore";
  4. import { getNonDeletedElements } from "../element";
  5. import type App from "../components/App";
  6. class Library {
  7. private libraryCache: LibraryItems | null = null;
  8. private app: App;
  9. constructor(app: App) {
  10. this.app = app;
  11. }
  12. resetLibrary = async () => {
  13. await this.app.props.onLibraryChange?.([]);
  14. this.libraryCache = [];
  15. };
  16. restoreLibraryItem = (libraryItem: LibraryItem): LibraryItem | null => {
  17. const elements = getNonDeletedElements(
  18. restoreElements(libraryItem.elements, null),
  19. );
  20. return elements.length ? { ...libraryItem, elements } : null;
  21. };
  22. /** imports library (currently merges, removing duplicates) */
  23. async importLibrary(blob: Blob, defaultStatus = "unpublished") {
  24. const libraryFile = await loadLibraryFromBlob(blob);
  25. if (!libraryFile || !(libraryFile.libraryItems || libraryFile.library)) {
  26. return;
  27. }
  28. /**
  29. * checks if library item does not exist already in current library
  30. */
  31. const isUniqueitem = (
  32. existingLibraryItems: LibraryItems,
  33. targetLibraryItem: LibraryItem,
  34. ) => {
  35. return !existingLibraryItems.find((libraryItem) => {
  36. if (libraryItem.elements.length !== targetLibraryItem.elements.length) {
  37. return false;
  38. }
  39. // detect z-index difference by checking the excalidraw elements
  40. // are in order
  41. return libraryItem.elements.every((libItemExcalidrawItem, idx) => {
  42. return (
  43. libItemExcalidrawItem.id === targetLibraryItem.elements[idx].id &&
  44. libItemExcalidrawItem.versionNonce ===
  45. targetLibraryItem.elements[idx].versionNonce
  46. );
  47. });
  48. });
  49. };
  50. const existingLibraryItems = await this.loadLibrary();
  51. const library = libraryFile.libraryItems || libraryFile.library || [];
  52. const restoredLibItems = restoreLibraryItems(
  53. library,
  54. defaultStatus as "published" | "unpublished",
  55. );
  56. const filteredItems = [];
  57. for (const item of restoredLibItems) {
  58. const restoredItem = this.restoreLibraryItem(item as LibraryItem);
  59. if (restoredItem && isUniqueitem(existingLibraryItems, restoredItem)) {
  60. filteredItems.push(restoredItem);
  61. }
  62. }
  63. await this.saveLibrary([...filteredItems, ...existingLibraryItems]);
  64. }
  65. loadLibrary = (): Promise<LibraryItems> => {
  66. return new Promise(async (resolve) => {
  67. if (this.libraryCache) {
  68. return resolve(JSON.parse(JSON.stringify(this.libraryCache)));
  69. }
  70. try {
  71. const libraryItems = this.app.libraryItemsFromStorage;
  72. if (!libraryItems) {
  73. return resolve([]);
  74. }
  75. const items = libraryItems.reduce((acc, item) => {
  76. const restoredItem = this.restoreLibraryItem(item);
  77. if (restoredItem) {
  78. acc.push(item);
  79. }
  80. return acc;
  81. }, [] as Mutable<LibraryItems>);
  82. // clone to ensure we don't mutate the cached library elements in the app
  83. this.libraryCache = JSON.parse(JSON.stringify(items));
  84. resolve(items);
  85. } catch (error: any) {
  86. console.error(error);
  87. resolve([]);
  88. }
  89. });
  90. };
  91. saveLibrary = async (items: LibraryItems) => {
  92. const prevLibraryItems = this.libraryCache;
  93. try {
  94. const serializedItems = JSON.stringify(items);
  95. // cache optimistically so that the app has access to the latest
  96. // immediately
  97. this.libraryCache = JSON.parse(serializedItems);
  98. await this.app.props.onLibraryChange?.(items);
  99. } catch (error: any) {
  100. this.libraryCache = prevLibraryItems;
  101. throw error;
  102. }
  103. };
  104. }
  105. export default Library;