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