fix: library init/import race conditions (#5101)

This commit is contained in:
David Luzar 2022-04-29 16:45:02 +02:00 committed by GitHub
parent 6a0f800716
commit d53ac2a61e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 248 additions and 133 deletions

View file

@ -257,6 +257,7 @@ import {
isPointHittingLinkIcon,
isLocalLink,
} from "../element/Hyperlink";
import { AbortError } from "../errors";
const defaultDeviceTypeContext: DeviceType = {
isMobile: false,
@ -703,21 +704,35 @@ class App extends React.Component<AppProps, AppState> {
window.history.replaceState({}, APP_NAME, `?${query.toString()}`);
}
const defaultStatus = "published";
this.setState({ isLibraryOpen: true });
try {
const request = await fetch(decodeURIComponent(url));
const blob = await request.blob();
const defaultStatus = "published";
const libraryItems = await loadLibraryFromBlob(blob, defaultStatus);
if (
token === this.id ||
window.confirm(
t("alerts.confirmAddLibrary", {
numShapes: libraryItems.length,
}),
)
) {
await this.library.importLibrary(libraryItems, defaultStatus);
}
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") });
@ -1674,6 +1689,11 @@ class App extends React.Component<AppProps, AppState> {
collaborators?: SceneData["collaborators"];
commitToHistory?: SceneData["commitToHistory"];
libraryItems?:
| ((
currentLibraryItems: LibraryItems,
) =>
| Required<SceneData>["libraryItems"]
| Promise<Required<SceneData>["libraryItems"]>)
| Required<SceneData>["libraryItems"]
| Promise<Required<SceneData>["libraryItems"]>;
}) => {
@ -1694,20 +1714,20 @@ class App extends React.Component<AppProps, AppState> {
}
if (sceneData.libraryItems) {
this.library.saveLibrary(
new Promise<LibraryItems>(async (resolve, reject) => {
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 sceneData.libraryItems,
"unpublished",
),
);
} catch {
reject(new Error(t("errors.importLibraryError")));
resolve(restoreLibraryItems(await nextItems, "unpublished"));
} catch (error: any) {
reject(error);
}
}),
);
});
});
}
},
);
@ -5280,11 +5300,14 @@ class App extends React.Component<AppProps, AppState> {
file?.type === MIME_TYPES.excalidrawlib ||
file?.name?.endsWith(".excalidrawlib")
) {
this.library
.importLibrary(file)
.catch((error) =>
this.setState({ isLoading: false, errorMessage: error.message }),
);
this.setState({ isLibraryOpen: true });
this.library.importLibrary(file).catch((error) => {
console.error(error);
this.setState({
isLoading: false,
errorMessage: t("errors.importLibraryError"),
});
});
// default: assume an Excalidraw file regardless of extension/MimeType
} else if (file) {
this.setState({ isLoading: true });

View file

@ -13,6 +13,10 @@
width: 100%;
margin: 2px 0;
.Spinner {
margin-right: 1rem;
}
button {
// 2px from the left to account for focus border of left-most button
margin: 0 2px;

View file

@ -139,7 +139,7 @@ export const LibraryMenu = ({
const nextItems = libraryItems.filter(
(item) => !selectedItems.includes(item.id),
);
library.saveLibrary(nextItems).catch(() => {
library.setLibrary(nextItems).catch(() => {
setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") });
});
setSelectedItems([]);
@ -170,7 +170,7 @@ export const LibraryMenu = ({
...libraryItems,
];
onAddToLibrary();
library.saveLibrary(nextItems).catch(() => {
library.setLibrary(nextItems).catch(() => {
setAppState({ errorMessage: t("alerts.errorAddingToLibrary") });
});
},
@ -220,7 +220,7 @@ export const LibraryMenu = ({
libItem.status = "published";
}
});
library.saveLibrary(nextLibItems);
library.setLibrary(nextLibItems);
},
[setShowPublishLibraryDialog, setPublishLibSuccess, selectedItems, library],
);
@ -229,7 +229,10 @@ export const LibraryMenu = ({
LibraryItem["id"] | null
>(null);
if (libraryItemsData.status === "loading") {
if (
libraryItemsData.status === "loading" &&
!libraryItemsData.isInitialized
) {
return (
<LibraryMenuWrapper ref={ref}>
<div className="layer-ui__library-message">
@ -255,7 +258,7 @@ export const LibraryMenu = ({
}
onError={(error) => window.alert(error)}
updateItemsInStorage={() =>
library.saveLibrary(libraryItemsData.libraryItems)
library.setLibrary(libraryItemsData.libraryItems)
}
onRemove={(id: string) =>
setSelectedItems(selectedItems.filter((_id) => _id !== id))
@ -264,6 +267,7 @@ export const LibraryMenu = ({
)}
{publishLibSuccess && renderPublishSuccess()}
<LibraryMenuItems
isLoading={libraryItemsData.status === "loading"}
libraryItems={libraryItemsData.libraryItems}
onRemoveFromLibrary={() =>
removeFromLibrary(libraryItemsData.libraryItems)

View file

@ -22,8 +22,10 @@ import { Tooltip } from "./Tooltip";
import "./LibraryMenuItems.scss";
import { VERSIONS } from "../constants";
import Spinner from "./Spinner";
const LibraryMenuItems = ({
isLoading,
libraryItems,
onRemoveFromLibrary,
onAddToLibrary,
@ -40,6 +42,7 @@ const LibraryMenuItems = ({
onPublish,
resetLibrary,
}: {
isLoading: boolean;
libraryItems: LibraryItems;
pendingElements: LibraryItem["elements"];
onRemoveFromLibrary: () => void;
@ -108,7 +111,8 @@ const LibraryMenuItems = ({
importLibraryFromJSON(library)
.catch(muteFSAbortError)
.catch((error) => {
setAppState({ errorMessage: error.message });
console.error(error);
setAppState({ errorMessage: t("errors.importLibraryError") });
});
}}
className="library-actions--load"
@ -125,7 +129,7 @@ const LibraryMenuItems = ({
onClick={async () => {
const libraryItems = itemsSelected
? items
: await library.loadLibrary();
: await library.getLatestLibrary();
saveLibraryAsJSON(libraryItems)
.catch(muteFSAbortError)
.catch((error) => {
@ -284,16 +288,20 @@ const LibraryMenuItems = ({
{showRemoveLibAlert && renderRemoveLibAlert()}
<div className="layer-ui__library-header" key="library-header">
{renderLibraryActions()}
<a
href={`${process.env.REACT_APP_LIBRARY_URL}?target=${
window.name || "_blank"
}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}&version=${
VERSIONS.excalidrawLibrary
}`}
target="_excalidraw_libraries"
>
{t("labels.libraries")}
</a>
{isLoading ? (
<Spinner />
) : (
<a
href={`${process.env.REACT_APP_LIBRARY_URL}?target=${
window.name || "_blank"
}&referrer=${referrer}&useHash=true&token=${id}&theme=${theme}&version=${
VERSIONS.excalidrawLibrary
}`}
target="_excalidraw_libraries"
>
{t("labels.libraries")}
</a>
)}
</div>
<Stack.Col
className="library-menu-items-container__items"