/* * Copyright (C) Contributors to the Suwayomi project * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import CheckBoxOutlineBlank from '@mui/icons-material/CheckBoxOutlineBlank'; import Delete from '@mui/icons-material/Delete'; import Download from '@mui/icons-material/Download'; import RemoveDone from '@mui/icons-material/RemoveDone'; import Done from '@mui/icons-material/Done'; import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; import Label from '@mui/icons-material/Label'; import { useMemo, useState } from 'react'; import SyncAltIcon from '@mui/icons-material/SyncAlt'; import SyncIcon from '@mui/icons-material/Sync'; import Dialog from '@mui/material/Dialog'; import { AwaitableComponent } from 'awaitable-component'; import { useLingui } from '@lingui/react/macro'; import { Mangas } from '@/features/manga/services/Mangas.ts'; import type { SelectableCollectionReturnType } from '@/base/collection/hooks/useSelectableCollection.ts'; import { MenuItem } from '@/base/components/menu/MenuItem.tsx'; import { createGetMenuItemTitle, createIsMenuItemDisabled, createShouldShowMenuItem, } from '@/base/components/menu/Menu.utils.ts'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { TrackManga } from '@/features/tracker/components/TrackManga.tsx'; import { ChaptersDownloadActionMenuItems } from '@/features/chapter/components/actions/ChaptersDownloadActionMenuItems.tsx'; import { NestedMenuItem } from '@/base/components/menu/NestedMenuItem.tsx'; import type { MangaChapterStatFieldsFragment } from '@/lib/graphql/generated/graphql.ts'; import type { MangaAction, MangaDownloadInfo, MangaIdInfo, MangaInLibraryInfo, MangaSourceIdInfo, MangaTitleInfo, MangaUnreadInfo, } from '@/features/manga/Manga.types.ts'; import { MANGA_ACTION_TO_TRANSLATION } from '@/features/manga/Manga.constants.ts'; import { CategorySelect } from '@/features/category/components/CategorySelect.tsx'; import { STABLE_EMPTY_ARRAY } from '@/base/Base.constants.ts'; type BaseProps = { onClose: () => void; setHideMenu: (hide: boolean) => void }; export type SingleModeProps = { manga: MangaIdInfo & MangaTitleInfo & MangaSourceIdInfo & MangaDownloadInfo & MangaUnreadInfo & Pick; handleSelection?: SelectableCollectionReturnType['handleSelection']; }; type SelectModeProps = { selectedMangas: MangaChapterStatFieldsFragment[]; }; type Props = | (BaseProps & SingleModeProps & PropertiesNever) | (BaseProps & PropertiesNever & SelectModeProps); export const MangaActionMenuItems = ({ manga, handleSelection, selectedMangas = STABLE_EMPTY_ARRAY, onClose, setHideMenu, }: Props) => { const { t } = useLingui(); const [isTrackDialogOpen, setIsTrackDialogOpen] = useState(false); const isSingleMode = !!manga; const getMenuItemTitle = createGetMenuItemTitle(isSingleMode, MANGA_ACTION_TO_TRANSLATION); const shouldShowMenuItem = createShouldShowMenuItem(isSingleMode); const isMenuItemDisabled = createIsMenuItemDisabled(isSingleMode); const isDownloadable = !!manga && !!manga.chapters.totalCount && !Mangas.isFullyDownloaded(manga); const hasDownloadedChapters = manga && Mangas.isPartiallyDownloaded(manga); const hasUnreadChapters = manga && !!manga.chapters.totalCount && !Mangas.isFullyRead(manga); const hasReadChapters = !!manga && Mangas.isPartiallyRead(manga); const handleSelect = () => { handleSelection?.(manga.id, true); onClose(); }; const performAction = (action: MangaAction, mangas: MangaIdInfo[]) => { Mangas.performAction(action, manga ? [manga.id] : Mangas.getIds(mangas), { wasManuallyMarkedAsRead: true, }).catch(defaultPromiseErrorHandler(`MangaActionMenuItems:performAction(${action})`)); onClose(); }; const { downloadableMangas, downloadedMangas, unreadMangas, readMangas } = useMemo( () => ({ downloadableMangas: [ ...Mangas.getNotDownloaded(selectedMangas), ...Mangas.getPartiallyDownloaded(selectedMangas), ], downloadedMangas: [ ...Mangas.getPartiallyDownloaded(selectedMangas), ...Mangas.getFullyDownloaded(selectedMangas), ], unreadMangas: [...Mangas.getUnread(selectedMangas), ...Mangas.getPartiallyRead(selectedMangas)], readMangas: [...Mangas.getPartiallyRead(selectedMangas), ...Mangas.getFullyRead(selectedMangas)], }), [selectedMangas], ); return ( <> {!!handleSelection && isSingleMode && ( )} {shouldShowMenuItem(isDownloadable) && ( )} {shouldShowMenuItem(hasDownloadedChapters) && ( performAction('delete', downloadedMangas)} title={getMenuItemTitle('delete', downloadedMangas.length)} /> )} {shouldShowMenuItem(hasUnreadChapters) && ( performAction('mark_as_read', unreadMangas)} title={getMenuItemTitle('mark_as_read', unreadMangas.length)} /> )} {shouldShowMenuItem(hasReadChapters) && ( performAction('mark_as_unread', readMangas)} title={getMenuItemTitle('mark_as_unread', readMangas.length)} /> )} performAction('migrate', selectedMangas)} /> {isSingleMode && ( { setIsTrackDialogOpen(true); setHideMenu(true); }} Icon={SyncIcon} title={getMenuItemTitle('track', selectedMangas.length)} /> )} { AwaitableComponent.show(CategorySelect, { mangaId: manga?.id, mangaIds: !isSingleMode ? Mangas.getIds(selectedMangas) : undefined, addToLibrary: false, }); setHideMenu(true); onClose(); }} Icon={Label} title={getMenuItemTitle('change_categories', selectedMangas.length)} /> performAction('remove_from_library', selectedMangas)} Icon={FavoriteBorderIcon} title={getMenuItemTitle('remove_from_library', selectedMangas.length)} /> {isTrackDialogOpen && ( { setIsTrackDialogOpen(false); onClose(); }} > )} ); };