library.ts 3.4 KB

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