mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: factor out url library init & switch to updateLibrary
API (#5115)
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
This commit is contained in:
parent
2537b225ac
commit
cad6097d60
11 changed files with 394 additions and 235 deletions
|
@ -70,14 +70,12 @@ import {
|
|||
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
||||
THEME,
|
||||
TOUCH_CTX_MENU_TIMEOUT,
|
||||
URL_HASH_KEYS,
|
||||
URL_QUERY_KEYS,
|
||||
VERTICAL_ALIGN,
|
||||
ZOOM_STEP,
|
||||
} from "../constants";
|
||||
import { loadFromBlob } from "../data";
|
||||
import Library from "../data/library";
|
||||
import { restore, restoreElements, restoreLibraryItems } from "../data/restore";
|
||||
import { restore, restoreElements } from "../data/restore";
|
||||
import {
|
||||
dragNewElement,
|
||||
dragSelectedElements,
|
||||
|
@ -234,7 +232,6 @@ import {
|
|||
isSupportedImageFile,
|
||||
loadSceneOrLibraryFromBlob,
|
||||
normalizeFile,
|
||||
loadLibraryFromBlob,
|
||||
resizeImageFile,
|
||||
SVGStringToFile,
|
||||
} from "../data/blob";
|
||||
|
@ -261,7 +258,6 @@ import {
|
|||
isPointHittingLinkIcon,
|
||||
isLocalLink,
|
||||
} from "../element/Hyperlink";
|
||||
import { AbortError } from "../errors";
|
||||
|
||||
const defaultDeviceTypeContext: DeviceType = {
|
||||
isMobile: false,
|
||||
|
@ -361,6 +357,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||
|
||||
this.id = nanoid();
|
||||
|
||||
this.library = new Library(this);
|
||||
|
||||
if (excalidrawRef) {
|
||||
const readyPromise =
|
||||
("current" in excalidrawRef && excalidrawRef.current?.readyPromise) ||
|
||||
|
@ -370,6 +368,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
ready: true,
|
||||
readyPromise,
|
||||
updateScene: this.updateScene,
|
||||
updateLibrary: this.library.updateLibrary,
|
||||
addFiles: this.addFiles,
|
||||
resetScene: this.resetScene,
|
||||
getSceneElementsIncludingDeleted: this.getSceneElementsIncludingDeleted,
|
||||
|
@ -381,7 +380,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
getAppState: () => this.state,
|
||||
getFiles: () => this.files,
|
||||
refresh: this.refresh,
|
||||
importLibrary: this.importLibraryFromUrl,
|
||||
setToastMessage: this.setToastMessage,
|
||||
id: this.id,
|
||||
setActiveTool: this.setActiveTool,
|
||||
|
@ -400,7 +398,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
};
|
||||
|
||||
this.scene = new Scene();
|
||||
this.library = new Library(this);
|
||||
this.history = new History();
|
||||
this.actionManager = new ActionManager(
|
||||
this.syncActionResult,
|
||||
|
@ -698,54 +695,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
this.onSceneUpdated();
|
||||
};
|
||||
|
||||
private importLibraryFromUrl = async (url: string, token?: string | null) => {
|
||||
if (window.location.hash.includes(URL_HASH_KEYS.addLibrary)) {
|
||||
const hash = new URLSearchParams(window.location.hash.slice(1));
|
||||
hash.delete(URL_HASH_KEYS.addLibrary);
|
||||
window.history.replaceState({}, APP_NAME, `#${hash.toString()}`);
|
||||
} else if (window.location.search.includes(URL_QUERY_KEYS.addLibrary)) {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
query.delete(URL_QUERY_KEYS.addLibrary);
|
||||
window.history.replaceState({}, APP_NAME, `?${query.toString()}`);
|
||||
}
|
||||
|
||||
const defaultStatus = "published";
|
||||
|
||||
this.setState({ isLibraryOpen: true });
|
||||
|
||||
try {
|
||||
await this.library.importLibrary(
|
||||
new Promise<LibraryItems>(async (resolve, reject) => {
|
||||
try {
|
||||
const request = await fetch(decodeURIComponent(url));
|
||||
const blob = await request.blob();
|
||||
const libraryItems = await loadLibraryFromBlob(blob, defaultStatus);
|
||||
|
||||
if (
|
||||
token === this.id ||
|
||||
window.confirm(
|
||||
t("alerts.confirmAddLibrary", {
|
||||
numShapes: libraryItems.length,
|
||||
}),
|
||||
)
|
||||
) {
|
||||
resolve(libraryItems);
|
||||
} else {
|
||||
reject(new AbortError());
|
||||
}
|
||||
} catch (error: any) {
|
||||
reject(error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
this.setState({ errorMessage: t("errors.importLibraryError") });
|
||||
} finally {
|
||||
this.focusContainer();
|
||||
}
|
||||
};
|
||||
|
||||
private resetHistory = () => {
|
||||
this.history.clear();
|
||||
};
|
||||
|
@ -790,7 +739,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||
try {
|
||||
initialData = (await this.props.initialData) || null;
|
||||
if (initialData?.libraryItems) {
|
||||
this.library.importLibrary(initialData.libraryItems, "unpublished");
|
||||
this.library
|
||||
.updateLibrary({
|
||||
libraryItems: initialData.libraryItems,
|
||||
merge: true,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
|
@ -802,10 +758,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
const scene = restore(initialData, null, null);
|
||||
scene.appState = {
|
||||
...scene.appState,
|
||||
isLibraryOpen: this.state.isLibraryOpen,
|
||||
activeTool:
|
||||
scene.appState.activeTool.type === "image"
|
||||
? { ...scene.appState.activeTool, type: "selection" }
|
||||
|
@ -834,20 +790,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
...scene,
|
||||
commitToHistory: true,
|
||||
});
|
||||
|
||||
const libraryUrl =
|
||||
// current
|
||||
new URLSearchParams(window.location.hash.slice(1)).get(
|
||||
URL_HASH_KEYS.addLibrary,
|
||||
) ||
|
||||
// legacy, kept for compat reasons
|
||||
new URLSearchParams(window.location.search).get(
|
||||
URL_QUERY_KEYS.addLibrary,
|
||||
);
|
||||
|
||||
if (libraryUrl) {
|
||||
await this.importLibraryFromUrl(libraryUrl);
|
||||
}
|
||||
};
|
||||
|
||||
public async componentDidMount() {
|
||||
|
@ -1691,14 +1633,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
appState?: Pick<AppState, K> | null;
|
||||
collaborators?: SceneData["collaborators"];
|
||||
commitToHistory?: SceneData["commitToHistory"];
|
||||
libraryItems?:
|
||||
| ((
|
||||
currentLibraryItems: LibraryItems,
|
||||
) =>
|
||||
| Required<SceneData>["libraryItems"]
|
||||
| Promise<Required<SceneData>["libraryItems"]>)
|
||||
| Required<SceneData>["libraryItems"]
|
||||
| Promise<Required<SceneData>["libraryItems"]>;
|
||||
}) => {
|
||||
if (sceneData.commitToHistory) {
|
||||
this.history.resumeRecording();
|
||||
|
@ -1715,23 +1649,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||
if (sceneData.collaborators) {
|
||||
this.setState({ collaborators: sceneData.collaborators });
|
||||
}
|
||||
|
||||
if (sceneData.libraryItems) {
|
||||
this.library.setLibrary((currentLibraryItems) => {
|
||||
const nextItems =
|
||||
typeof sceneData.libraryItems === "function"
|
||||
? sceneData.libraryItems(currentLibraryItems)
|
||||
: sceneData.libraryItems;
|
||||
|
||||
return new Promise<LibraryItems>(async (resolve, reject) => {
|
||||
try {
|
||||
resolve(restoreLibraryItems(await nextItems, "unpublished"));
|
||||
} catch (error: any) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -5335,19 +5252,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||
commitToHistory: true,
|
||||
});
|
||||
} else if (ret.type === MIME_TYPES.excalidrawlib) {
|
||||
this.library
|
||||
.importLibrary(file)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
});
|
||||
await this.library
|
||||
.updateLibrary({
|
||||
libraryItems: file,
|
||||
merge: true,
|
||||
openLibraryMenu: true,
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
errorMessage: t("errors.importLibraryError"),
|
||||
});
|
||||
this.setState({ errorMessage: t("errors.importLibraryError") });
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { chunk } from "lodash";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { importLibraryFromJSON, saveLibraryAsJSON } from "../data/json";
|
||||
import { saveLibraryAsJSON } from "../data/json";
|
||||
import Library from "../data/library";
|
||||
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
||||
import { t } from "../i18n";
|
||||
|
@ -23,6 +23,7 @@ import { Tooltip } from "./Tooltip";
|
|||
import "./LibraryMenuItems.scss";
|
||||
import { VERSIONS } from "../constants";
|
||||
import Spinner from "./Spinner";
|
||||
import { fileOpen } from "../data/filesystem";
|
||||
|
||||
const LibraryMenuItems = ({
|
||||
isLoading,
|
||||
|
@ -107,13 +108,23 @@ const LibraryMenuItems = ({
|
|||
title={t("buttons.load")}
|
||||
aria-label={t("buttons.load")}
|
||||
icon={load}
|
||||
onClick={() => {
|
||||
importLibraryFromJSON(library)
|
||||
.catch(muteFSAbortError)
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
setAppState({ errorMessage: t("errors.importLibraryError") });
|
||||
onClick={async () => {
|
||||
try {
|
||||
await fileOpen({
|
||||
description: "Excalidraw library files",
|
||||
// ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
|
||||
// gets resolved. Else, iOS users cannot open `.excalidraw` files.
|
||||
/*
|
||||
extensions: [".json", ".excalidrawlib"],
|
||||
*/
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error?.name === "AbortError") {
|
||||
console.warn(error);
|
||||
return;
|
||||
}
|
||||
setAppState({ errorMessage: t("errors.importLibraryError") });
|
||||
}
|
||||
}}
|
||||
className="library-actions--load"
|
||||
/>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue