import { useCallback, useState } from "react"; import { t } from "../i18n"; import { jotaiScope } from "../jotai"; import { AppState, LibraryItem, LibraryItems } from "../types"; import { useApp, useExcalidrawAppState, useExcalidrawSetAppState } from "./App"; import { saveLibraryAsJSON } from "../data/json"; import Library, { libraryItemsAtom } from "../data/library"; import { DotsIcon, ExportIcon, LoadIcon, publishIcon, TrashIcon, } from "./icons"; import { ToolButton } from "./ToolButton"; import { fileOpen } from "../data/filesystem"; import { muteFSAbortError } from "../utils"; import { useAtom } from "jotai"; import ConfirmDialog from "./ConfirmDialog"; import PublishLibrary from "./PublishLibrary"; import { Dialog } from "./Dialog"; import DropdownMenu from "./dropdownMenu/DropdownMenu"; import { isLibraryMenuOpenAtom } from "./LibraryMenu"; const getSelectedItems = ( libraryItems: LibraryItems, selectedItems: LibraryItem["id"][], ) => libraryItems.filter((item) => selectedItems.includes(item.id)); export const LibraryDropdownMenuButton: React.FC<{ setAppState: React.Component["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, jotaiScope, ); 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 ( { if (selectedItems.length) { onRemoveFromLibrary(); } else { resetLibrary(); } setShowRemoveLibAlert(false); }} onCancel={() => { setShowRemoveLibAlert(false); }} title={title} >

{content}

); }, [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); const renderPublishSuccess = useCallback(() => { return ( setPublishLibSuccess(null)} title={t("publishSuccessDialog.title")} className="publish-library-success" small={true} >

{t("publishSuccessDialog.content", { authorName: publishLibSuccess!.authorName, })}{" "} {t("publishSuccessDialog.link")}

setPublishLibSuccess(null)} data-testid="publish-library-success-close" className="publish-library-success-close" />
); }, [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 ( setIsLibraryMenuOpen(!isLibraryMenuOpen)} > {DotsIcon} setIsLibraryMenuOpen(false)} onSelect={() => setIsLibraryMenuOpen(false)} className="library-menu" > {!itemsSelected && ( {t("buttons.load")} )} {!!items.length && ( {t("buttons.export")} )} {!!items.length && ( setShowRemoveLibAlert(true)} icon={TrashIcon} > {resetLabel} )} {itemsSelected && ( setShowPublishLibraryDialog(true)} data-testid="lib-dropdown--remove" > {t("buttons.publishLibrary")} )} ); }; return (
{renderLibraryMenu()} {selectedItems.length > 0 && (
{selectedItems.length}
)} {showRemoveLibAlert && renderRemoveLibAlert()} {showPublishLibraryDialog && ( 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()}
); }; export const LibraryDropdownMenu = ({ selectedItems, onSelectItems, }: { selectedItems: LibraryItem["id"][]; onSelectItems: (id: LibraryItem["id"][]) => void; }) => { const { library } = useApp(); const appState = useExcalidrawAppState(); const setAppState = useExcalidrawSetAppState(); const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope); const removeFromLibrary = useCallback( async (libraryItems: LibraryItems) => { const nextItems = libraryItems.filter( (item) => !selectedItems.includes(item.id), ); library.setLibrary(nextItems).catch(() => { setAppState({ errorMessage: t("alerts.errorRemovingFromLibrary") }); }); onSelectItems([]); }, [library, setAppState, selectedItems, onSelectItems], ); const resetLibrary = useCallback(() => { library.resetLibrary(); }, [library]); return ( removeFromLibrary(libraryItemsData.libraryItems) } resetLibrary={resetLibrary} /> ); };