Import and export library from/to a file (#1940)

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Mohammed Salman 2020-07-27 15:29:19 +03:00 committed by GitHub
parent 7eff6893c5
commit ee8fa6aaad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 199 additions and 39 deletions

View file

@ -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(),
},
});
}

View file

@ -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;

View file

@ -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),
);
}}