mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Import and export library from/to a file (#1940)
Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
7eff6893c5
commit
ee8fa6aaad
11 changed files with 199 additions and 39 deletions
|
@ -143,6 +143,7 @@ import { actionFinalize, actionDeleteSelected } from "../actions";
|
|||
import {
|
||||
restoreUsernameFromLocalStorage,
|
||||
saveUsernameToLocalStorage,
|
||||
loadLibrary,
|
||||
} from "../data/localStorage";
|
||||
|
||||
import throttle from "lodash.throttle";
|
||||
|
@ -153,6 +154,7 @@ import {
|
|||
isElementInGroup,
|
||||
getSelectedGroupIdForElement,
|
||||
} from "../groups";
|
||||
import { Library } from "../data/library";
|
||||
|
||||
/**
|
||||
* @param func handler taking at most single parameter (event).
|
||||
|
@ -3206,7 +3208,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||
|
||||
private handleCanvasOnDrop = (event: React.DragEvent<HTMLCanvasElement>) => {
|
||||
const libraryShapes = event.dataTransfer.getData(
|
||||
"application/vnd.excalidraw.json",
|
||||
"application/vnd.excalidrawlib+json",
|
||||
);
|
||||
if (libraryShapes !== "") {
|
||||
this.addElementsFromPasteOrLibrary(
|
||||
|
@ -3237,6 +3239,17 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||
.catch((error) => {
|
||||
this.setState({ isLoading: false, errorMessage: error.message });
|
||||
});
|
||||
} else if (
|
||||
file?.type === "application/vnd.excalidrawlib+json" ||
|
||||
file?.name.endsWith(".excalidrawlib")
|
||||
) {
|
||||
Library.importLibrary(file)
|
||||
.then(() => {
|
||||
this.setState({ isLibraryOpen: false });
|
||||
})
|
||||
.catch((error) =>
|
||||
this.setState({ isLoading: false, errorMessage: error.message }),
|
||||
);
|
||||
} else {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
|
@ -3484,6 +3497,7 @@ declare global {
|
|||
setState: React.Component<any, AppState>["setState"];
|
||||
history: SceneHistory;
|
||||
app: InstanceType<typeof App>;
|
||||
library: ReturnType<typeof loadLibrary>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3506,6 +3520,9 @@ if (
|
|||
history: {
|
||||
get: () => history,
|
||||
},
|
||||
library: {
|
||||
get: () => loadLibrary(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,8 @@ import { showSelectedShapeActions } from "../element";
|
|||
import { calculateScrollCenter, getSelectedElements } from "../scene";
|
||||
import { exportCanvas } from "../data";
|
||||
|
||||
import { AppState, LibraryItems } from "../types";
|
||||
import {
|
||||
NonDeletedExcalidrawElement,
|
||||
ExcalidrawElement,
|
||||
NonDeleted,
|
||||
} from "../element/types";
|
||||
import { AppState, LibraryItems, LibraryItem } from "../types";
|
||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { Island } from "./Island";
|
||||
|
@ -37,13 +33,16 @@ import { ErrorDialog } from "./ErrorDialog";
|
|||
import { ShortcutsDialog } from "./ShortcutsDialog";
|
||||
import { LoadingMessage } from "./LoadingMessage";
|
||||
import { CLASSES } from "../constants";
|
||||
import { shield } from "./icons";
|
||||
import { shield, exportFile, load } from "./icons";
|
||||
import { GitHubCorner } from "./GitHubCorner";
|
||||
import { Tooltip } from "./Tooltip";
|
||||
|
||||
import "./LayerUI.scss";
|
||||
import { LibraryUnit } from "./LibraryUnit";
|
||||
import { loadLibrary, saveLibrary } from "../data/localStorage";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import { saveLibraryAsJSON, importLibraryFromJSON } from "../data/json";
|
||||
import { muteFSAbortError } from "../utils";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
|
@ -55,7 +54,7 @@ interface LayerUIProps {
|
|||
onUsernameChange: (username: string) => void;
|
||||
onRoomDestroy: () => void;
|
||||
onLockToggle: () => void;
|
||||
onInsertShape: (elements: readonly NonDeleted<ExcalidrawElement>[]) => void;
|
||||
onInsertShape: (elements: LibraryItem) => void;
|
||||
zenModeEnabled: boolean;
|
||||
toggleZenMode: () => void;
|
||||
lng: string;
|
||||
|
@ -95,13 +94,15 @@ const LibraryMenuItems = ({
|
|||
onAddToLibrary,
|
||||
onInsertShape,
|
||||
pendingElements,
|
||||
setAppState,
|
||||
}: {
|
||||
library: LibraryItems;
|
||||
pendingElements: NonDeleted<ExcalidrawElement>[];
|
||||
pendingElements: LibraryItem;
|
||||
onClickOutside: (event: MouseEvent) => void;
|
||||
onRemoveFromLibrary: (index: number) => void;
|
||||
onInsertShape: (elements: readonly NonDeleted<ExcalidrawElement>[]) => void;
|
||||
onAddToLibrary: (elements: NonDeleted<ExcalidrawElement>[]) => void;
|
||||
onInsertShape: (elements: LibraryItem) => void;
|
||||
onAddToLibrary: (elements: LibraryItem) => void;
|
||||
setAppState: any;
|
||||
}) => {
|
||||
const isMobile = useIsMobile();
|
||||
const numCells = library.length + (pendingElements.length > 0 ? 1 : 0);
|
||||
|
@ -110,6 +111,44 @@ const LibraryMenuItems = ({
|
|||
const rows = [];
|
||||
let addedPendingElements = false;
|
||||
|
||||
rows.push(
|
||||
<Stack.Row align="center" gap={1} key={"actions"}>
|
||||
<ToolButton
|
||||
key="import"
|
||||
type="button"
|
||||
title={t("buttons.load")}
|
||||
aria-label={t("buttons.load")}
|
||||
icon={load}
|
||||
onClick={() => {
|
||||
importLibraryFromJSON()
|
||||
.then(() => {
|
||||
// Maybe we should close and open the menu so that the items get updated.
|
||||
// But for now we just close the menu.
|
||||
setAppState({ isLibraryOpen: false });
|
||||
})
|
||||
.catch(muteFSAbortError)
|
||||
.catch((error) => {
|
||||
setAppState({ errorMessage: error.message });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ToolButton
|
||||
key="export"
|
||||
type="button"
|
||||
title={t("buttons.export")}
|
||||
aria-label={t("buttons.export")}
|
||||
icon={exportFile}
|
||||
onClick={() => {
|
||||
saveLibraryAsJSON()
|
||||
.catch(muteFSAbortError)
|
||||
.catch((error) => {
|
||||
setAppState({ errorMessage: error.message });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Stack.Row>,
|
||||
);
|
||||
|
||||
for (let row = 0; row < numRows; row++) {
|
||||
const i = CELLS_PER_ROW * row;
|
||||
const children = [];
|
||||
|
@ -156,11 +195,13 @@ const LibraryMenu = ({
|
|||
onInsertShape,
|
||||
pendingElements,
|
||||
onAddToLibrary,
|
||||
setAppState,
|
||||
}: {
|
||||
pendingElements: NonDeleted<ExcalidrawElement>[];
|
||||
pendingElements: LibraryItem;
|
||||
onClickOutside: (event: MouseEvent) => void;
|
||||
onInsertShape: (elements: readonly NonDeleted<ExcalidrawElement>[]) => void;
|
||||
onInsertShape: (elements: LibraryItem) => void;
|
||||
onAddToLibrary: () => void;
|
||||
setAppState: any;
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useOnClickOutside(ref, onClickOutside);
|
||||
|
@ -202,7 +243,7 @@ const LibraryMenu = ({
|
|||
}, []);
|
||||
|
||||
const addToLibrary = useCallback(
|
||||
async (elements: NonDeleted<ExcalidrawElement>[]) => {
|
||||
async (elements: LibraryItem) => {
|
||||
const items = await loadLibrary();
|
||||
const nextItems = [...items, elements];
|
||||
onAddToLibrary();
|
||||
|
@ -226,6 +267,7 @@ const LibraryMenu = ({
|
|||
onAddToLibrary={addToLibrary}
|
||||
onInsertShape={onInsertShape}
|
||||
pendingElements={pendingElements}
|
||||
setAppState={setAppState}
|
||||
/>
|
||||
)}
|
||||
</Island>
|
||||
|
@ -372,6 +414,7 @@ const LayerUI = ({
|
|||
onClickOutside={closeLibrary}
|
||||
onInsertShape={onInsertShape}
|
||||
onAddToLibrary={deselectItems}
|
||||
setAppState={setAppState}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { exportToSvg } from "../scene/export";
|
||||
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
||||
import { close } from "../components/icons";
|
||||
|
||||
import "./LibraryUnit.scss";
|
||||
import { t } from "../i18n";
|
||||
import useIsMobile from "../is-mobile";
|
||||
import { LibraryItem } from "../types";
|
||||
|
||||
// fa-plus
|
||||
const PLUS_ICON = (
|
||||
|
@ -20,8 +20,8 @@ export const LibraryUnit = ({
|
|||
onRemoveFromLibrary,
|
||||
onClick,
|
||||
}: {
|
||||
elements?: NonDeleted<ExcalidrawElement>[];
|
||||
pendingElements?: NonDeleted<ExcalidrawElement>[];
|
||||
elements?: LibraryItem;
|
||||
pendingElements?: LibraryItem;
|
||||
onRemoveFromLibrary: () => void;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
|
@ -75,7 +75,7 @@ export const LibraryUnit = ({
|
|||
onDragStart={(event) => {
|
||||
setIsHovered(false);
|
||||
event.dataTransfer.setData(
|
||||
"application/vnd.excalidraw.json",
|
||||
"application/vnd.excalidrawlib+json",
|
||||
JSON.stringify(elements),
|
||||
);
|
||||
}}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue