@excalidraw/excalidraw 0.17.1-a38e82f → 0.17.1-b7babe5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/dist/browser/dev/excalidraw-assets-dev/{chunk-IM4WTX2M.js → chunk-6NMK7JTV.js} +2 -1
  3. package/dist/browser/dev/excalidraw-assets-dev/chunk-6NMK7JTV.js.map +7 -0
  4. package/dist/browser/dev/excalidraw-assets-dev/{chunk-5VWQDKDR.js → chunk-CX3RATXT.js} +50 -5
  5. package/dist/browser/dev/excalidraw-assets-dev/chunk-CX3RATXT.js.map +7 -0
  6. package/dist/browser/dev/excalidraw-assets-dev/{en-IOBA4CS2.js → en-BZY7JRTM.js} +2 -2
  7. package/dist/browser/dev/excalidraw-assets-dev/{image-VKDAL6BQ.js → image-CVN3YKRW.js} +2 -2
  8. package/dist/browser/dev/index.js +332 -76
  9. package/dist/browser/dev/index.js.map +4 -4
  10. package/dist/browser/prod/excalidraw-assets/{chunk-N2C5DK3B.js → chunk-VJAIK3AX.js} +15 -15
  11. package/dist/browser/prod/excalidraw-assets/{chunk-LIG3S5TN.js → chunk-YYO5DFUW.js} +3 -3
  12. package/dist/browser/prod/excalidraw-assets/{en-WFZVQ7I6.js → en-O2YCQM2W.js} +1 -1
  13. package/dist/browser/prod/excalidraw-assets/image-6FKY54X5.js +1 -0
  14. package/dist/browser/prod/index.js +16 -16
  15. package/dist/{prod/en-TDNWCAOT.json → dev/en-EY7E2L5O.json} +1 -0
  16. package/dist/dev/index.js +372 -77
  17. package/dist/dev/index.js.map +3 -3
  18. package/dist/excalidraw/data/library.d.ts +60 -8
  19. package/dist/excalidraw/data/library.js +302 -33
  20. package/dist/excalidraw/element/index.d.ts +8 -0
  21. package/dist/excalidraw/element/index.js +23 -0
  22. package/dist/excalidraw/element/textElement.d.ts +16 -1
  23. package/dist/excalidraw/element/textElement.js +10 -3
  24. package/dist/excalidraw/index.d.ts +2 -2
  25. package/dist/excalidraw/index.js +2 -2
  26. package/dist/excalidraw/locales/en.json +1 -0
  27. package/dist/excalidraw/queue.d.ts +9 -0
  28. package/dist/excalidraw/queue.js +27 -0
  29. package/dist/excalidraw/types.d.ts +6 -6
  30. package/dist/excalidraw/utility-types.d.ts +2 -0
  31. package/dist/excalidraw/utils.d.ts +3 -1
  32. package/dist/excalidraw/utils.js +6 -0
  33. package/dist/{dev/en-TDNWCAOT.json → prod/en-EY7E2L5O.json} +1 -0
  34. package/dist/prod/index.js +26 -26
  35. package/package.json +1 -1
  36. package/dist/browser/dev/excalidraw-assets-dev/chunk-5VWQDKDR.js.map +0 -7
  37. package/dist/browser/dev/excalidraw-assets-dev/chunk-IM4WTX2M.js.map +0 -7
  38. package/dist/browser/prod/excalidraw-assets/image-4AT7LYMR.js +0 -1
  39. /package/dist/browser/dev/excalidraw-assets-dev/{en-IOBA4CS2.js.map → en-BZY7JRTM.js.map} +0 -0
  40. /package/dist/browser/dev/excalidraw-assets-dev/{image-VKDAL6BQ.js.map → image-CVN3YKRW.js.map} +0 -0
@@ -216,6 +216,7 @@
216
216
  "failedToFetchImage": "Failed to fetch image.",
217
217
  "cannotResolveCollabServer": "Couldn't connect to the collab server. Please reload the page and try again.",
218
218
  "importLibraryError": "Couldn't load library",
219
+ "saveLibraryError": "Couldn't save library to storage. Please save your library to a file locally to make sure you don't lose changes.",
219
220
  "collabSaveFailed": "Couldn't save to the backend database. If problems persist, you should save your file locally to ensure you don't lose your work.",
220
221
  "collabSaveFailed_sizeExceeded": "Couldn't save to the backend database, the canvas seems to be too big. You should save the file locally to ensure you don't lose your work.",
221
222
  "imageToolNotSupported": "Images are disabled.",
package/dist/dev/index.js CHANGED
@@ -448,7 +448,7 @@ function getSvgPathFromStroke(points, closed = true) {
448
448
  }
449
449
  return result;
450
450
  }
451
- var mockDateTime, getDateTime, capitalizeString, isToolIcon, isInputLike, isInteractive, isWritableElement, getFontFamilyString, getFontString, debounce, throttleRAF, easeOut, easeOutInterpolate, easeToValuesRAF, chunk, distance, updateActiveTool, getShortcutKey, viewportCoordsToSceneCoords, sceneCoordsToViewportCoords, getGlobalCSSVariable, RS_LTR_CHARS, RS_RTL_CHARS, RE_RTL_CHECK, isRTL, tupleToCoors, muteFSAbortError, findIndex, findLastIndex, isTransparent, getNearestScrollableContainer, focusNearestParent, bytesToHexString, getUpdatedTimestamp, arrayToMap, arrayToMapWithIndex, isTestEnv, wrapEvent, updateObject, getFrame, isPromiseLike, queryFocusableElements, _defaultIsShallowComparatorFallback, isShallowEqual, composeEventHandlers, assertNever, memoize, isMemberOf, cloneJSON, isFiniteNumber, updateStable, average, normalizeEOL, toBrandedType;
451
+ var mockDateTime, getDateTime, capitalizeString, isToolIcon, isInputLike, isInteractive, isWritableElement, getFontFamilyString, getFontString, debounce, throttleRAF, easeOut, easeOutInterpolate, easeToValuesRAF, chunk, distance, updateActiveTool, getShortcutKey, viewportCoordsToSceneCoords, sceneCoordsToViewportCoords, getGlobalCSSVariable, RS_LTR_CHARS, RS_RTL_CHARS, RE_RTL_CHECK, isRTL, tupleToCoors, muteFSAbortError, findIndex, findLastIndex, isTransparent, resolvablePromise, getNearestScrollableContainer, focusNearestParent, preventUnload, bytesToHexString, getUpdatedTimestamp, arrayToMap, arrayToMapWithIndex, isTestEnv, wrapEvent, updateObject, getFrame, isPromiseLike, queryFocusableElements, _defaultIsShallowComparatorFallback, isShallowEqual, composeEventHandlers, assertNever, memoize, isMemberOf, cloneJSON, isFiniteNumber, updateStable, average, normalizeEOL, toBrandedType, promiseTry;
452
452
  var init_utils = __esm({
453
453
  "utils.ts"() {
454
454
  "use strict";
@@ -735,6 +735,17 @@ var init_utils = __esm({
735
735
  const isRRGGBBTransparent = color.length === 9 && color.substr(7, 2) === "00";
736
736
  return isRGBTransparent || isRRGGBBTransparent || color === COLOR_PALETTE.transparent;
737
737
  };
738
+ resolvablePromise = () => {
739
+ let resolve;
740
+ let reject;
741
+ const promise = new Promise((_resolve, _reject) => {
742
+ resolve = _resolve;
743
+ reject = _reject;
744
+ });
745
+ promise.resolve = resolve;
746
+ promise.reject = reject;
747
+ return promise;
748
+ };
738
749
  getNearestScrollableContainer = (element) => {
739
750
  let parent = element.parentElement;
740
751
  while (parent) {
@@ -760,6 +771,10 @@ var init_utils = __esm({
760
771
  parent = parent.parentElement;
761
772
  }
762
773
  };
774
+ preventUnload = (event) => {
775
+ event.preventDefault();
776
+ event.returnValue = "";
777
+ };
763
778
  bytesToHexString = (bytes) => {
764
779
  return Array.from(bytes).map((byte) => `0${byte.toString(16)}`.slice(-2)).join("");
765
780
  };
@@ -933,6 +948,11 @@ var init_utils = __esm({
933
948
  toBrandedType = (value) => {
934
949
  return value;
935
950
  };
951
+ promiseTry = async (fn, ...args) => {
952
+ return new Promise((resolve) => {
953
+ resolve(fn(...args));
954
+ });
955
+ };
936
956
  }
937
957
  });
938
958
 
@@ -5224,7 +5244,7 @@ var init_textElement = __esm({
5224
5244
  return fontSize * lineHeight;
5225
5245
  };
5226
5246
  getVerticalOffset = (fontFamily, fontSize, lineHeightPx) => {
5227
- const { unitsPerEm, ascender, descender } = FONT_METRICS[fontFamily];
5247
+ const { unitsPerEm, ascender, descender } = FONT_METRICS[fontFamily] || FONT_METRICS[FONT_FAMILY.Helvetica];
5228
5248
  const fontSizeEm = fontSize / unitsPerEm;
5229
5249
  const lineGap = lineHeightPx - fontSizeEm * ascender + fontSizeEm * descender;
5230
5250
  const verticalOffset = fontSizeEm * ascender + lineGap;
@@ -5618,6 +5638,11 @@ var init_textElement = __esm({
5618
5638
  unitsPerEm: 2048,
5619
5639
  ascender: 1977,
5620
5640
  descender: -480
5641
+ },
5642
+ [FONT_FAMILY.Assistant]: {
5643
+ unitsPerEm: 1e3,
5644
+ ascender: 1021,
5645
+ descender: -287
5621
5646
  }
5622
5647
  };
5623
5648
  getDefaultLineHeight = (fontFamily) => {
@@ -13096,7 +13121,7 @@ var init_showSelectedShapeActions = __esm({
13096
13121
  });
13097
13122
 
13098
13123
  // element/index.ts
13099
- var getSceneVersion, getVisibleElements, getNonDeletedElements3, isNonDeletedElement, _clearElements, clearElementsForDatabase, clearElementsForExport;
13124
+ var getSceneVersion, hashElementsVersion, hashString, getVisibleElements, getNonDeletedElements3, isNonDeletedElement, _clearElements, clearElementsForDatabase, clearElementsForExport;
13100
13125
  var init_element = __esm({
13101
13126
  "element/index.ts"() {
13102
13127
  "use strict";
@@ -13115,6 +13140,21 @@ var init_element = __esm({
13115
13140
  init_sizeHelpers();
13116
13141
  init_showSelectedShapeActions();
13117
13142
  getSceneVersion = (elements) => elements.reduce((acc, el) => acc + el.version, 0);
13143
+ hashElementsVersion = (elements) => {
13144
+ let hash = 5381;
13145
+ for (let i = 0; i < elements.length; i++) {
13146
+ hash = (hash << 5) + hash + elements[i].versionNonce;
13147
+ }
13148
+ return hash >>> 0;
13149
+ };
13150
+ hashString = (s) => {
13151
+ let hash = 5381;
13152
+ for (let i = 0; i < s.length; i++) {
13153
+ const char = s.charCodeAt(i);
13154
+ hash = (hash << 5) + hash + char;
13155
+ }
13156
+ return hash >>> 0;
13157
+ };
13118
13158
  getVisibleElements = (elements) => elements.filter(
13119
13159
  (el) => !el.isDeleted && !isInvisiblySmallElement(el)
13120
13160
  );
@@ -13385,7 +13425,7 @@ init_define_import_meta_env();
13385
13425
 
13386
13426
  // i18n.ts
13387
13427
  init_define_import_meta_env();
13388
- import fallbackLangData from "./en-TDNWCAOT.json";
13428
+ import fallbackLangData from "./en-EY7E2L5O.json";
13389
13429
  import percentages from "./percentages-UCQDHIQF.json";
13390
13430
 
13391
13431
  // jotai.ts
@@ -13409,7 +13449,7 @@ var globImport_locales_json = __glob({
13409
13449
  "./locales/da-DK.json": () => import("./da-DK-WBEQB3CJ.json"),
13410
13450
  "./locales/de-DE.json": () => import("./de-DE-VEIMCP7R.json"),
13411
13451
  "./locales/el-GR.json": () => import("./el-GR-TKRKG5GQ.json"),
13412
- "./locales/en.json": () => import("./en-TDNWCAOT.json"),
13452
+ "./locales/en.json": () => import("./en-EY7E2L5O.json"),
13413
13453
  "./locales/es-ES.json": () => import("./es-ES-TOLWEZNW.json"),
13414
13454
  "./locales/eu-ES.json": () => import("./eu-ES-7CDRJQWJ.json"),
13415
13455
  "./locales/fa-IR.json": () => import("./fa-IR-527E2XGU.json"),
@@ -22878,7 +22918,79 @@ var useLibraryCache = () => {
22878
22918
 
22879
22919
  // data/library.ts
22880
22920
  init_utils();
22881
- var libraryItemsAtom = atom5({ status: "loaded", isInitialized: true, libraryItems: [] });
22921
+
22922
+ // emitter.ts
22923
+ init_define_import_meta_env();
22924
+ var Emitter = class {
22925
+ subscribers = [];
22926
+ /**
22927
+ * Attaches subscriber
22928
+ *
22929
+ * @returns unsubscribe function
22930
+ */
22931
+ on(...handlers) {
22932
+ const _handlers = handlers.flat().filter((item) => typeof item === "function");
22933
+ this.subscribers.push(..._handlers);
22934
+ return () => this.off(_handlers);
22935
+ }
22936
+ once(...handlers) {
22937
+ const _handlers = handlers.flat().filter((item) => typeof item === "function");
22938
+ _handlers.push(() => detach());
22939
+ const detach = this.on(..._handlers);
22940
+ return detach;
22941
+ }
22942
+ off(...handlers) {
22943
+ const _handlers = handlers.flat();
22944
+ this.subscribers = this.subscribers.filter(
22945
+ (handler) => !_handlers.includes(handler)
22946
+ );
22947
+ }
22948
+ trigger(...payload) {
22949
+ for (const handler of this.subscribers) {
22950
+ handler(...payload);
22951
+ }
22952
+ return this;
22953
+ }
22954
+ clear() {
22955
+ this.subscribers = [];
22956
+ }
22957
+ };
22958
+
22959
+ // queue.ts
22960
+ init_define_import_meta_env();
22961
+ init_utils();
22962
+ var Queue = class {
22963
+ jobs = [];
22964
+ running = false;
22965
+ tick() {
22966
+ if (this.running) {
22967
+ return;
22968
+ }
22969
+ const job = this.jobs.shift();
22970
+ if (job) {
22971
+ this.running = true;
22972
+ job.promise.resolve(
22973
+ promiseTry(job.jobFactory, ...job.args).finally(() => {
22974
+ this.running = false;
22975
+ this.tick();
22976
+ })
22977
+ );
22978
+ } else {
22979
+ this.running = false;
22980
+ }
22981
+ }
22982
+ push(jobFactory, ...args) {
22983
+ const promise = resolvablePromise();
22984
+ this.jobs.push({ jobFactory, promise, args });
22985
+ this.tick();
22986
+ return promise;
22987
+ }
22988
+ };
22989
+
22990
+ // data/library.ts
22991
+ init_element();
22992
+ var onLibraryUpdateEmitter = new Emitter();
22993
+ var libraryItemsAtom = atom5({ status: "loaded", isInitialized: false, libraryItems: [] });
22882
22994
  var cloneLibraryItems = (libraryItems) => cloneJSON(libraryItems);
22883
22995
  var isUniqueItem = (existingLibraryItems, targetLibraryItem) => {
22884
22996
  return !existingLibraryItems.find((libraryItem) => {
@@ -22899,12 +23011,30 @@ var mergeLibraryItems = (localItems, otherItems) => {
22899
23011
  }
22900
23012
  return [...newItems, ...localItems];
22901
23013
  };
23014
+ var createLibraryUpdate = (prevLibraryItems, nextLibraryItems) => {
23015
+ const nextItemsMap = arrayToMap(nextLibraryItems);
23016
+ const update = {
23017
+ deletedItems: /* @__PURE__ */ new Map(),
23018
+ addedItems: /* @__PURE__ */ new Map()
23019
+ };
23020
+ for (const item of prevLibraryItems) {
23021
+ if (!nextItemsMap.has(item.id)) {
23022
+ update.deletedItems.set(item.id, item);
23023
+ }
23024
+ }
23025
+ const prevItemsMap = arrayToMap(prevLibraryItems);
23026
+ for (const item of nextLibraryItems) {
23027
+ if (!prevItemsMap.has(item.id)) {
23028
+ update.addedItems.set(item.id, item);
23029
+ }
23030
+ }
23031
+ return update;
23032
+ };
22902
23033
  var Library = class {
22903
23034
  /** latest libraryItems */
22904
- lastLibraryItems = [];
22905
- /** indicates whether library is initialized with library items (has gone
22906
- * though at least one update) */
22907
- isInitialized = false;
23035
+ currLibraryItems = [];
23036
+ /** snapshot of library items since last onLibraryChange call */
23037
+ prevLibraryItems = cloneLibraryItems(this.currLibraryItems);
22908
23038
  app;
22909
23039
  constructor(app) {
22910
23040
  this.app = app;
@@ -22915,21 +23045,25 @@ var Library = class {
22915
23045
  };
22916
23046
  notifyListeners = () => {
22917
23047
  if (this.updateQueue.length > 0) {
22918
- jotaiStore.set(libraryItemsAtom, {
23048
+ jotaiStore.set(libraryItemsAtom, (s) => ({
22919
23049
  status: "loading",
22920
- libraryItems: this.lastLibraryItems,
22921
- isInitialized: this.isInitialized
22922
- });
23050
+ libraryItems: this.currLibraryItems,
23051
+ isInitialized: s.isInitialized
23052
+ }));
22923
23053
  } else {
22924
- this.isInitialized = true;
22925
23054
  jotaiStore.set(libraryItemsAtom, {
22926
23055
  status: "loaded",
22927
- libraryItems: this.lastLibraryItems,
22928
- isInitialized: this.isInitialized
23056
+ libraryItems: this.currLibraryItems,
23057
+ isInitialized: true
22929
23058
  });
22930
23059
  try {
22931
- this.app.props.onLibraryChange?.(
22932
- cloneLibraryItems(this.lastLibraryItems)
23060
+ const prevLibraryItems = this.prevLibraryItems;
23061
+ this.prevLibraryItems = cloneLibraryItems(this.currLibraryItems);
23062
+ const nextLibraryItems = cloneLibraryItems(this.currLibraryItems);
23063
+ this.app.props.onLibraryChange?.(nextLibraryItems);
23064
+ onLibraryUpdateEmitter.trigger(
23065
+ createLibraryUpdate(prevLibraryItems, nextLibraryItems),
23066
+ nextLibraryItems
22933
23067
  );
22934
23068
  } catch (error) {
22935
23069
  console.error(error);
@@ -22938,9 +23072,8 @@ var Library = class {
22938
23072
  };
22939
23073
  /** call on excalidraw instance unmount */
22940
23074
  destroy = () => {
22941
- this.isInitialized = false;
22942
23075
  this.updateQueue = [];
22943
- this.lastLibraryItems = [];
23076
+ this.currLibraryItems = [];
22944
23077
  jotaiStore.set(libraryItemSvgsCache, /* @__PURE__ */ new Map());
22945
23078
  };
22946
23079
  resetLibrary = () => {
@@ -22952,14 +23085,14 @@ var Library = class {
22952
23085
  getLatestLibrary = () => {
22953
23086
  return new Promise(async (resolve) => {
22954
23087
  try {
22955
- const libraryItems = await (this.getLastUpdateTask() || this.lastLibraryItems);
23088
+ const libraryItems = await (this.getLastUpdateTask() || this.currLibraryItems);
22956
23089
  if (this.updateQueue.length > 0) {
22957
23090
  resolve(this.getLatestLibrary());
22958
23091
  } else {
22959
23092
  resolve(cloneLibraryItems(libraryItems));
22960
23093
  }
22961
23094
  } catch (error) {
22962
- return resolve(this.lastLibraryItems);
23095
+ return resolve(this.currLibraryItems);
22963
23096
  }
22964
23097
  });
22965
23098
  };
@@ -22981,7 +23114,7 @@ var Library = class {
22981
23114
  return this.setLibrary(() => {
22982
23115
  return new Promise(async (resolve, reject) => {
22983
23116
  try {
22984
- const source = await (typeof libraryItems === "function" && !(libraryItems instanceof Blob) ? libraryItems(this.lastLibraryItems) : libraryItems);
23117
+ const source = await (typeof libraryItems === "function" && !(libraryItems instanceof Blob) ? libraryItems(this.currLibraryItems) : libraryItems);
22985
23118
  let nextItems;
22986
23119
  if (source instanceof Blob) {
22987
23120
  nextItems = await loadLibraryFromBlob(source, defaultStatus);
@@ -22997,7 +23130,7 @@ var Library = class {
22997
23130
  this.app.focusContainer();
22998
23131
  }
22999
23132
  if (merge) {
23000
- resolve(mergeLibraryItems(this.lastLibraryItems, nextItems));
23133
+ resolve(mergeLibraryItems(this.currLibraryItems, nextItems));
23001
23134
  } else {
23002
23135
  resolve(nextItems);
23003
23136
  }
@@ -23015,17 +23148,17 @@ var Library = class {
23015
23148
  try {
23016
23149
  await this.getLastUpdateTask();
23017
23150
  if (typeof libraryItems === "function") {
23018
- libraryItems = libraryItems(this.lastLibraryItems);
23151
+ libraryItems = libraryItems(this.currLibraryItems);
23019
23152
  }
23020
- this.lastLibraryItems = cloneLibraryItems(await libraryItems);
23021
- resolve(this.lastLibraryItems);
23153
+ this.currLibraryItems = cloneLibraryItems(await libraryItems);
23154
+ resolve(this.currLibraryItems);
23022
23155
  } catch (error) {
23023
23156
  reject(error);
23024
23157
  }
23025
23158
  }).catch((error) => {
23026
23159
  if (error.name === "AbortError") {
23027
23160
  console.warn("Library update aborted by user");
23028
- return this.lastLibraryItems;
23161
+ return this.currLibraryItems;
23029
23162
  }
23030
23163
  throw error;
23031
23164
  }).finally(() => {
@@ -23117,15 +23250,86 @@ var parseLibraryTokensFromUrl = () => {
23117
23250
  const idToken = libraryUrl ? new URLSearchParams(window.location.hash.slice(1)).get("token") : null;
23118
23251
  return libraryUrl ? { libraryUrl, idToken } : null;
23119
23252
  };
23120
- var useHandleLibrary = ({
23121
- excalidrawAPI,
23122
- getInitialLibraryItems
23123
- }) => {
23124
- const getInitialLibraryRef = useRef11(getInitialLibraryItems);
23253
+ var AdapterTransaction = class _AdapterTransaction {
23254
+ static queue = new Queue();
23255
+ static async getLibraryItems(adapter, source, _queue = true) {
23256
+ const task = () => new Promise(async (resolve, reject) => {
23257
+ try {
23258
+ const data = await adapter.load({ source });
23259
+ resolve(restoreLibraryItems(data?.libraryItems || [], "published"));
23260
+ } catch (error) {
23261
+ reject(error);
23262
+ }
23263
+ });
23264
+ if (_queue) {
23265
+ return _AdapterTransaction.queue.push(task);
23266
+ }
23267
+ return task();
23268
+ }
23269
+ static run = async (adapter, fn) => {
23270
+ const transaction = new _AdapterTransaction(adapter);
23271
+ return _AdapterTransaction.queue.push(() => fn(transaction));
23272
+ };
23273
+ // ------------------
23274
+ adapter;
23275
+ constructor(adapter) {
23276
+ this.adapter = adapter;
23277
+ }
23278
+ getLibraryItems(source) {
23279
+ return _AdapterTransaction.getLibraryItems(this.adapter, source, false);
23280
+ }
23281
+ };
23282
+ var lastSavedLibraryItemsHash = 0;
23283
+ var librarySaveCounter = 0;
23284
+ var getLibraryItemsHash = (items) => {
23285
+ return hashString(
23286
+ items.map((item) => {
23287
+ return `${item.id}:${hashElementsVersion(item.elements)}`;
23288
+ }).sort().join()
23289
+ );
23290
+ };
23291
+ var persistLibraryUpdate = async (adapter, update) => {
23292
+ try {
23293
+ librarySaveCounter++;
23294
+ return await AdapterTransaction.run(adapter, async (transaction) => {
23295
+ const nextLibraryItemsMap = arrayToMap(
23296
+ await transaction.getLibraryItems("save")
23297
+ );
23298
+ for (const [id] of update.deletedItems) {
23299
+ nextLibraryItemsMap.delete(id);
23300
+ }
23301
+ const addedItems = [];
23302
+ for (const [id, item] of update.addedItems) {
23303
+ if (nextLibraryItemsMap.has(id)) {
23304
+ nextLibraryItemsMap.set(id, item);
23305
+ } else {
23306
+ addedItems.push(item);
23307
+ }
23308
+ }
23309
+ const nextLibraryItems = addedItems.concat(
23310
+ Array.from(nextLibraryItemsMap.values())
23311
+ );
23312
+ const version = getLibraryItemsHash(nextLibraryItems);
23313
+ if (version !== lastSavedLibraryItemsHash) {
23314
+ await adapter.save({ libraryItems: nextLibraryItems });
23315
+ }
23316
+ lastSavedLibraryItemsHash = version;
23317
+ return nextLibraryItems;
23318
+ });
23319
+ } finally {
23320
+ librarySaveCounter--;
23321
+ }
23322
+ };
23323
+ var useHandleLibrary = (opts) => {
23324
+ const { excalidrawAPI } = opts;
23325
+ const optsRef = useRef11(opts);
23326
+ optsRef.current = opts;
23327
+ const isLibraryLoadedRef = useRef11(false);
23125
23328
  useEffect15(() => {
23126
23329
  if (!excalidrawAPI) {
23127
23330
  return;
23128
23331
  }
23332
+ isLibraryLoadedRef.current = false;
23129
23333
  const importLibraryFromURL = async ({
23130
23334
  libraryUrl,
23131
23335
  idToken
@@ -23176,20 +23380,145 @@ var useHandleLibrary = ({
23176
23380
  importLibraryFromURL(libraryUrlTokens2);
23177
23381
  }
23178
23382
  };
23179
- if (getInitialLibraryRef.current) {
23180
- excalidrawAPI.updateLibrary({
23181
- libraryItems: getInitialLibraryRef.current()
23182
- });
23183
- }
23184
23383
  const libraryUrlTokens = parseLibraryTokensFromUrl();
23185
23384
  if (libraryUrlTokens) {
23186
23385
  importLibraryFromURL(libraryUrlTokens);
23187
23386
  }
23387
+ if ("getInitialLibraryItems" in optsRef.current && optsRef.current.getInitialLibraryItems) {
23388
+ console.warn(
23389
+ "useHandleLibrar `opts.getInitialLibraryItems` is deprecated. Use `opts.adapter` instead."
23390
+ );
23391
+ Promise.resolve(optsRef.current.getInitialLibraryItems()).then((libraryItems) => {
23392
+ excalidrawAPI.updateLibrary({
23393
+ libraryItems,
23394
+ // merge with current library items because we may have already
23395
+ // populated it (e.g. by installing 3rd party library which can
23396
+ // happen before the DB data is loaded)
23397
+ merge: true
23398
+ });
23399
+ }).catch((error) => {
23400
+ console.error(
23401
+ `UseHandeLibrary getInitialLibraryItems failed: ${error?.message}`
23402
+ );
23403
+ });
23404
+ }
23405
+ if ("adapter" in optsRef.current && optsRef.current.adapter) {
23406
+ const adapter = optsRef.current.adapter;
23407
+ const migrationAdapter = optsRef.current.migrationAdapter;
23408
+ const initDataPromise = resolvablePromise();
23409
+ if (migrationAdapter) {
23410
+ initDataPromise.resolve(
23411
+ promiseTry(migrationAdapter.load).then(async (libraryData) => {
23412
+ let restoredData = null;
23413
+ try {
23414
+ if (!libraryData) {
23415
+ return AdapterTransaction.getLibraryItems(adapter, "load");
23416
+ }
23417
+ restoredData = restoreLibraryItems(
23418
+ libraryData.libraryItems || [],
23419
+ "published"
23420
+ );
23421
+ const nextItems = await persistLibraryUpdate(
23422
+ adapter,
23423
+ createLibraryUpdate([], restoredData)
23424
+ );
23425
+ try {
23426
+ await migrationAdapter.clear();
23427
+ } catch (error) {
23428
+ console.error(
23429
+ `couldn't delete legacy library data: ${error.message}`
23430
+ );
23431
+ }
23432
+ return nextItems;
23433
+ } catch (error) {
23434
+ console.error(
23435
+ `couldn't migrate legacy library data: ${error.message}`
23436
+ );
23437
+ return restoredData;
23438
+ }
23439
+ }).catch((error) => {
23440
+ console.error(`error during library migration: ${error.message}`);
23441
+ return AdapterTransaction.getLibraryItems(adapter, "load");
23442
+ })
23443
+ );
23444
+ } else {
23445
+ initDataPromise.resolve(
23446
+ promiseTry(AdapterTransaction.getLibraryItems, adapter, "load")
23447
+ );
23448
+ }
23449
+ excalidrawAPI.updateLibrary({
23450
+ libraryItems: initDataPromise.then((libraryItems) => {
23451
+ const _libraryItems = libraryItems || [];
23452
+ lastSavedLibraryItemsHash = getLibraryItemsHash(_libraryItems);
23453
+ return _libraryItems;
23454
+ }),
23455
+ // merge with current library items because we may have already
23456
+ // populated it (e.g. by installing 3rd party library which can
23457
+ // happen before the DB data is loaded)
23458
+ merge: true
23459
+ }).finally(() => {
23460
+ isLibraryLoadedRef.current = true;
23461
+ });
23462
+ }
23188
23463
  window.addEventListener("hashchange" /* HASHCHANGE */, onHashChange);
23189
23464
  return () => {
23190
23465
  window.removeEventListener("hashchange" /* HASHCHANGE */, onHashChange);
23191
23466
  };
23192
- }, [excalidrawAPI]);
23467
+ }, [
23468
+ // important this useEffect only depends on excalidrawAPI so it only reruns
23469
+ // on editor remounts (the excalidrawAPI changes)
23470
+ excalidrawAPI
23471
+ ]);
23472
+ useEffect15(
23473
+ () => {
23474
+ const unsubOnLibraryUpdate = onLibraryUpdateEmitter.on(
23475
+ async (update, nextLibraryItems) => {
23476
+ const isLoaded = isLibraryLoadedRef.current;
23477
+ const adapter = "adapter" in optsRef.current && optsRef.current.adapter || null;
23478
+ try {
23479
+ if (adapter) {
23480
+ if (
23481
+ // if nextLibraryItems hash identical to previously saved hash,
23482
+ // exit early, even if actual upstream state ends up being
23483
+ // different (e.g. has more data than we have locally), as it'd
23484
+ // be low-impact scenario.
23485
+ lastSavedLibraryItemsHash !== getLibraryItemsHash(nextLibraryItems)
23486
+ ) {
23487
+ await persistLibraryUpdate(adapter, update);
23488
+ }
23489
+ }
23490
+ } catch (error) {
23491
+ console.error(
23492
+ `couldn't persist library update: ${error.message}`,
23493
+ update
23494
+ );
23495
+ if (isLoaded && optsRef.current.excalidrawAPI) {
23496
+ optsRef.current.excalidrawAPI.updateScene({
23497
+ appState: {
23498
+ errorMessage: t("errors.saveLibraryError")
23499
+ }
23500
+ });
23501
+ }
23502
+ }
23503
+ }
23504
+ );
23505
+ const onUnload = (event) => {
23506
+ if (librarySaveCounter) {
23507
+ preventUnload(event);
23508
+ }
23509
+ };
23510
+ window.addEventListener("beforeunload" /* BEFORE_UNLOAD */, onUnload);
23511
+ return () => {
23512
+ window.removeEventListener("beforeunload" /* BEFORE_UNLOAD */, onUnload);
23513
+ unsubOnLibraryUpdate();
23514
+ lastSavedLibraryItemsHash = 0;
23515
+ librarySaveCounter = 0;
23516
+ };
23517
+ },
23518
+ [
23519
+ // this effect must not have any deps so it doesn't rerun
23520
+ ]
23521
+ );
23193
23522
  };
23194
23523
 
23195
23524
  // components/App.tsx
@@ -33785,43 +34114,6 @@ var SVGLayer = ({ trails }) => {
33785
34114
  // components/App.tsx
33786
34115
  init_cursor();
33787
34116
 
33788
- // emitter.ts
33789
- init_define_import_meta_env();
33790
- var Emitter = class {
33791
- subscribers = [];
33792
- /**
33793
- * Attaches subscriber
33794
- *
33795
- * @returns unsubscribe function
33796
- */
33797
- on(...handlers) {
33798
- const _handlers = handlers.flat().filter((item) => typeof item === "function");
33799
- this.subscribers.push(..._handlers);
33800
- return () => this.off(_handlers);
33801
- }
33802
- once(...handlers) {
33803
- const _handlers = handlers.flat().filter((item) => typeof item === "function");
33804
- _handlers.push(() => detach());
33805
- const detach = this.on(..._handlers);
33806
- return detach;
33807
- }
33808
- off(...handlers) {
33809
- const _handlers = handlers.flat();
33810
- this.subscribers = this.subscribers.filter(
33811
- (handler) => !_handlers.includes(handler)
33812
- );
33813
- }
33814
- trigger(...payload) {
33815
- for (const handler of this.subscribers) {
33816
- handler(...payload);
33817
- }
33818
- return this;
33819
- }
33820
- clear() {
33821
- this.subscribers = [];
33822
- }
33823
- };
33824
-
33825
34117
  // element/ElementCanvasButtons.tsx
33826
34118
  init_define_import_meta_env();
33827
34119
  init_utils();
@@ -42142,9 +42434,12 @@ export {
42142
42434
  exportToSvg2 as exportToSvg,
42143
42435
  getCommonBounds,
42144
42436
  getFreeDrawSvgPath,
42437
+ getLibraryItemsHash,
42145
42438
  getNonDeletedElements3 as getNonDeletedElements,
42146
42439
  getSceneVersion,
42147
42440
  getVisibleSceneBounds,
42441
+ hashElementsVersion,
42442
+ hashString,
42148
42443
  isElementInsideBBox,
42149
42444
  isInvisiblySmallElement,
42150
42445
  isLinearElement,