feat: Make library local to given excalidraw instance and allow consumer to control it (#3451)

* feat: dnt share library & attach to the excalidraw instance

* fix

* Add addToLibrary, resetLibrary and libraryItems in initialData

* remove comment

* handle errors & improve types

* remove resetLibrary and addToLibrary and add onLibraryChange prop

* set library cache to empty arrary on reset

* Add i18n for remove/add library

* Update src/locales/en.json

Co-authored-by: David Luzar <luzar.david@gmail.com>

* update docs

* Assign unique ID to
 each excalidraw component and remove csrfToken from library as its not needed

* tweaks

Co-authored-by: David Luzar <luzar.david@gmail.com>

* update

* tweak

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Aakansha Doshi 2021-04-21 23:38:24 +05:30 committed by GitHub
parent 46624cc953
commit 37d513ad59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 189 additions and 77 deletions

View file

@ -10,7 +10,6 @@ import { ActionManager } from "../actions/manager";
import { CLASSES } from "../constants";
import { exportCanvas } from "../data";
import { importLibraryFromJSON, saveLibraryAsJSON } from "../data/json";
import { Library } from "../data/library";
import { isTextElement, showSelectedShapeActions } from "../element";
import { NonDeletedExcalidrawElement } from "../element/types";
import { Language, t } from "../i18n";
@ -47,6 +46,7 @@ import Stack from "./Stack";
import { ToolButton } from "./ToolButton";
import { Tooltip } from "./Tooltip";
import { UserList } from "./UserList";
import Library from "../data/library";
interface LayerUIProps {
actionManager: ActionManager;
@ -73,6 +73,8 @@ interface LayerUIProps {
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
UIOptions: AppProps["UIOptions"];
focusContainer: () => void;
library: Library;
id: string;
}
const useOnClickOutside = (
@ -104,7 +106,7 @@ const useOnClickOutside = (
};
const LibraryMenuItems = ({
library,
libraryItems,
onRemoveFromLibrary,
onAddToLibrary,
onInsertShape,
@ -113,8 +115,10 @@ const LibraryMenuItems = ({
setLibraryItems,
libraryReturnUrl,
focusContainer,
library,
id,
}: {
library: LibraryItems;
libraryItems: LibraryItems;
pendingElements: LibraryItem;
onRemoveFromLibrary: (index: number) => void;
onInsertShape: (elements: LibraryItem) => void;
@ -123,9 +127,11 @@ const LibraryMenuItems = ({
setLibraryItems: (library: LibraryItems) => void;
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
focusContainer: () => void;
library: Library;
id: string;
}) => {
const isMobile = useIsMobile();
const numCells = library.length + (pendingElements.length > 0 ? 1 : 0);
const numCells = libraryItems.length + (pendingElements.length > 0 ? 1 : 0);
const CELLS_PER_ROW = isMobile ? 4 : 6;
const numRows = Math.max(1, Math.ceil(numCells / CELLS_PER_ROW));
const rows = [];
@ -143,7 +149,7 @@ const LibraryMenuItems = ({
aria-label={t("buttons.load")}
icon={load}
onClick={() => {
importLibraryFromJSON()
importLibraryFromJSON(library)
.then(() => {
// Close and then open to get the libraries updated
setAppState({ isLibraryOpen: false });
@ -155,7 +161,7 @@ const LibraryMenuItems = ({
});
}}
/>
{!!library.length && (
{!!libraryItems.length && (
<>
<ToolButton
key="export"
@ -164,7 +170,7 @@ const LibraryMenuItems = ({
aria-label={t("buttons.export")}
icon={exportFile}
onClick={() => {
saveLibraryAsJSON()
saveLibraryAsJSON(library)
.catch(muteFSAbortError)
.catch((error) => {
setAppState({ errorMessage: error.message });
@ -179,7 +185,7 @@ const LibraryMenuItems = ({
icon={trash}
onClick={() => {
if (window.confirm(t("alerts.resetLibrary"))) {
Library.resetLibrary();
library.resetLibrary();
setLibraryItems([]);
focusContainer();
}
@ -190,7 +196,7 @@ const LibraryMenuItems = ({
<a
href={`https://libraries.excalidraw.com?target=${
window.name || "_blank"
}&referrer=${referrer}&useHash=true&token=${Library.csrfToken}`}
}&referrer=${referrer}&useHash=true&token=${id}`}
target="_excalidraw_libraries"
>
{t("labels.libraries")}
@ -205,13 +211,13 @@ const LibraryMenuItems = ({
const shouldAddPendingElements: boolean =
pendingElements.length > 0 &&
!addedPendingElements &&
y + x >= library.length;
y + x >= libraryItems.length;
addedPendingElements = addedPendingElements || shouldAddPendingElements;
children.push(
<Stack.Col key={x}>
<LibraryUnit
elements={library[y + x]}
elements={libraryItems[y + x]}
pendingElements={
shouldAddPendingElements ? pendingElements : undefined
}
@ -219,7 +225,7 @@ const LibraryMenuItems = ({
onClick={
shouldAddPendingElements
? onAddToLibrary.bind(null, pendingElements)
: onInsertShape.bind(null, library[y + x])
: onInsertShape.bind(null, libraryItems[y + x])
}
/>
</Stack.Col>,
@ -247,6 +253,8 @@ const LibraryMenu = ({
setAppState,
libraryReturnUrl,
focusContainer,
library,
id,
}: {
pendingElements: LibraryItem;
onClickOutside: (event: MouseEvent) => void;
@ -255,6 +263,8 @@ const LibraryMenu = ({
setAppState: React.Component<any, AppState>["setState"];
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
focusContainer: () => void;
library: Library;
id: string;
}) => {
const ref = useRef<HTMLDivElement | null>(null);
useOnClickOutside(ref, (event) => {
@ -280,7 +290,7 @@ const LibraryMenu = ({
resolve("loading");
}, 100);
}),
Library.loadLibrary().then((items) => {
library.loadLibrary().then((items) => {
setLibraryItems(items);
setIsLoading("ready");
}),
@ -292,24 +302,33 @@ const LibraryMenu = ({
return () => {
clearTimeout(loadingTimerRef.current!);
};
}, []);
}, [library]);
const removeFromLibrary = useCallback(async (indexToRemove) => {
const items = await Library.loadLibrary();
const nextItems = items.filter((_, index) => index !== indexToRemove);
Library.saveLibrary(nextItems);
setLibraryItems(nextItems);
}, []);
const removeFromLibrary = useCallback(
async (indexToRemove) => {
const items = await library.loadLibrary();
const nextItems = items.filter((_, index) => index !== indexToRemove);
library.saveLibrary(nextItems).catch((error) => {
setLibraryItems(items);
setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") });
});
setLibraryItems(nextItems);
},
[library, setAppState],
);
const addToLibrary = useCallback(
async (elements: LibraryItem) => {
const items = await Library.loadLibrary();
const items = await library.loadLibrary();
const nextItems = [...items, elements];
onAddToLibrary();
Library.saveLibrary(nextItems);
library.saveLibrary(nextItems).catch((error) => {
setLibraryItems(items);
setAppState({ errorMessage: t("alerts.errorAddingToLibrary") });
});
setLibraryItems(nextItems);
},
[onAddToLibrary],
[onAddToLibrary, library, setAppState],
);
return loadingState === "preloading" ? null : (
@ -320,7 +339,7 @@ const LibraryMenu = ({
</div>
) : (
<LibraryMenuItems
library={libraryItems}
libraryItems={libraryItems}
onRemoveFromLibrary={removeFromLibrary}
onAddToLibrary={addToLibrary}
onInsertShape={onInsertShape}
@ -329,6 +348,8 @@ const LibraryMenu = ({
setLibraryItems={setLibraryItems}
libraryReturnUrl={libraryReturnUrl}
focusContainer={focusContainer}
library={library}
id={id}
/>
)}
</Island>
@ -355,6 +376,8 @@ const LayerUI = ({
libraryReturnUrl,
UIOptions,
focusContainer,
library,
id,
}: LayerUIProps) => {
const isMobile = useIsMobile();
@ -526,6 +549,8 @@ const LayerUI = ({
setAppState={setAppState}
libraryReturnUrl={libraryReturnUrl}
focusContainer={focusContainer}
library={library}
id={id}
/>
) : null;