@excalidraw/excalidraw 0.17.1-7500-ac247a0 → 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.
- package/CHANGELOG.md +56 -2
- package/dist/browser/dev/excalidraw-assets-dev/{chunk-2W5GQUR4.js → chunk-6NMK7JTV.js} +13 -6
- package/dist/browser/dev/excalidraw-assets-dev/chunk-6NMK7JTV.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/chunk-CX3RATXT.js +20324 -0
- package/dist/browser/dev/excalidraw-assets-dev/chunk-CX3RATXT.js.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js → en-BZY7JRTM.js} +4 -2
- package/dist/browser/dev/excalidraw-assets-dev/{image-5TVMINCA.js → image-CVN3YKRW.js} +2 -4
- package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css +6 -0
- package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css.map +7 -0
- package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js +161 -0
- package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js.map +7 -0
- package/dist/browser/dev/index.css +189 -129
- package/dist/browser/dev/index.css.map +3 -3
- package/dist/browser/dev/index.js +34964 -37
- package/dist/browser/dev/index.js.map +4 -4
- package/dist/browser/prod/excalidraw-assets/chunk-VJAIK3AX.js +55 -0
- package/dist/browser/prod/excalidraw-assets/chunk-YYO5DFUW.js +11 -0
- package/dist/browser/prod/excalidraw-assets/en-O2YCQM2W.js +1 -0
- package/dist/browser/prod/excalidraw-assets/image-6FKY54X5.js +1 -0
- package/dist/browser/prod/excalidraw-assets/image-X66R2EM5.css +1 -0
- package/dist/browser/prod/excalidraw-assets/roundRect-2ACQK4DA.js +1 -0
- package/dist/browser/prod/index.css +1 -1
- package/dist/browser/prod/index.js +203 -1
- package/dist/{prod/en-RLIAOBCI.json → dev/en-EY7E2L5O.json} +10 -5
- package/dist/dev/index.css +189 -129
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +38702 -39409
- package/dist/dev/index.js.map +4 -4
- package/dist/excalidraw/actions/actionAddToLibrary.d.ts +15 -15
- package/dist/excalidraw/actions/actionAlign.d.ts +6 -6
- package/dist/excalidraw/actions/actionAlign.js +2 -1
- package/dist/excalidraw/actions/actionBoundText.d.ts +10 -10
- package/dist/excalidraw/actions/actionBoundText.js +8 -8
- package/dist/excalidraw/actions/actionCanvas.d.ts +58 -58
- package/dist/excalidraw/actions/actionClipboard.d.ts +34 -34
- package/dist/excalidraw/actions/actionClipboard.js +9 -2
- package/dist/excalidraw/actions/actionDeleteSelected.d.ts +15 -15
- package/dist/excalidraw/actions/actionDeleteSelected.js +3 -2
- package/dist/excalidraw/actions/actionDistribute.d.ts +2 -2
- package/dist/excalidraw/actions/actionDistribute.js +1 -1
- package/dist/excalidraw/actions/actionDuplicateSelection.d.ts +1 -1
- package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -3
- package/dist/excalidraw/actions/actionElementLock.d.ts +10 -10
- package/dist/excalidraw/actions/actionExport.d.ts +43 -43
- package/dist/excalidraw/actions/actionExport.js +4 -4
- package/dist/excalidraw/actions/actionFinalize.d.ts +9 -9
- package/dist/excalidraw/actions/actionFinalize.js +7 -6
- package/dist/excalidraw/actions/actionFlip.d.ts +2 -2
- package/dist/excalidraw/actions/actionFlip.js +11 -11
- package/dist/excalidraw/actions/actionFrame.d.ts +16 -16
- package/dist/excalidraw/actions/actionFrame.js +1 -1
- package/dist/excalidraw/actions/actionGroup.d.ts +10 -10
- package/dist/excalidraw/actions/actionGroup.js +3 -2
- package/dist/excalidraw/actions/actionLinearEditor.d.ts +5 -5
- package/dist/excalidraw/actions/actionLinearEditor.js +1 -1
- package/dist/excalidraw/{element/Hyperlink.d.ts → actions/actionLink.d.ts} +29 -51
- package/dist/excalidraw/actions/actionLink.js +40 -0
- package/dist/excalidraw/actions/actionMenu.d.ts +13 -13
- package/dist/excalidraw/actions/actionNavigate.d.ts +10 -10
- package/dist/excalidraw/actions/actionNavigate.js +1 -1
- package/dist/excalidraw/actions/actionProperties.d.ts +77 -77
- package/dist/excalidraw/actions/actionProperties.js +32 -27
- package/dist/excalidraw/actions/actionSelectAll.d.ts +5 -5
- package/dist/excalidraw/actions/actionSelectAll.js +1 -1
- package/dist/excalidraw/actions/actionStyles.d.ts +7 -7
- package/dist/excalidraw/actions/actionStyles.js +4 -4
- package/dist/excalidraw/actions/actionToggleGridMode.d.ts +5 -5
- package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +5 -5
- package/dist/excalidraw/actions/actionToggleStats.d.ts +5 -5
- package/dist/excalidraw/actions/actionToggleViewMode.d.ts +5 -5
- package/dist/excalidraw/actions/actionToggleZenMode.d.ts +5 -5
- package/dist/excalidraw/actions/index.d.ts +1 -1
- package/dist/excalidraw/actions/index.js +1 -1
- package/dist/excalidraw/actions/manager.js +2 -1
- package/dist/excalidraw/align.d.ts +2 -2
- package/dist/excalidraw/align.js +2 -2
- package/dist/excalidraw/animated-trail.d.ts +33 -0
- package/dist/excalidraw/animated-trail.js +96 -0
- package/dist/excalidraw/animation-frame-handler.d.ts +16 -0
- package/dist/excalidraw/animation-frame-handler.js +55 -0
- package/dist/excalidraw/appState.d.ts +1 -1
- package/dist/excalidraw/appState.js +1 -3
- package/dist/excalidraw/clipboard.js +5 -5
- package/dist/excalidraw/components/Actions.d.ts +3 -3
- package/dist/excalidraw/components/Actions.js +18 -7
- package/dist/excalidraw/components/App.d.ts +23 -16
- package/dist/excalidraw/components/App.js +387 -272
- package/dist/excalidraw/components/Button.d.ts +1 -1
- package/dist/excalidraw/components/FilledButton.d.ts +2 -2
- package/dist/excalidraw/components/FilledButton.js +27 -3
- package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
- package/dist/excalidraw/components/ImageExportDialog.d.ts +2 -1
- package/dist/excalidraw/components/ImageExportDialog.js +17 -13
- package/dist/excalidraw/components/JSONExportDialog.js +1 -1
- package/dist/excalidraw/components/{LaserTool/LaserPointerButton.d.ts → LaserPointerButton.d.ts} +1 -1
- package/dist/excalidraw/components/{LaserTool/LaserPointerButton.js → LaserPointerButton.js} +2 -2
- package/dist/excalidraw/components/LayerUI.js +3 -3
- package/dist/excalidraw/components/MobileMenu.js +1 -1
- package/dist/excalidraw/components/ProjectName.d.ts +0 -1
- package/dist/excalidraw/components/ProjectName.js +1 -1
- package/dist/excalidraw/components/PublishLibrary.js +1 -1
- package/dist/excalidraw/components/SVGLayer.d.ts +8 -0
- package/dist/excalidraw/components/SVGLayer.js +20 -0
- package/dist/excalidraw/components/ShareableLinkDialog.js +10 -10
- package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
- package/dist/excalidraw/components/Stack.d.ts +2 -2
- package/dist/excalidraw/components/TTDDialog/common.js +10 -1
- package/dist/excalidraw/components/TextField.d.ts +5 -2
- package/dist/excalidraw/components/TextField.js +6 -3
- package/dist/excalidraw/components/Toast.d.ts +3 -2
- package/dist/excalidraw/components/Toast.js +2 -2
- package/dist/excalidraw/components/ToolButton.js +2 -1
- package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +2 -2
- package/dist/excalidraw/components/canvases/InteractiveCanvas.js +6 -5
- package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +4 -3
- package/dist/excalidraw/components/canvases/StaticCanvas.js +7 -5
- package/dist/excalidraw/components/dropdownMenu/DropdownMenuContent.js +22 -2
- package/dist/excalidraw/components/hyperlink/Hyperlink.d.ts +19 -0
- package/dist/excalidraw/{element → components/hyperlink}/Hyperlink.js +40 -115
- package/dist/excalidraw/components/hyperlink/helpers.d.ts +7 -0
- package/dist/excalidraw/components/hyperlink/helpers.js +49 -0
- package/dist/excalidraw/components/icons.d.ts +2 -1
- package/dist/excalidraw/components/icons.js +2 -1
- package/dist/excalidraw/components/live-collaboration/LiveCollaborationTrigger.js +3 -2
- package/dist/excalidraw/components/main-menu/DefaultItems.js +5 -2
- package/dist/excalidraw/constants.d.ts +6 -0
- package/dist/excalidraw/constants.js +6 -0
- package/dist/excalidraw/data/blob.js +13 -14
- package/dist/excalidraw/data/filesystem.d.ts +1 -1
- package/dist/excalidraw/data/index.d.ts +2 -1
- package/dist/excalidraw/data/index.js +20 -16
- package/dist/excalidraw/data/json.d.ts +1 -1
- package/dist/excalidraw/data/json.js +5 -3
- package/dist/excalidraw/data/library.d.ts +60 -8
- package/dist/excalidraw/data/library.js +302 -33
- package/dist/excalidraw/data/resave.d.ts +1 -1
- package/dist/excalidraw/data/resave.js +2 -2
- package/dist/excalidraw/data/restore.js +8 -13
- package/dist/excalidraw/data/transform.js +13 -9
- package/dist/excalidraw/distribute.d.ts +2 -2
- package/dist/excalidraw/distribute.js +2 -2
- package/dist/excalidraw/element/ElementCanvasButtons.d.ts +3 -2
- package/dist/excalidraw/element/ElementCanvasButtons.js +4 -4
- package/dist/excalidraw/element/binding.d.ts +9 -9
- package/dist/excalidraw/element/binding.js +61 -59
- package/dist/excalidraw/element/bounds.d.ts +5 -5
- package/dist/excalidraw/element/bounds.js +29 -32
- package/dist/excalidraw/element/collision.d.ts +11 -11
- package/dist/excalidraw/element/collision.js +49 -46
- package/dist/excalidraw/element/containerCache.d.ts +11 -0
- package/dist/excalidraw/element/containerCache.js +14 -0
- package/dist/excalidraw/element/dragElements.js +10 -19
- package/dist/excalidraw/element/embeddable.d.ts +12 -13
- package/dist/excalidraw/element/embeddable.js +17 -27
- package/dist/excalidraw/element/image.js +1 -2
- package/dist/excalidraw/element/index.d.ts +8 -1
- package/dist/excalidraw/element/index.js +23 -1
- package/dist/excalidraw/element/linearElementEditor.d.ts +36 -36
- package/dist/excalidraw/element/linearElementEditor.js +79 -80
- package/dist/excalidraw/element/newElement.d.ts +4 -6
- package/dist/excalidraw/element/newElement.js +11 -16
- package/dist/excalidraw/element/resizeElements.d.ts +6 -6
- package/dist/excalidraw/element/resizeElements.js +40 -46
- package/dist/excalidraw/element/resizeTest.d.ts +3 -3
- package/dist/excalidraw/element/resizeTest.js +4 -4
- package/dist/excalidraw/element/sizeHelpers.d.ts +2 -2
- package/dist/excalidraw/element/sizeHelpers.js +2 -2
- package/dist/excalidraw/element/textElement.d.ts +34 -21
- package/dist/excalidraw/element/textElement.js +87 -111
- package/dist/excalidraw/element/textWysiwyg.d.ts +1 -6
- package/dist/excalidraw/element/textWysiwyg.js +15 -37
- package/dist/excalidraw/element/transformHandles.d.ts +4 -4
- package/dist/excalidraw/element/transformHandles.js +6 -6
- package/dist/excalidraw/element/typeChecks.js +4 -1
- package/dist/excalidraw/element/types.d.ts +24 -11
- package/dist/excalidraw/frame.d.ts +26 -20
- package/dist/excalidraw/frame.js +157 -84
- package/dist/excalidraw/groups.d.ts +3 -3
- package/dist/excalidraw/groups.js +11 -3
- package/dist/excalidraw/history.d.ts +1 -1
- package/dist/excalidraw/hooks/useLibraryItemSvg.js +1 -1
- package/dist/excalidraw/index.d.ts +9 -10
- package/dist/excalidraw/index.js +16 -12
- package/dist/excalidraw/laser-trails.d.ts +19 -0
- package/dist/excalidraw/laser-trails.js +95 -0
- package/dist/excalidraw/locales/en.json +10 -5
- package/dist/excalidraw/queue.d.ts +9 -0
- package/dist/excalidraw/queue.js +27 -0
- package/dist/excalidraw/reactUtils.d.ts +14 -0
- package/dist/excalidraw/reactUtils.js +45 -0
- package/dist/excalidraw/renderer/helpers.d.ts +13 -0
- package/dist/excalidraw/renderer/helpers.js +39 -0
- package/dist/excalidraw/renderer/interactiveScene.d.ts +20 -0
- package/dist/excalidraw/renderer/{renderScene.js → interactiveScene.js} +199 -474
- package/dist/excalidraw/renderer/renderElement.d.ts +6 -6
- package/dist/excalidraw/renderer/renderElement.js +54 -366
- package/dist/excalidraw/renderer/staticScene.d.ts +11 -0
- package/dist/excalidraw/renderer/staticScene.js +205 -0
- package/dist/excalidraw/renderer/staticSvgScene.d.ts +5 -0
- package/dist/excalidraw/renderer/staticSvgScene.js +385 -0
- package/dist/excalidraw/scene/Fonts.js +2 -1
- package/dist/excalidraw/scene/Renderer.d.ts +1 -1
- package/dist/excalidraw/scene/Renderer.js +32 -20
- package/dist/excalidraw/scene/Scene.d.ts +10 -9
- package/dist/excalidraw/scene/Scene.js +45 -21
- package/dist/excalidraw/scene/Shape.d.ts +3 -1
- package/dist/excalidraw/scene/Shape.js +7 -5
- package/dist/excalidraw/scene/ShapeCache.d.ts +2 -1
- package/dist/excalidraw/scene/ShapeCache.js +1 -0
- package/dist/excalidraw/scene/comparisons.js +2 -1
- package/dist/excalidraw/scene/export.d.ts +3 -0
- package/dist/excalidraw/scene/export.js +20 -40
- package/dist/excalidraw/scene/index.d.ts +0 -1
- package/dist/excalidraw/scene/index.js +0 -1
- package/dist/excalidraw/scene/scrollbars.d.ts +1 -1
- package/dist/excalidraw/scene/scrollbars.js +1 -1
- package/dist/excalidraw/scene/selection.d.ts +5 -5
- package/dist/excalidraw/scene/selection.js +16 -14
- package/dist/excalidraw/scene/types.d.ts +11 -5
- package/dist/excalidraw/snapping.d.ts +7 -7
- package/dist/excalidraw/snapping.js +21 -20
- package/dist/excalidraw/types.d.ts +16 -17
- package/dist/excalidraw/utility-types.d.ts +7 -0
- package/dist/excalidraw/utils.d.ts +21 -16
- package/dist/excalidraw/utils.js +43 -45
- package/dist/{dev/en-RLIAOBCI.json → prod/en-EY7E2L5O.json} +10 -5
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +42 -42
- package/dist/utils/bbox.d.ts +2 -2
- package/dist/utils/export.d.ts +3 -3
- package/dist/utils/export.js +3 -13
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +2 -2
- package/dist/utils/withinBounds.d.ts +1 -1
- package/dist/utils/withinBounds.js +5 -2
- package/package.json +4 -4
- package/dist/browser/dev/excalidraw-assets-dev/chunk-2W5GQUR4.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/chunk-KGZXLFLR.js +0 -53497
- package/dist/browser/dev/excalidraw-assets-dev/chunk-KGZXLFLR.js.map +0 -7
- package/dist/browser/dev/excalidraw-assets-dev/image-3MFRCKYM.css +0 -5797
- package/dist/browser/dev/excalidraw-assets-dev/image-3MFRCKYM.css.map +0 -7
- package/dist/browser/prod/excalidraw-assets/chunk-4YN2HN3S.js +0 -257
- package/dist/browser/prod/excalidraw-assets/chunk-OWLL6VOG.js +0 -11
- package/dist/browser/prod/excalidraw-assets/en-ERQOR3OC.js +0 -1
- package/dist/browser/prod/excalidraw-assets/image-LTLHTTSE.js +0 -1
- package/dist/browser/prod/excalidraw-assets/image-QBL334OA.css +0 -1
- package/dist/excalidraw/components/LaserTool/LaserPathManager.d.ts +0 -28
- package/dist/excalidraw/components/LaserTool/LaserPathManager.js +0 -225
- package/dist/excalidraw/components/LaserTool/LaserTool.d.ts +0 -8
- package/dist/excalidraw/components/LaserTool/LaserTool.js +0 -15
- package/dist/excalidraw/renderer/renderScene.d.ts +0 -25
- package/dist/excalidraw/vite.config.d.mts +0 -2
- package/dist/excalidraw/vite.config.mjs +0 -13
- /package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js.map → en-BZY7JRTM.js.map} +0 -0
- /package/dist/browser/dev/excalidraw-assets-dev/{image-5TVMINCA.js.map → image-CVN3YKRW.js.map} +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { copyBlobToClipboardAsPng, copyTextToSystemClipboard, } from "../clipboard";
|
|
2
|
-
import { DEFAULT_EXPORT_PADDING, isFirefox, MIME_TYPES } from "../constants";
|
|
2
|
+
import { DEFAULT_EXPORT_PADDING, DEFAULT_FILENAME, isFirefox, MIME_TYPES, } from "../constants";
|
|
3
3
|
import { getNonDeletedElements } from "../element";
|
|
4
4
|
import { isFrameLikeElement } from "../element/typeChecks";
|
|
5
5
|
import { t } from "../i18n";
|
|
6
|
-
import { elementsOverlappingBBox } from "../../utils/index";
|
|
7
6
|
import { isSomeElementSelected, getSelectedElements } from "../scene";
|
|
8
7
|
import { exportToCanvas, exportToSvg } from "../scene/export";
|
|
9
8
|
import { cloneJSON } from "../utils";
|
|
10
9
|
import { canvasToBlob } from "./blob";
|
|
11
10
|
import { fileSave } from "./filesystem";
|
|
12
11
|
import { serializeAsJSON } from "./json";
|
|
12
|
+
import { getElementsOverlappingFrame } from "../frame";
|
|
13
13
|
export { loadFromBlob } from "./blob";
|
|
14
14
|
export { loadFromJSON, saveAsJSON } from "./json";
|
|
15
15
|
export const prepareElementsForExport = (elements, { selectedElementIds }, exportSelectionOnly) => {
|
|
@@ -26,11 +26,7 @@ export const prepareElementsForExport = (elements, { selectedElementIds }, expor
|
|
|
26
26
|
if (exportedElements.length === 1 &&
|
|
27
27
|
isFrameLikeElement(exportedElements[0])) {
|
|
28
28
|
exportingFrame = exportedElements[0];
|
|
29
|
-
exportedElements =
|
|
30
|
-
elements,
|
|
31
|
-
bounds: exportingFrame,
|
|
32
|
-
type: "overlap",
|
|
33
|
-
});
|
|
29
|
+
exportedElements = getElementsOverlappingFrame(elements, exportingFrame);
|
|
34
30
|
}
|
|
35
31
|
else if (exportedElements.length > 1) {
|
|
36
32
|
exportedElements = getSelectedElements(elements, { selectedElementIds }, {
|
|
@@ -44,12 +40,12 @@ export const prepareElementsForExport = (elements, { selectedElementIds }, expor
|
|
|
44
40
|
exportedElements: cloneJSON(exportedElements),
|
|
45
41
|
};
|
|
46
42
|
};
|
|
47
|
-
export const exportCanvas = async (type, elements, appState, files, { exportBackground, exportPadding = DEFAULT_EXPORT_PADDING, viewBackgroundColor, name, fileHandle = null, exportingFrame = null, }) => {
|
|
43
|
+
export const exportCanvas = async (type, elements, appState, files, { exportBackground, exportPadding = DEFAULT_EXPORT_PADDING, viewBackgroundColor, name = appState.name || DEFAULT_FILENAME, fileHandle = null, exportingFrame = null, }) => {
|
|
48
44
|
if (elements.length === 0) {
|
|
49
45
|
throw new Error(t("alerts.cannotExportEmptyCanvas"));
|
|
50
46
|
}
|
|
51
47
|
if (type === "svg" || type === "clipboard-svg") {
|
|
52
|
-
const
|
|
48
|
+
const svgPromise = exportToSvg(elements, {
|
|
53
49
|
exportBackground,
|
|
54
50
|
exportWithDarkMode: appState.exportWithDarkMode,
|
|
55
51
|
viewBackgroundColor,
|
|
@@ -58,7 +54,9 @@ export const exportCanvas = async (type, elements, appState, files, { exportBack
|
|
|
58
54
|
exportEmbedScene: appState.exportEmbedScene && type === "svg",
|
|
59
55
|
}, files, { exportingFrame });
|
|
60
56
|
if (type === "svg") {
|
|
61
|
-
return
|
|
57
|
+
return fileSave(svgPromise.then((svg) => {
|
|
58
|
+
return new Blob([svg.outerHTML], { type: MIME_TYPES.svg });
|
|
59
|
+
}), {
|
|
62
60
|
description: "Export to SVG",
|
|
63
61
|
name,
|
|
64
62
|
extension: appState.exportEmbedScene ? "excalidraw.svg" : "svg",
|
|
@@ -66,7 +64,13 @@ export const exportCanvas = async (type, elements, appState, files, { exportBack
|
|
|
66
64
|
});
|
|
67
65
|
}
|
|
68
66
|
else if (type === "clipboard-svg") {
|
|
69
|
-
await
|
|
67
|
+
const svg = await svgPromise.then((svg) => svg.outerHTML);
|
|
68
|
+
try {
|
|
69
|
+
await copyTextToSystemClipboard(svg);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
throw new Error(t("errors.copyToSystemClipboardFailed"));
|
|
73
|
+
}
|
|
70
74
|
return;
|
|
71
75
|
}
|
|
72
76
|
}
|
|
@@ -77,14 +81,14 @@ export const exportCanvas = async (type, elements, appState, files, { exportBack
|
|
|
77
81
|
exportingFrame,
|
|
78
82
|
});
|
|
79
83
|
if (type === "png") {
|
|
80
|
-
let blob =
|
|
84
|
+
let blob = canvasToBlob(tempCanvas);
|
|
81
85
|
if (appState.exportEmbedScene) {
|
|
82
|
-
blob =
|
|
86
|
+
blob = blob.then((blob) => import("./image").then(({ encodePngMetadata }) => encodePngMetadata({
|
|
83
87
|
blob,
|
|
84
88
|
metadata: serializeAsJSON(elements, appState, files, "local"),
|
|
85
|
-
});
|
|
89
|
+
})));
|
|
86
90
|
}
|
|
87
|
-
return
|
|
91
|
+
return fileSave(blob, {
|
|
88
92
|
description: "Export to PNG",
|
|
89
93
|
name,
|
|
90
94
|
// FIXME reintroduce `excalidraw.png` when most people upgrade away
|
|
@@ -101,7 +105,7 @@ export const exportCanvas = async (type, elements, appState, files, { exportBack
|
|
|
101
105
|
catch (error) {
|
|
102
106
|
console.warn(error);
|
|
103
107
|
if (error.name === "CANVAS_POSSIBLY_TOO_BIG") {
|
|
104
|
-
throw
|
|
108
|
+
throw new Error(t("canvasError.canvasTooBig"));
|
|
105
109
|
}
|
|
106
110
|
// TypeError *probably* suggests ClipboardItem not defined, which
|
|
107
111
|
// people on Firefox can enable through a flag, so let's tell them.
|
|
@@ -2,7 +2,7 @@ import { ExcalidrawElement } from "../element/types";
|
|
|
2
2
|
import { AppState, BinaryFiles, LibraryItems } from "../types";
|
|
3
3
|
import { ImportedDataState, ImportedLibraryData } from "./types";
|
|
4
4
|
export declare const serializeAsJSON: (elements: readonly ExcalidrawElement[], appState: Partial<AppState>, files: BinaryFiles, type: "local" | "database") => string;
|
|
5
|
-
export declare const saveAsJSON: (elements: readonly ExcalidrawElement[], appState: AppState, files: BinaryFiles) => Promise<{
|
|
5
|
+
export declare const saveAsJSON: (elements: readonly ExcalidrawElement[], appState: AppState, files: BinaryFiles, name?: string) => Promise<{
|
|
6
6
|
fileHandle: import("browser-fs-access").FileSystemHandle | null;
|
|
7
7
|
}>;
|
|
8
8
|
export declare const loadFromJSON: (localAppState: AppState, localElements: readonly ExcalidrawElement[] | null) => Promise<import("./restore").RestoredDataState>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fileOpen, fileSave } from "./filesystem";
|
|
2
2
|
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
|
|
3
|
-
import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES, VERSIONS, } from "../constants";
|
|
3
|
+
import { DEFAULT_FILENAME, EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES, VERSIONS, } from "../constants";
|
|
4
4
|
import { clearElementsForDatabase, clearElementsForExport } from "../element";
|
|
5
5
|
import { isImageFileHandle, loadFromBlob, normalizeFile } from "./blob";
|
|
6
6
|
/**
|
|
@@ -36,13 +36,15 @@ export const serializeAsJSON = (elements, appState, files, type) => {
|
|
|
36
36
|
};
|
|
37
37
|
return JSON.stringify(data, null, 2);
|
|
38
38
|
};
|
|
39
|
-
export const saveAsJSON = async (elements, appState, files
|
|
39
|
+
export const saveAsJSON = async (elements, appState, files,
|
|
40
|
+
/** filename */
|
|
41
|
+
name = appState.name || DEFAULT_FILENAME) => {
|
|
40
42
|
const serialized = serializeAsJSON(elements, appState, files, "local");
|
|
41
43
|
const blob = new Blob([serialized], {
|
|
42
44
|
type: MIME_TYPES.excalidraw,
|
|
43
45
|
});
|
|
44
46
|
const fileHandle = await fileSave(blob, {
|
|
45
|
-
name
|
|
47
|
+
name,
|
|
46
48
|
extension: "excalidraw",
|
|
47
49
|
description: "Excalidraw file",
|
|
48
50
|
fileHandle: isImageFileHandle(appState.fileHandle)
|
|
@@ -1,13 +1,53 @@
|
|
|
1
|
-
import { LibraryItems, ExcalidrawImperativeAPI, LibraryItemsSource } from "../types";
|
|
1
|
+
import { LibraryItems, ExcalidrawImperativeAPI, LibraryItemsSource, LibraryItems_anyVersion } from "../types";
|
|
2
2
|
import type App from "../components/App";
|
|
3
3
|
import { ExcalidrawElement } from "../element/types";
|
|
4
|
+
import { MaybePromise } from "../utility-types";
|
|
5
|
+
export type LibraryPersistedData = {
|
|
6
|
+
libraryItems: LibraryItems;
|
|
7
|
+
};
|
|
8
|
+
export type LibraryAdatapterSource = "load" | "save";
|
|
9
|
+
export interface LibraryPersistenceAdapter {
|
|
10
|
+
/**
|
|
11
|
+
* Should load data that were previously saved into the database using the
|
|
12
|
+
* `save` method. Should throw if saving fails.
|
|
13
|
+
*
|
|
14
|
+
* Will be used internally in multiple places, such as during save to
|
|
15
|
+
* in order to reconcile changes with latest store data.
|
|
16
|
+
*/
|
|
17
|
+
load(metadata: {
|
|
18
|
+
/**
|
|
19
|
+
* Indicates whether we're loading data for save purposes, or reading
|
|
20
|
+
* purposes, in which case host app can implement more aggressive caching.
|
|
21
|
+
*/
|
|
22
|
+
source: LibraryAdatapterSource;
|
|
23
|
+
}): MaybePromise<{
|
|
24
|
+
libraryItems: LibraryItems_anyVersion;
|
|
25
|
+
} | null>;
|
|
26
|
+
/** Should persist to the database as is (do no change the data structure). */
|
|
27
|
+
save(libraryData: LibraryPersistedData): MaybePromise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface LibraryMigrationAdapter {
|
|
30
|
+
/**
|
|
31
|
+
* loads data from legacy data source. Returns `null` if no data is
|
|
32
|
+
* to be migrated.
|
|
33
|
+
*/
|
|
34
|
+
load(): MaybePromise<{
|
|
35
|
+
libraryItems: LibraryItems_anyVersion;
|
|
36
|
+
} | null>;
|
|
37
|
+
/** clears entire storage afterwards */
|
|
38
|
+
clear(): MaybePromise<void>;
|
|
39
|
+
}
|
|
4
40
|
export declare const libraryItemsAtom: import("jotai").PrimitiveAtom<{
|
|
5
41
|
status: "loading" | "loaded";
|
|
42
|
+
/** indicates whether library is initialized with library items (has gone
|
|
43
|
+
* through at least one update). Used in UI. Specific to this atom only. */
|
|
6
44
|
isInitialized: boolean;
|
|
7
45
|
libraryItems: LibraryItems;
|
|
8
46
|
}> & {
|
|
9
47
|
init: {
|
|
10
48
|
status: "loading" | "loaded";
|
|
49
|
+
/** indicates whether library is initialized with library items (has gone
|
|
50
|
+
* through at least one update). Used in UI. Specific to this atom only. */
|
|
11
51
|
isInitialized: boolean;
|
|
12
52
|
libraryItems: LibraryItems;
|
|
13
53
|
};
|
|
@@ -17,10 +57,9 @@ export declare const libraryItemsAtom: import("jotai").PrimitiveAtom<{
|
|
|
17
57
|
export declare const mergeLibraryItems: (localItems: LibraryItems, otherItems: LibraryItems) => LibraryItems;
|
|
18
58
|
declare class Library {
|
|
19
59
|
/** latest libraryItems */
|
|
20
|
-
private
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
private isInitialized;
|
|
60
|
+
private currLibraryItems;
|
|
61
|
+
/** snapshot of library items since last onLibraryChange call */
|
|
62
|
+
private prevLibraryItems;
|
|
24
63
|
private app;
|
|
25
64
|
constructor(app: App);
|
|
26
65
|
private updateQueue;
|
|
@@ -48,7 +87,20 @@ export declare const parseLibraryTokensFromUrl: () => {
|
|
|
48
87
|
libraryUrl: string;
|
|
49
88
|
idToken: string | null;
|
|
50
89
|
} | null;
|
|
51
|
-
export declare const
|
|
90
|
+
export declare const getLibraryItemsHash: (items: LibraryItems) => number;
|
|
91
|
+
export declare const useHandleLibrary: (opts: {
|
|
52
92
|
excalidrawAPI: ExcalidrawImperativeAPI | null;
|
|
53
|
-
|
|
54
|
-
|
|
93
|
+
} & ({
|
|
94
|
+
/** @deprecated we recommend using `opts.adapter` instead */
|
|
95
|
+
getInitialLibraryItems?: () => MaybePromise<LibraryItemsSource>;
|
|
96
|
+
} | {
|
|
97
|
+
adapter: LibraryPersistenceAdapter;
|
|
98
|
+
/**
|
|
99
|
+
* Adapter that takes care of loading data from legacy data store.
|
|
100
|
+
* Supply this if you want to migrate data on initial load from legacy
|
|
101
|
+
* data store.
|
|
102
|
+
*
|
|
103
|
+
* Can be a different LibraryPersistenceAdapter.
|
|
104
|
+
*/
|
|
105
|
+
migrationAdapter?: LibraryMigrationAdapter;
|
|
106
|
+
})) => void;
|
|
@@ -8,8 +8,12 @@ import { t } from "../i18n";
|
|
|
8
8
|
import { useEffect, useRef } from "react";
|
|
9
9
|
import { URL_HASH_KEYS, URL_QUERY_KEYS, APP_NAME, EVENT, DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_TAB, } from "../constants";
|
|
10
10
|
import { libraryItemSvgsCache } from "../hooks/useLibraryItemSvg";
|
|
11
|
-
import { cloneJSON } from "../utils";
|
|
12
|
-
|
|
11
|
+
import { arrayToMap, cloneJSON, preventUnload, promiseTry, resolvablePromise, } from "../utils";
|
|
12
|
+
import { Emitter } from "../emitter";
|
|
13
|
+
import { Queue } from "../queue";
|
|
14
|
+
import { hashElementsVersion, hashString } from "../element";
|
|
15
|
+
const onLibraryUpdateEmitter = new Emitter();
|
|
16
|
+
export const libraryItemsAtom = atom({ status: "loaded", isInitialized: false, libraryItems: [] });
|
|
13
17
|
const cloneLibraryItems = (libraryItems) => cloneJSON(libraryItems);
|
|
14
18
|
/**
|
|
15
19
|
* checks if library item does not exist already in current library
|
|
@@ -39,12 +43,36 @@ export const mergeLibraryItems = (localItems, otherItems) => {
|
|
|
39
43
|
}
|
|
40
44
|
return [...newItems, ...localItems];
|
|
41
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Returns { deletedItems, addedItems } maps of all added and deleted items
|
|
48
|
+
* since last onLibraryChange event.
|
|
49
|
+
*
|
|
50
|
+
* Host apps are recommended to diff with the latest state they have.
|
|
51
|
+
*/
|
|
52
|
+
const createLibraryUpdate = (prevLibraryItems, nextLibraryItems) => {
|
|
53
|
+
const nextItemsMap = arrayToMap(nextLibraryItems);
|
|
54
|
+
const update = {
|
|
55
|
+
deletedItems: new Map(),
|
|
56
|
+
addedItems: new Map(),
|
|
57
|
+
};
|
|
58
|
+
for (const item of prevLibraryItems) {
|
|
59
|
+
if (!nextItemsMap.has(item.id)) {
|
|
60
|
+
update.deletedItems.set(item.id, item);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const prevItemsMap = arrayToMap(prevLibraryItems);
|
|
64
|
+
for (const item of nextLibraryItems) {
|
|
65
|
+
if (!prevItemsMap.has(item.id)) {
|
|
66
|
+
update.addedItems.set(item.id, item);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return update;
|
|
70
|
+
};
|
|
42
71
|
class Library {
|
|
43
72
|
/** latest libraryItems */
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
isInitialized = false;
|
|
73
|
+
currLibraryItems = [];
|
|
74
|
+
/** snapshot of library items since last onLibraryChange call */
|
|
75
|
+
prevLibraryItems = cloneLibraryItems(this.currLibraryItems);
|
|
48
76
|
app;
|
|
49
77
|
constructor(app) {
|
|
50
78
|
this.app = app;
|
|
@@ -55,21 +83,25 @@ class Library {
|
|
|
55
83
|
};
|
|
56
84
|
notifyListeners = () => {
|
|
57
85
|
if (this.updateQueue.length > 0) {
|
|
58
|
-
jotaiStore.set(libraryItemsAtom, {
|
|
86
|
+
jotaiStore.set(libraryItemsAtom, (s) => ({
|
|
59
87
|
status: "loading",
|
|
60
|
-
libraryItems: this.
|
|
61
|
-
isInitialized:
|
|
62
|
-
});
|
|
88
|
+
libraryItems: this.currLibraryItems,
|
|
89
|
+
isInitialized: s.isInitialized,
|
|
90
|
+
}));
|
|
63
91
|
}
|
|
64
92
|
else {
|
|
65
|
-
this.isInitialized = true;
|
|
66
93
|
jotaiStore.set(libraryItemsAtom, {
|
|
67
94
|
status: "loaded",
|
|
68
|
-
libraryItems: this.
|
|
69
|
-
isInitialized:
|
|
95
|
+
libraryItems: this.currLibraryItems,
|
|
96
|
+
isInitialized: true,
|
|
70
97
|
});
|
|
71
98
|
try {
|
|
72
|
-
this.
|
|
99
|
+
const prevLibraryItems = this.prevLibraryItems;
|
|
100
|
+
this.prevLibraryItems = cloneLibraryItems(this.currLibraryItems);
|
|
101
|
+
const nextLibraryItems = cloneLibraryItems(this.currLibraryItems);
|
|
102
|
+
this.app.props.onLibraryChange?.(nextLibraryItems);
|
|
103
|
+
// for internal use in `useHandleLibrary` hook
|
|
104
|
+
onLibraryUpdateEmitter.trigger(createLibraryUpdate(prevLibraryItems, nextLibraryItems), nextLibraryItems);
|
|
73
105
|
}
|
|
74
106
|
catch (error) {
|
|
75
107
|
console.error(error);
|
|
@@ -78,9 +110,8 @@ class Library {
|
|
|
78
110
|
};
|
|
79
111
|
/** call on excalidraw instance unmount */
|
|
80
112
|
destroy = () => {
|
|
81
|
-
this.isInitialized = false;
|
|
82
113
|
this.updateQueue = [];
|
|
83
|
-
this.
|
|
114
|
+
this.currLibraryItems = [];
|
|
84
115
|
jotaiStore.set(libraryItemSvgsCache, new Map());
|
|
85
116
|
// TODO uncomment after/if we make jotai store scoped to each excal instance
|
|
86
117
|
// jotaiStore.set(libraryItemsAtom, {
|
|
@@ -99,7 +130,7 @@ class Library {
|
|
|
99
130
|
return new Promise(async (resolve) => {
|
|
100
131
|
try {
|
|
101
132
|
const libraryItems = await (this.getLastUpdateTask() ||
|
|
102
|
-
this.
|
|
133
|
+
this.currLibraryItems);
|
|
103
134
|
if (this.updateQueue.length > 0) {
|
|
104
135
|
resolve(this.getLatestLibrary());
|
|
105
136
|
}
|
|
@@ -108,7 +139,7 @@ class Library {
|
|
|
108
139
|
}
|
|
109
140
|
}
|
|
110
141
|
catch (error) {
|
|
111
|
-
return resolve(this.
|
|
142
|
+
return resolve(this.currLibraryItems);
|
|
112
143
|
}
|
|
113
144
|
});
|
|
114
145
|
};
|
|
@@ -126,7 +157,7 @@ class Library {
|
|
|
126
157
|
try {
|
|
127
158
|
const source = await (typeof libraryItems === "function" &&
|
|
128
159
|
!(libraryItems instanceof Blob)
|
|
129
|
-
? libraryItems(this.
|
|
160
|
+
? libraryItems(this.currLibraryItems)
|
|
130
161
|
: libraryItems);
|
|
131
162
|
let nextItems;
|
|
132
163
|
if (source instanceof Blob) {
|
|
@@ -146,7 +177,7 @@ class Library {
|
|
|
146
177
|
this.app.focusContainer();
|
|
147
178
|
}
|
|
148
179
|
if (merge) {
|
|
149
|
-
resolve(mergeLibraryItems(this.
|
|
180
|
+
resolve(mergeLibraryItems(this.currLibraryItems, nextItems));
|
|
150
181
|
}
|
|
151
182
|
else {
|
|
152
183
|
resolve(nextItems);
|
|
@@ -178,10 +209,10 @@ class Library {
|
|
|
178
209
|
try {
|
|
179
210
|
await this.getLastUpdateTask();
|
|
180
211
|
if (typeof libraryItems === "function") {
|
|
181
|
-
libraryItems = libraryItems(this.
|
|
212
|
+
libraryItems = libraryItems(this.currLibraryItems);
|
|
182
213
|
}
|
|
183
|
-
this.
|
|
184
|
-
resolve(this.
|
|
214
|
+
this.currLibraryItems = cloneLibraryItems(await libraryItems);
|
|
215
|
+
resolve(this.currLibraryItems);
|
|
185
216
|
}
|
|
186
217
|
catch (error) {
|
|
187
218
|
reject(error);
|
|
@@ -190,7 +221,7 @@ class Library {
|
|
|
190
221
|
.catch((error) => {
|
|
191
222
|
if (error.name === "AbortError") {
|
|
192
223
|
console.warn("Library update aborted by user");
|
|
193
|
-
return this.
|
|
224
|
+
return this.currLibraryItems;
|
|
194
225
|
}
|
|
195
226
|
throw error;
|
|
196
227
|
})
|
|
@@ -291,12 +322,105 @@ export const parseLibraryTokensFromUrl = () => {
|
|
|
291
322
|
: null;
|
|
292
323
|
return libraryUrl ? { libraryUrl, idToken } : null;
|
|
293
324
|
};
|
|
294
|
-
|
|
295
|
-
|
|
325
|
+
class AdapterTransaction {
|
|
326
|
+
static queue = new Queue();
|
|
327
|
+
static async getLibraryItems(adapter, source, _queue = true) {
|
|
328
|
+
const task = () => new Promise(async (resolve, reject) => {
|
|
329
|
+
try {
|
|
330
|
+
const data = await adapter.load({ source });
|
|
331
|
+
resolve(restoreLibraryItems(data?.libraryItems || [], "published"));
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
reject(error);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
if (_queue) {
|
|
338
|
+
return AdapterTransaction.queue.push(task);
|
|
339
|
+
}
|
|
340
|
+
return task();
|
|
341
|
+
}
|
|
342
|
+
static run = async (adapter, fn) => {
|
|
343
|
+
const transaction = new AdapterTransaction(adapter);
|
|
344
|
+
return AdapterTransaction.queue.push(() => fn(transaction));
|
|
345
|
+
};
|
|
346
|
+
// ------------------
|
|
347
|
+
adapter;
|
|
348
|
+
constructor(adapter) {
|
|
349
|
+
this.adapter = adapter;
|
|
350
|
+
}
|
|
351
|
+
getLibraryItems(source) {
|
|
352
|
+
return AdapterTransaction.getLibraryItems(this.adapter, source, false);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
let lastSavedLibraryItemsHash = 0;
|
|
356
|
+
let librarySaveCounter = 0;
|
|
357
|
+
export const getLibraryItemsHash = (items) => {
|
|
358
|
+
return hashString(items
|
|
359
|
+
.map((item) => {
|
|
360
|
+
return `${item.id}:${hashElementsVersion(item.elements)}`;
|
|
361
|
+
})
|
|
362
|
+
.sort()
|
|
363
|
+
.join());
|
|
364
|
+
};
|
|
365
|
+
const persistLibraryUpdate = async (adapter, update) => {
|
|
366
|
+
try {
|
|
367
|
+
librarySaveCounter++;
|
|
368
|
+
return await AdapterTransaction.run(adapter, async (transaction) => {
|
|
369
|
+
const nextLibraryItemsMap = arrayToMap(await transaction.getLibraryItems("save"));
|
|
370
|
+
for (const [id] of update.deletedItems) {
|
|
371
|
+
nextLibraryItemsMap.delete(id);
|
|
372
|
+
}
|
|
373
|
+
const addedItems = [];
|
|
374
|
+
// we want to merge current library items with the ones stored in the
|
|
375
|
+
// DB so that we don't lose any elements that for some reason aren't
|
|
376
|
+
// in the current editor library, which could happen when:
|
|
377
|
+
//
|
|
378
|
+
// 1. we haven't received an update deleting some elements
|
|
379
|
+
// (in which case it's still better to keep them in the DB lest
|
|
380
|
+
// it was due to a different reason)
|
|
381
|
+
// 2. we keep a single DB for all active editors, but the editors'
|
|
382
|
+
// libraries aren't synced or there's a race conditions during
|
|
383
|
+
// syncing
|
|
384
|
+
// 3. some other race condition, e.g. during init where emit updates
|
|
385
|
+
// for partial updates (e.g. you install a 3rd party library and
|
|
386
|
+
// init from DB only after — we emit events for both updates)
|
|
387
|
+
for (const [id, item] of update.addedItems) {
|
|
388
|
+
if (nextLibraryItemsMap.has(id)) {
|
|
389
|
+
// replace item with latest version
|
|
390
|
+
// TODO we could prefer the newer item instead
|
|
391
|
+
nextLibraryItemsMap.set(id, item);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// we want to prepend the new items with the ones that are already
|
|
395
|
+
// in DB to preserve the ordering we do in editor (newly added
|
|
396
|
+
// items are added to the beginning)
|
|
397
|
+
addedItems.push(item);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const nextLibraryItems = addedItems.concat(Array.from(nextLibraryItemsMap.values()));
|
|
401
|
+
const version = getLibraryItemsHash(nextLibraryItems);
|
|
402
|
+
if (version !== lastSavedLibraryItemsHash) {
|
|
403
|
+
await adapter.save({ libraryItems: nextLibraryItems });
|
|
404
|
+
}
|
|
405
|
+
lastSavedLibraryItemsHash = version;
|
|
406
|
+
return nextLibraryItems;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
finally {
|
|
410
|
+
librarySaveCounter--;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
export const useHandleLibrary = (opts) => {
|
|
414
|
+
const { excalidrawAPI } = opts;
|
|
415
|
+
const optsRef = useRef(opts);
|
|
416
|
+
optsRef.current = opts;
|
|
417
|
+
const isLibraryLoadedRef = useRef(false);
|
|
296
418
|
useEffect(() => {
|
|
297
419
|
if (!excalidrawAPI) {
|
|
298
420
|
return;
|
|
299
421
|
}
|
|
422
|
+
// reset on editor remount (excalidrawAPI changed)
|
|
423
|
+
isLibraryLoadedRef.current = false;
|
|
300
424
|
const importLibraryFromURL = async ({ libraryUrl, idToken, }) => {
|
|
301
425
|
const libraryPromise = new Promise(async (resolve, reject) => {
|
|
302
426
|
try {
|
|
@@ -357,20 +481,165 @@ export const useHandleLibrary = ({ excalidrawAPI, getInitialLibraryItems, }) =>
|
|
|
357
481
|
}
|
|
358
482
|
};
|
|
359
483
|
// -------------------------------------------------------------------------
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
excalidrawAPI.updateLibrary({
|
|
363
|
-
libraryItems: getInitialLibraryRef.current(),
|
|
364
|
-
});
|
|
365
|
-
}
|
|
484
|
+
// ---------------------------------- init ---------------------------------
|
|
485
|
+
// -------------------------------------------------------------------------
|
|
366
486
|
const libraryUrlTokens = parseLibraryTokensFromUrl();
|
|
367
487
|
if (libraryUrlTokens) {
|
|
368
488
|
importLibraryFromURL(libraryUrlTokens);
|
|
369
489
|
}
|
|
490
|
+
// ------ (A) init load (legacy) -------------------------------------------
|
|
491
|
+
if ("getInitialLibraryItems" in optsRef.current &&
|
|
492
|
+
optsRef.current.getInitialLibraryItems) {
|
|
493
|
+
console.warn("useHandleLibrar `opts.getInitialLibraryItems` is deprecated. Use `opts.adapter` instead.");
|
|
494
|
+
Promise.resolve(optsRef.current.getInitialLibraryItems())
|
|
495
|
+
.then((libraryItems) => {
|
|
496
|
+
excalidrawAPI.updateLibrary({
|
|
497
|
+
libraryItems,
|
|
498
|
+
// merge with current library items because we may have already
|
|
499
|
+
// populated it (e.g. by installing 3rd party library which can
|
|
500
|
+
// happen before the DB data is loaded)
|
|
501
|
+
merge: true,
|
|
502
|
+
});
|
|
503
|
+
})
|
|
504
|
+
.catch((error) => {
|
|
505
|
+
console.error(`UseHandeLibrary getInitialLibraryItems failed: ${error?.message}`);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
// -------------------------------------------------------------------------
|
|
370
509
|
// --------------------------------------------------------- init load -----
|
|
510
|
+
// -------------------------------------------------------------------------
|
|
511
|
+
// ------ (B) data source adapter ------------------------------------------
|
|
512
|
+
if ("adapter" in optsRef.current && optsRef.current.adapter) {
|
|
513
|
+
const adapter = optsRef.current.adapter;
|
|
514
|
+
const migrationAdapter = optsRef.current.migrationAdapter;
|
|
515
|
+
const initDataPromise = resolvablePromise();
|
|
516
|
+
// migrate from old data source if needed
|
|
517
|
+
// (note, if `migrate` function is defined, we always migrate even
|
|
518
|
+
// if the data has already been migrated. In that case it'll be a no-op,
|
|
519
|
+
// though with several unnecessary steps — we will still load latest
|
|
520
|
+
// DB data during the `persistLibraryChange()` step)
|
|
521
|
+
// -----------------------------------------------------------------------
|
|
522
|
+
if (migrationAdapter) {
|
|
523
|
+
initDataPromise.resolve(promiseTry(migrationAdapter.load)
|
|
524
|
+
.then(async (libraryData) => {
|
|
525
|
+
let restoredData = null;
|
|
526
|
+
try {
|
|
527
|
+
// if no library data to migrate, assume no migration needed
|
|
528
|
+
// and skip persisting to new data store, as well as well
|
|
529
|
+
// clearing the old store via `migrationAdapter.clear()`
|
|
530
|
+
if (!libraryData) {
|
|
531
|
+
return AdapterTransaction.getLibraryItems(adapter, "load");
|
|
532
|
+
}
|
|
533
|
+
restoredData = restoreLibraryItems(libraryData.libraryItems || [], "published");
|
|
534
|
+
// we don't queue this operation because it's running inside
|
|
535
|
+
// a promise that's running inside Library update queue itself
|
|
536
|
+
const nextItems = await persistLibraryUpdate(adapter, createLibraryUpdate([], restoredData));
|
|
537
|
+
try {
|
|
538
|
+
await migrationAdapter.clear();
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
console.error(`couldn't delete legacy library data: ${error.message}`);
|
|
542
|
+
}
|
|
543
|
+
// migration suceeded, load migrated data
|
|
544
|
+
return nextItems;
|
|
545
|
+
}
|
|
546
|
+
catch (error) {
|
|
547
|
+
console.error(`couldn't migrate legacy library data: ${error.message}`);
|
|
548
|
+
// migration failed, load data from previous store, if any
|
|
549
|
+
return restoredData;
|
|
550
|
+
}
|
|
551
|
+
})
|
|
552
|
+
// errors caught during `migrationAdapter.load()`
|
|
553
|
+
.catch((error) => {
|
|
554
|
+
console.error(`error during library migration: ${error.message}`);
|
|
555
|
+
// as a default, load latest library from current data source
|
|
556
|
+
return AdapterTransaction.getLibraryItems(adapter, "load");
|
|
557
|
+
}));
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
initDataPromise.resolve(promiseTry(AdapterTransaction.getLibraryItems, adapter, "load"));
|
|
561
|
+
}
|
|
562
|
+
// load initial (or migrated) library
|
|
563
|
+
excalidrawAPI
|
|
564
|
+
.updateLibrary({
|
|
565
|
+
libraryItems: initDataPromise.then((libraryItems) => {
|
|
566
|
+
const _libraryItems = libraryItems || [];
|
|
567
|
+
lastSavedLibraryItemsHash = getLibraryItemsHash(_libraryItems);
|
|
568
|
+
return _libraryItems;
|
|
569
|
+
}),
|
|
570
|
+
// merge with current library items because we may have already
|
|
571
|
+
// populated it (e.g. by installing 3rd party library which can
|
|
572
|
+
// happen before the DB data is loaded)
|
|
573
|
+
merge: true,
|
|
574
|
+
})
|
|
575
|
+
.finally(() => {
|
|
576
|
+
isLibraryLoadedRef.current = true;
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
// ---------------------------------------------- data source datapter -----
|
|
371
580
|
window.addEventListener(EVENT.HASHCHANGE, onHashChange);
|
|
372
581
|
return () => {
|
|
373
582
|
window.removeEventListener(EVENT.HASHCHANGE, onHashChange);
|
|
374
583
|
};
|
|
375
|
-
}, [
|
|
584
|
+
}, [
|
|
585
|
+
// important this useEffect only depends on excalidrawAPI so it only reruns
|
|
586
|
+
// on editor remounts (the excalidrawAPI changes)
|
|
587
|
+
excalidrawAPI,
|
|
588
|
+
]);
|
|
589
|
+
// This effect is run without excalidrawAPI dependency so that host apps
|
|
590
|
+
// can run this hook outside of an active editor instance and the library
|
|
591
|
+
// update queue/loop survives editor remounts
|
|
592
|
+
//
|
|
593
|
+
// This effect is still only meant to be run if host apps supply an persitence
|
|
594
|
+
// adapter. If we don't have access to it, it the update listener doesn't
|
|
595
|
+
// do anything.
|
|
596
|
+
useEffect(() => {
|
|
597
|
+
// on update, merge with current library items and persist
|
|
598
|
+
// -----------------------------------------------------------------------
|
|
599
|
+
const unsubOnLibraryUpdate = onLibraryUpdateEmitter.on(async (update, nextLibraryItems) => {
|
|
600
|
+
const isLoaded = isLibraryLoadedRef.current;
|
|
601
|
+
// we want to operate with the latest adapter, but we don't want this
|
|
602
|
+
// effect to rerun on every adapter change in case host apps' adapter
|
|
603
|
+
// isn't stable
|
|
604
|
+
const adapter = ("adapter" in optsRef.current && optsRef.current.adapter) || null;
|
|
605
|
+
try {
|
|
606
|
+
if (adapter) {
|
|
607
|
+
if (
|
|
608
|
+
// if nextLibraryItems hash identical to previously saved hash,
|
|
609
|
+
// exit early, even if actual upstream state ends up being
|
|
610
|
+
// different (e.g. has more data than we have locally), as it'd
|
|
611
|
+
// be low-impact scenario.
|
|
612
|
+
lastSavedLibraryItemsHash !==
|
|
613
|
+
getLibraryItemsHash(nextLibraryItems)) {
|
|
614
|
+
await persistLibraryUpdate(adapter, update);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
console.error(`couldn't persist library update: ${error.message}`, update);
|
|
620
|
+
// currently we only show error if an editor is loaded
|
|
621
|
+
if (isLoaded && optsRef.current.excalidrawAPI) {
|
|
622
|
+
optsRef.current.excalidrawAPI.updateScene({
|
|
623
|
+
appState: {
|
|
624
|
+
errorMessage: t("errors.saveLibraryError"),
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
const onUnload = (event) => {
|
|
631
|
+
if (librarySaveCounter) {
|
|
632
|
+
preventUnload(event);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
window.addEventListener(EVENT.BEFORE_UNLOAD, onUnload);
|
|
636
|
+
return () => {
|
|
637
|
+
window.removeEventListener(EVENT.BEFORE_UNLOAD, onUnload);
|
|
638
|
+
unsubOnLibraryUpdate();
|
|
639
|
+
lastSavedLibraryItemsHash = 0;
|
|
640
|
+
librarySaveCounter = 0;
|
|
641
|
+
};
|
|
642
|
+
}, [
|
|
643
|
+
// this effect must not have any deps so it doesn't rerun
|
|
644
|
+
]);
|
|
376
645
|
};
|