123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- import React, { useCallback, useState } from "react";
- import { saveLibraryAsJSON } from "../data/json";
- import Library, { libraryItemsAtom } from "../data/library";
- import { t } from "../i18n";
- import { AppState, LibraryItem, LibraryItems } from "../types";
- import {
- DotsIcon,
- ExportIcon,
- LoadIcon,
- publishIcon,
- TrashIcon,
- } from "./icons";
- import { ToolButton } from "./ToolButton";
- import { fileOpen } from "../data/filesystem";
- import { muteFSAbortError } from "../utils";
- import { atom, useAtom } from "jotai";
- import { jotaiScope } from "../jotai";
- import ConfirmDialog from "./ConfirmDialog";
- import PublishLibrary from "./PublishLibrary";
- import { Dialog } from "./Dialog";
- import DropdownMenu from "./dropdownMenu/DropdownMenu";
- export const isLibraryMenuOpenAtom = atom(false);
- const getSelectedItems = (
- libraryItems: LibraryItems,
- selectedItems: LibraryItem["id"][],
- ) => libraryItems.filter((item) => selectedItems.includes(item.id));
- export const LibraryMenuHeader: React.FC<{
- setAppState: React.Component<any, AppState>["setState"];
- selectedItems: LibraryItem["id"][];
- library: Library;
- onRemoveFromLibrary: () => void;
- resetLibrary: () => void;
- onSelectItems: (items: LibraryItem["id"][]) => void;
- appState: AppState;
- }> = ({
- setAppState,
- selectedItems,
- library,
- onRemoveFromLibrary,
- resetLibrary,
- onSelectItems,
- appState,
- }) => {
- const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
- const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom(
- isLibraryMenuOpenAtom,
- );
- const renderRemoveLibAlert = useCallback(() => {
- const content = selectedItems.length
- ? t("alerts.removeItemsFromsLibrary", { count: selectedItems.length })
- : t("alerts.resetLibrary");
- const title = selectedItems.length
- ? t("confirmDialog.removeItemsFromLib")
- : t("confirmDialog.resetLibrary");
- return (
- <ConfirmDialog
- onConfirm={() => {
- if (selectedItems.length) {
- onRemoveFromLibrary();
- } else {
- resetLibrary();
- }
- setShowRemoveLibAlert(false);
- }}
- onCancel={() => {
- setShowRemoveLibAlert(false);
- }}
- title={title}
- >
- <p>{content}</p>
- </ConfirmDialog>
- );
- }, [selectedItems, onRemoveFromLibrary, resetLibrary]);
- const [showRemoveLibAlert, setShowRemoveLibAlert] = useState(false);
- const itemsSelected = !!selectedItems.length;
- const items = itemsSelected
- ? libraryItemsData.libraryItems.filter((item) =>
- selectedItems.includes(item.id),
- )
- : libraryItemsData.libraryItems;
- const resetLabel = itemsSelected
- ? t("buttons.remove")
- : t("buttons.resetLibrary");
- const [showPublishLibraryDialog, setShowPublishLibraryDialog] =
- useState(false);
- const [publishLibSuccess, setPublishLibSuccess] = useState<null | {
- url: string;
- authorName: string;
- }>(null);
- const renderPublishSuccess = useCallback(() => {
- return (
- <Dialog
- onCloseRequest={() => setPublishLibSuccess(null)}
- title={t("publishSuccessDialog.title")}
- className="publish-library-success"
- small={true}
- >
- <p>
- {t("publishSuccessDialog.content", {
- authorName: publishLibSuccess!.authorName,
- })}{" "}
- <a
- href={publishLibSuccess?.url}
- target="_blank"
- rel="noopener noreferrer"
- >
- {t("publishSuccessDialog.link")}
- </a>
- </p>
- <ToolButton
- type="button"
- title={t("buttons.close")}
- aria-label={t("buttons.close")}
- label={t("buttons.close")}
- onClick={() => setPublishLibSuccess(null)}
- data-testid="publish-library-success-close"
- className="publish-library-success-close"
- />
- </Dialog>
- );
- }, [setPublishLibSuccess, publishLibSuccess]);
- const onPublishLibSuccess = useCallback(
- (data: { url: string; authorName: string }, libraryItems: LibraryItems) => {
- setShowPublishLibraryDialog(false);
- setPublishLibSuccess({ url: data.url, authorName: data.authorName });
- const nextLibItems = libraryItems.slice();
- nextLibItems.forEach((libItem) => {
- if (selectedItems.includes(libItem.id)) {
- libItem.status = "published";
- }
- });
- library.setLibrary(nextLibItems);
- },
- [setShowPublishLibraryDialog, setPublishLibSuccess, selectedItems, library],
- );
- const onLibraryImport = async () => {
- try {
- await library.updateLibrary({
- libraryItems: fileOpen({
- description: "Excalidraw library files",
- // ToDo: Be over-permissive until https://bugs.webkit.org/show_bug.cgi?id=34442
- // gets resolved. Else, iOS users cannot open `.excalidraw` files.
- /*
- extensions: [".json", ".excalidrawlib"],
- */
- }),
- merge: true,
- openLibraryMenu: true,
- });
- } catch (error: any) {
- if (error?.name === "AbortError") {
- console.warn(error);
- return;
- }
- setAppState({ errorMessage: t("errors.importLibraryError") });
- }
- };
- const onLibraryExport = async () => {
- const libraryItems = itemsSelected
- ? items
- : await library.getLatestLibrary();
- saveLibraryAsJSON(libraryItems)
- .catch(muteFSAbortError)
- .catch((error) => {
- setAppState({ errorMessage: error.message });
- });
- };
- const renderLibraryMenu = () => {
- return (
- <DropdownMenu open={isLibraryMenuOpen}>
- <DropdownMenu.Trigger
- className="Sidebar__dropdown-btn"
- onToggle={() => setIsLibraryMenuOpen(!isLibraryMenuOpen)}
- >
- {DotsIcon}
- </DropdownMenu.Trigger>
- <DropdownMenu.Content
- onClickOutside={() => setIsLibraryMenuOpen(false)}
- onSelect={() => setIsLibraryMenuOpen(false)}
- className="library-menu"
- >
- {!itemsSelected && (
- <DropdownMenu.Item
- onSelect={onLibraryImport}
- icon={LoadIcon}
- data-testid="lib-dropdown--load"
- >
- {t("buttons.load")}
- </DropdownMenu.Item>
- )}
- {!!items.length && (
- <DropdownMenu.Item
- onSelect={onLibraryExport}
- icon={ExportIcon}
- data-testid="lib-dropdown--export"
- >
- {t("buttons.export")}
- </DropdownMenu.Item>
- )}
- {!!items.length && (
- <DropdownMenu.Item
- onSelect={() => setShowRemoveLibAlert(true)}
- icon={TrashIcon}
- >
- {resetLabel}
- </DropdownMenu.Item>
- )}
- {itemsSelected && (
- <DropdownMenu.Item
- icon={publishIcon}
- onSelect={() => setShowPublishLibraryDialog(true)}
- data-testid="lib-dropdown--remove"
- >
- {t("buttons.publishLibrary")}
- </DropdownMenu.Item>
- )}
- </DropdownMenu.Content>
- </DropdownMenu>
- );
- };
- return (
- <div style={{ position: "relative" }}>
- {renderLibraryMenu()}
- {selectedItems.length > 0 && (
- <div className="library-actions-counter">{selectedItems.length}</div>
- )}
- {showRemoveLibAlert && renderRemoveLibAlert()}
- {showPublishLibraryDialog && (
- <PublishLibrary
- onClose={() => setShowPublishLibraryDialog(false)}
- libraryItems={getSelectedItems(
- libraryItemsData.libraryItems,
- selectedItems,
- )}
- appState={appState}
- onSuccess={(data) =>
- onPublishLibSuccess(data, libraryItemsData.libraryItems)
- }
- onError={(error) => window.alert(error)}
- updateItemsInStorage={() =>
- library.setLibrary(libraryItemsData.libraryItems)
- }
- onRemove={(id: string) =>
- onSelectItems(selectedItems.filter((_id) => _id !== id))
- }
- />
- )}
- {publishLibSuccess && renderPublishSuccess()}
- </div>
- );
- };
|