Merge manga action menus (#527)
This commit is contained in:
@@ -12,7 +12,6 @@ import Typography from '@mui/material/Typography';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Avatar, Box, CardContent, Stack, styled, Tooltip } from '@mui/material';
|
import { Avatar, Box, CardContent, Stack, styled, Tooltip } from '@mui/material';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import React from 'react';
|
|
||||||
import PopupState, { bindMenu } from 'material-ui-popup-state';
|
import PopupState, { bindMenu } from 'material-ui-popup-state';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
import { GridLayout, useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
||||||
@@ -21,7 +20,8 @@ import { TManga, TPartialManga } from '@/typings.ts';
|
|||||||
import { ContinueReadingButton } from '@/components/manga/ContinueReadingButton.tsx';
|
import { ContinueReadingButton } from '@/components/manga/ContinueReadingButton.tsx';
|
||||||
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
||||||
import { MangaOptionButton } from '@/components/manga/MangaOptionButton.tsx';
|
import { MangaOptionButton } from '@/components/manga/MangaOptionButton.tsx';
|
||||||
import { MangaActionMenu } from '@/components/manga/MangaActionMenu.tsx';
|
import { MangaActionMenuItems, SingleModeProps } from '@/components/manga/MangaActionMenuItems.tsx';
|
||||||
|
import { Menu } from '@/components/manga/Menu.tsx';
|
||||||
|
|
||||||
const BottomGradient = styled('div')({
|
const BottomGradient = styled('div')({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -270,11 +270,16 @@ export const MangaCard = (props: IProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Link>
|
</Link>
|
||||||
{!!handleSelection && popupState.isOpen && (
|
{!!handleSelection && popupState.isOpen && (
|
||||||
<MangaActionMenu
|
<Menu {...bindMenu(popupState)}>
|
||||||
{...bindMenu(popupState)}
|
{(onClose, setHideMenu) => (
|
||||||
manga={manga as React.ComponentProps<typeof MangaActionMenu>['manga']}
|
<MangaActionMenuItems
|
||||||
handleSelection={handleSelection}
|
manga={manga as SingleModeProps['manga']}
|
||||||
/>
|
handleSelection={handleSelection}
|
||||||
|
onClose={onClose}
|
||||||
|
setHideMenu={setHideMenu}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -380,11 +385,16 @@ export const MangaCard = (props: IProps) => {
|
|||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
</Card>
|
</Card>
|
||||||
{!!handleSelection && popupState.isOpen && (
|
{!!handleSelection && popupState.isOpen && (
|
||||||
<MangaActionMenu
|
<Menu {...bindMenu(popupState)}>
|
||||||
{...bindMenu(popupState)}
|
{(onClose, setHideMenu) => (
|
||||||
manga={manga as React.ComponentProps<typeof MangaActionMenu>['manga']}
|
<MangaActionMenuItems
|
||||||
handleSelection={handleSelection}
|
manga={manga as SingleModeProps['manga']}
|
||||||
/>
|
handleSelection={handleSelection}
|
||||||
|
onClose={onClose}
|
||||||
|
setHideMenu={setHideMenu}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 Menu from '@mui/material/Menu';
|
|
||||||
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 { useTranslation } from 'react-i18next';
|
|
||||||
import { bindMenu } from 'material-ui-popup-state';
|
|
||||||
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
|
||||||
import Label from '@mui/icons-material/Label';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { MangaAction, MangaDownloadInfo, Mangas, MangaUnreadInfo } from '@/lib/data/Mangas.ts';
|
|
||||||
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
|
||||||
import { CategorySelect } from '@/components/navbar/action/CategorySelect.tsx';
|
|
||||||
import { MenuItem } from '@/components/manga/MenuItem.tsx';
|
|
||||||
|
|
||||||
export const MangaActionMenu = ({
|
|
||||||
manga,
|
|
||||||
handleSelection,
|
|
||||||
...bindMenuProps
|
|
||||||
}: {
|
|
||||||
manga: Pick<TManga, 'id'> & MangaDownloadInfo & MangaUnreadInfo;
|
|
||||||
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
|
|
||||||
} & ReturnType<typeof bindMenu>) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [isCategorySelectOpen, setIsCategorySelectOpen] = useState(false);
|
|
||||||
|
|
||||||
const isFullyDownloaded = manga.downloadCount === manga.chapters.totalCount;
|
|
||||||
const hasDownloadedChapters = !!manga.downloadCount;
|
|
||||||
const hasUnreadChapters = !!manga.unreadCount;
|
|
||||||
const hasReadChapters = manga.unreadCount !== manga.chapters.totalCount;
|
|
||||||
|
|
||||||
const handleSelect = () => {
|
|
||||||
handleSelection?.(manga.id, true);
|
|
||||||
bindMenuProps.onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const performAction = (action: MangaAction) => {
|
|
||||||
Mangas.performAction(action, [manga.id], {
|
|
||||||
wasManuallyMarkedAsRead: true,
|
|
||||||
}).catch(() => {});
|
|
||||||
|
|
||||||
bindMenuProps.onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Menu {...bindMenuProps} open={bindMenuProps.open && !isCategorySelectOpen}>
|
|
||||||
{!!handleSelection && (
|
|
||||||
<MenuItem
|
|
||||||
onClick={handleSelect}
|
|
||||||
Icon={CheckBoxOutlineBlank}
|
|
||||||
title={t('chapter.action.label.select')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!isFullyDownloaded && (
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => performAction('download')}
|
|
||||||
Icon={Download}
|
|
||||||
title={t('chapter.action.download.add.label.action')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{hasDownloadedChapters && (
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => performAction('delete')}
|
|
||||||
Icon={Delete}
|
|
||||||
title={t('chapter.action.download.delete.label.action')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{hasUnreadChapters && (
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => performAction('mark_as_read')}
|
|
||||||
Icon={Done}
|
|
||||||
title={t('chapter.action.mark_as_read.add.label.action.current')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{hasReadChapters && (
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => performAction('mark_as_unread')}
|
|
||||||
Icon={RemoveDone}
|
|
||||||
title={t('chapter.action.mark_as_read.remove.label.action')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => setIsCategorySelectOpen(true)}
|
|
||||||
Icon={Label}
|
|
||||||
title={t('manga.action.category.label.action')}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => performAction('remove_from_library')}
|
|
||||||
Icon={FavoriteBorderIcon}
|
|
||||||
title={t('manga.action.library.remove.label.action')}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
{isCategorySelectOpen && (
|
|
||||||
<CategorySelect
|
|
||||||
open={isCategorySelectOpen}
|
|
||||||
onClose={() => {
|
|
||||||
setIsCategorySelectOpen(false);
|
|
||||||
bindMenuProps.onClose();
|
|
||||||
}}
|
|
||||||
mangaId={manga.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
169
src/components/manga/MangaActionMenuItems.tsx
Normal file
169
src/components/manga/MangaActionMenuItems.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useTranslation } from 'react-i18next';
|
||||||
|
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
||||||
|
import Label from '@mui/icons-material/Label';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { t as translate } from 'i18next';
|
||||||
|
import { TManga } from '@/typings.ts';
|
||||||
|
import { actionToTranslationKey, MangaAction, MangaDownloadInfo, Mangas, MangaUnreadInfo } from '@/lib/data/Mangas.ts';
|
||||||
|
import { SelectableCollectionReturnType } from '@/components/collection/useSelectableCollection.ts';
|
||||||
|
import { CategorySelect } from '@/components/navbar/action/CategorySelect.tsx';
|
||||||
|
import { MenuItem } from '@/components/manga/MenuItem.tsx';
|
||||||
|
|
||||||
|
const ACTION_DISABLES_SELECTION_MODE: MangaAction[] = ['remove_from_library'] as const;
|
||||||
|
|
||||||
|
const createGetMenuItemTitle =
|
||||||
|
(isSingleMode: boolean) =>
|
||||||
|
(action: MangaAction, count: number): string => {
|
||||||
|
const countSuffix = count > 0 ? ` (${count})` : '';
|
||||||
|
return `${translate(
|
||||||
|
actionToTranslationKey[action].action[isSingleMode ? 'single' : 'selected'],
|
||||||
|
)}${countSuffix}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createShouldShowMenuItem =
|
||||||
|
(isSingleMode: boolean) =>
|
||||||
|
(shouldBeVisible: boolean = false): boolean =>
|
||||||
|
isSingleMode ? shouldBeVisible : true;
|
||||||
|
|
||||||
|
const createIsMenuItemDisabled =
|
||||||
|
(isSingleMode: boolean) =>
|
||||||
|
(shouldBeDisabled: boolean): boolean =>
|
||||||
|
isSingleMode ? false : shouldBeDisabled;
|
||||||
|
|
||||||
|
type BaseProps = { onClose: (selectionModeState: boolean) => void; setHideMenu: (hide: boolean) => void };
|
||||||
|
|
||||||
|
export type SingleModeProps = {
|
||||||
|
manga: Pick<TManga, 'id'> & MangaDownloadInfo & MangaUnreadInfo;
|
||||||
|
handleSelection?: SelectableCollectionReturnType<TManga['id']>['handleSelection'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type SelectModeProps = {
|
||||||
|
selectedMangas: TManga[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props =
|
||||||
|
| (BaseProps & SingleModeProps & PropertiesNever<SelectModeProps>)
|
||||||
|
| (BaseProps & PropertiesNever<SingleModeProps> & SelectModeProps);
|
||||||
|
|
||||||
|
export const MangaActionMenuItems = ({ manga, handleSelection, selectedMangas = [], onClose, setHideMenu }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [isCategorySelectOpen, setIsCategorySelectOpen] = useState(false);
|
||||||
|
|
||||||
|
const isSingleMode = !!manga;
|
||||||
|
|
||||||
|
const getMenuItemTitle = createGetMenuItemTitle(isSingleMode);
|
||||||
|
const shouldShowMenuItem = createShouldShowMenuItem(isSingleMode);
|
||||||
|
const isMenuItemDisabled = createIsMenuItemDisabled(isSingleMode);
|
||||||
|
|
||||||
|
const isFullyDownloaded = !!manga && manga.downloadCount === manga.chapters.totalCount;
|
||||||
|
const hasDownloadedChapters = !!manga?.downloadCount;
|
||||||
|
const hasUnreadChapters = !!manga?.unreadCount;
|
||||||
|
const hasReadChapters = !!manga && manga.unreadCount !== manga.chapters.totalCount;
|
||||||
|
|
||||||
|
const handleSelect = () => {
|
||||||
|
handleSelection?.(manga.id, true);
|
||||||
|
onClose(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const performAction = (action: MangaAction, mangas: TManga[]) => {
|
||||||
|
Mangas.performAction(action, manga ? [manga.id] : Mangas.getIds(mangas), {
|
||||||
|
wasManuallyMarkedAsRead: true,
|
||||||
|
}).catch(() => {});
|
||||||
|
|
||||||
|
onClose(!ACTION_DISABLES_SELECTION_MODE.includes(action));
|
||||||
|
};
|
||||||
|
|
||||||
|
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 && (
|
||||||
|
<MenuItem onClick={handleSelect} Icon={CheckBoxOutlineBlank} title={t('chapter.action.label.select')} />
|
||||||
|
)}
|
||||||
|
{shouldShowMenuItem(!isFullyDownloaded) && (
|
||||||
|
<MenuItem
|
||||||
|
Icon={Download}
|
||||||
|
isDisabled={isMenuItemDisabled(!downloadableMangas.length)}
|
||||||
|
onClick={() => performAction('download', downloadableMangas)}
|
||||||
|
title={getMenuItemTitle('download', downloadableMangas.length)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{shouldShowMenuItem(hasDownloadedChapters) && (
|
||||||
|
<MenuItem
|
||||||
|
Icon={Delete}
|
||||||
|
isDisabled={isMenuItemDisabled(!downloadedMangas.length)}
|
||||||
|
onClick={() => performAction('delete', downloadedMangas)}
|
||||||
|
title={getMenuItemTitle('delete', downloadedMangas.length)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{shouldShowMenuItem(hasUnreadChapters) && (
|
||||||
|
<MenuItem
|
||||||
|
Icon={Done}
|
||||||
|
isDisabled={isMenuItemDisabled(!unreadMangas.length)}
|
||||||
|
onClick={() => performAction('mark_as_read', unreadMangas)}
|
||||||
|
title={getMenuItemTitle('mark_as_read', unreadMangas.length)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{shouldShowMenuItem(hasReadChapters) && (
|
||||||
|
<MenuItem
|
||||||
|
Icon={RemoveDone}
|
||||||
|
isDisabled={isMenuItemDisabled(!readMangas.length)}
|
||||||
|
onClick={() => performAction('mark_as_unread', readMangas)}
|
||||||
|
title={getMenuItemTitle('mark_as_unread', readMangas.length)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setIsCategorySelectOpen(true);
|
||||||
|
setHideMenu(true);
|
||||||
|
}}
|
||||||
|
Icon={Label}
|
||||||
|
title={t('manga.action.category.label.action')}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => performAction('remove_from_library', selectedMangas)}
|
||||||
|
Icon={FavoriteBorderIcon}
|
||||||
|
title={t('manga.action.library.remove.label.action')}
|
||||||
|
/>
|
||||||
|
{isCategorySelectOpen && (
|
||||||
|
<CategorySelect
|
||||||
|
open={isCategorySelectOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsCategorySelectOpen(false);
|
||||||
|
onClose(true);
|
||||||
|
}}
|
||||||
|
mangaId={manga?.id as undefined} // either mangaId or mangaIds is undefined, however, ts is not able to infer it correctly and raises an error
|
||||||
|
mangaIds={Mangas.getIds(selectedMangas)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 Download from '@mui/icons-material/Download';
|
|
||||||
import Delete from '@mui/icons-material/Delete';
|
|
||||||
import Done from '@mui/icons-material/Done';
|
|
||||||
import RemoveDone from '@mui/icons-material/RemoveDone';
|
|
||||||
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
|
|
||||||
import Label from '@mui/icons-material/Label';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
import { t as translate } from 'i18next';
|
|
||||||
import { MenuItem } from '@/components/manga/MenuItem.tsx';
|
|
||||||
import { TManga } from '@/typings.ts';
|
|
||||||
import { actionToTranslationKey, MangaAction, Mangas } from '@/lib/data/Mangas.ts';
|
|
||||||
import { CategorySelect } from '@/components/navbar/action/CategorySelect.tsx';
|
|
||||||
|
|
||||||
const ACTION_DISABLES_SELECTION_MODE: MangaAction[] = ['remove_from_library'] as const;
|
|
||||||
|
|
||||||
const getMenuItemTitle = (action: MangaAction, count: number): string => {
|
|
||||||
const countSuffix = count > 0 ? ` (${count})` : '';
|
|
||||||
return `${translate(actionToTranslationKey[action].action.selected)}${countSuffix}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MangasSelectionFABActionItems = ({
|
|
||||||
selectedMangas,
|
|
||||||
handleClose,
|
|
||||||
}: {
|
|
||||||
selectedMangas: TManga[];
|
|
||||||
handleClose: (selectionModeState: boolean) => void;
|
|
||||||
}) => {
|
|
||||||
const [isCategorySelectOpen, setIsCategorySelectOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleAction = (action: MangaAction, mangas: TManga[]) => {
|
|
||||||
Mangas.performAction(action, Mangas.getIds(mangas), {
|
|
||||||
wasManuallyMarkedAsRead: true,
|
|
||||||
}).catch(() => {});
|
|
||||||
handleClose(!ACTION_DISABLES_SELECTION_MODE.includes(action));
|
|
||||||
};
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<MenuItem
|
|
||||||
Icon={Download}
|
|
||||||
isDisabled={!downloadableMangas.length}
|
|
||||||
onClick={() => handleAction('download', downloadableMangas)}
|
|
||||||
title={getMenuItemTitle('download', downloadableMangas.length)}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
Icon={Delete}
|
|
||||||
isDisabled={!downloadedMangas.length}
|
|
||||||
onClick={() => handleAction('delete', downloadedMangas)}
|
|
||||||
title={getMenuItemTitle('delete', downloadedMangas.length)}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
Icon={Done}
|
|
||||||
isDisabled={!unreadMangas.length}
|
|
||||||
onClick={() => handleAction('mark_as_read', unreadMangas)}
|
|
||||||
title={getMenuItemTitle('mark_as_read', unreadMangas.length)}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
Icon={RemoveDone}
|
|
||||||
isDisabled={!readMangas.length}
|
|
||||||
onClick={() => handleAction('mark_as_unread', readMangas)}
|
|
||||||
title={getMenuItemTitle('mark_as_unread', readMangas.length)}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
Icon={Label}
|
|
||||||
onClick={() => handleAction('change_categories', selectedMangas)}
|
|
||||||
title={getMenuItemTitle('change_categories', selectedMangas.length)}
|
|
||||||
/>
|
|
||||||
<MenuItem
|
|
||||||
Icon={FavoriteBorderIcon}
|
|
||||||
onClick={() => handleAction('remove_from_library', selectedMangas)}
|
|
||||||
title={getMenuItemTitle('remove_from_library', selectedMangas.length)}
|
|
||||||
/>
|
|
||||||
{isCategorySelectOpen && (
|
|
||||||
<CategorySelect
|
|
||||||
open={isCategorySelectOpen}
|
|
||||||
onClose={() => {
|
|
||||||
setIsCategorySelectOpen(false);
|
|
||||||
handleClose(true);
|
|
||||||
}}
|
|
||||||
mangaIds={Mangas.getIds(selectedMangas)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -8,14 +8,29 @@
|
|||||||
|
|
||||||
import { MenuProps } from '@mui/material/Menu/Menu';
|
import { MenuProps } from '@mui/material/Menu/Menu';
|
||||||
import { Menu as MuiMenu } from '@mui/material';
|
import { Menu as MuiMenu } from '@mui/material';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
export const Menu = ({
|
export const Menu = ({
|
||||||
children,
|
children,
|
||||||
onClose,
|
onClose,
|
||||||
...props
|
...props
|
||||||
}: Omit<MenuProps, 'children' | 'onClose'> &
|
}: Omit<MenuProps, 'children' | 'onClose'> &
|
||||||
Required<Pick<MenuProps, 'onClose'>> & { children: (onClose: () => void) => JSX.Element }) => (
|
Required<Pick<MenuProps, 'onClose'>> & {
|
||||||
<MuiMenu {...props} onClose={onClose}>
|
children: (onClose: () => void, setHideMenu: (hide: boolean) => void) => JSX.Element;
|
||||||
{children(() => onClose({}, 'backdropClick'))}
|
}) => {
|
||||||
</MuiMenu>
|
const [shouldHideMenu, setShouldHideMenu] = useState(false);
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<MuiMenu
|
||||||
|
{...props}
|
||||||
|
open={props.open}
|
||||||
|
onClose={onClose}
|
||||||
|
sx={{ visibility: !props.open || shouldHideMenu ? 'hidden' : 'visible' }}
|
||||||
|
>
|
||||||
|
{children(() => {
|
||||||
|
onClose({}, 'backdropClick');
|
||||||
|
setShouldHideMenu(false);
|
||||||
|
}, setShouldHideMenu)}
|
||||||
|
</MuiMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { TranslationKey } from '@/typings.ts';
|
|||||||
import { Menu } from '@/components/manga/Menu.tsx';
|
import { Menu } from '@/components/manga/Menu.tsx';
|
||||||
|
|
||||||
interface SelectionFABProps {
|
interface SelectionFABProps {
|
||||||
children: (handleClose: () => void) => JSX.Element;
|
children: (handleClose: () => void, setHideMenu: (hide: boolean) => void) => JSX.Element;
|
||||||
selectedItemsCount: number;
|
selectedItemsCount: number;
|
||||||
title: TranslationKey;
|
title: TranslationKey;
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ export const SelectionFAB: React.FC<SelectionFABProps> = ({ children, selectedIt
|
|||||||
'aria-labelledby': 'selectionMenuButton',
|
'aria-labelledby': 'selectionMenuButton',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(onClose) => children(onClose)}
|
{(onClose, setHideMenu) => children(onClose, setHideMenu)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type MangaAction =
|
|||||||
export const actionToTranslationKey: {
|
export const actionToTranslationKey: {
|
||||||
[key in MangaAction]: {
|
[key in MangaAction]: {
|
||||||
action: {
|
action: {
|
||||||
|
single: TranslationKey;
|
||||||
selected: TranslationKey;
|
selected: TranslationKey;
|
||||||
};
|
};
|
||||||
success: TranslationKey;
|
success: TranslationKey;
|
||||||
@@ -36,6 +37,7 @@ export const actionToTranslationKey: {
|
|||||||
} = {
|
} = {
|
||||||
download: {
|
download: {
|
||||||
action: {
|
action: {
|
||||||
|
single: 'chapter.action.download.add.label.action',
|
||||||
selected: 'chapter.action.download.add.button.selected',
|
selected: 'chapter.action.download.add.button.selected',
|
||||||
},
|
},
|
||||||
success: 'chapter.action.download.add.label.success',
|
success: 'chapter.action.download.add.label.success',
|
||||||
@@ -43,6 +45,7 @@ export const actionToTranslationKey: {
|
|||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
action: {
|
action: {
|
||||||
|
single: 'chapter.action.download.delete.label.action',
|
||||||
selected: 'chapter.action.download.delete.button.selected',
|
selected: 'chapter.action.download.delete.button.selected',
|
||||||
},
|
},
|
||||||
success: 'chapter.action.download.delete.label.success',
|
success: 'chapter.action.download.delete.label.success',
|
||||||
@@ -50,6 +53,7 @@ export const actionToTranslationKey: {
|
|||||||
},
|
},
|
||||||
mark_as_read: {
|
mark_as_read: {
|
||||||
action: {
|
action: {
|
||||||
|
single: 'chapter.action.mark_as_read.add.label.action.current',
|
||||||
selected: 'chapter.action.mark_as_read.add.button.selected',
|
selected: 'chapter.action.mark_as_read.add.button.selected',
|
||||||
},
|
},
|
||||||
success: 'chapter.action.mark_as_read.add.label.success',
|
success: 'chapter.action.mark_as_read.add.label.success',
|
||||||
@@ -57,6 +61,7 @@ export const actionToTranslationKey: {
|
|||||||
},
|
},
|
||||||
mark_as_unread: {
|
mark_as_unread: {
|
||||||
action: {
|
action: {
|
||||||
|
single: 'chapter.action.mark_as_read.remove.label.action',
|
||||||
selected: 'chapter.action.mark_as_read.remove.button.selected',
|
selected: 'chapter.action.mark_as_read.remove.button.selected',
|
||||||
},
|
},
|
||||||
success: 'chapter.action.mark_as_read.remove.label.success',
|
success: 'chapter.action.mark_as_read.remove.label.success',
|
||||||
@@ -64,6 +69,7 @@ export const actionToTranslationKey: {
|
|||||||
},
|
},
|
||||||
remove_from_library: {
|
remove_from_library: {
|
||||||
action: {
|
action: {
|
||||||
|
single: 'manga.action.library.remove.label.action',
|
||||||
selected: 'manga.action.library.remove.button.selected',
|
selected: 'manga.action.library.remove.button.selected',
|
||||||
},
|
},
|
||||||
success: 'manga.action.library.remove.label.success',
|
success: 'manga.action.library.remove.label.success',
|
||||||
@@ -71,6 +77,7 @@ export const actionToTranslationKey: {
|
|||||||
},
|
},
|
||||||
change_categories: {
|
change_categories: {
|
||||||
action: {
|
action: {
|
||||||
|
single: 'manga.action.category.label.action',
|
||||||
selected: 'manga.action.category.button.selected',
|
selected: 'manga.action.category.button.selected',
|
||||||
},
|
},
|
||||||
success: 'manga.action.category.label.success',
|
success: 'manga.action.category.label.success',
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import { TManga } from '@/typings.ts';
|
|||||||
import { SelectableCollectionSelectMode } from '@/components/collection/SelectableCollectionSelectMode.tsx';
|
import { SelectableCollectionSelectMode } from '@/components/collection/SelectableCollectionSelectMode.tsx';
|
||||||
import { useGetVisibleLibraryMangas } from '@/components/library/useGetVisibleLibraryMangas.ts';
|
import { useGetVisibleLibraryMangas } from '@/components/library/useGetVisibleLibraryMangas.ts';
|
||||||
import { SelectionFAB } from '@/components/manga/SelectionFAB.tsx';
|
import { SelectionFAB } from '@/components/manga/SelectionFAB.tsx';
|
||||||
import { MangasSelectionFABActionItems } from '@/components/manga/MangasSelectionFABActionItems.tsx';
|
|
||||||
import { PARTIAL_MANGA_FIELDS } from '@/lib/graphql/Fragments.ts';
|
import { PARTIAL_MANGA_FIELDS } from '@/lib/graphql/Fragments.ts';
|
||||||
|
import { MangaActionMenuItems } from '@/components/manga/MangaActionMenuItems.tsx';
|
||||||
|
|
||||||
const StyledGridWrapper = styled(Box)(({ theme }) => ({
|
const StyledGridWrapper = styled(Box)(({ theme }) => ({
|
||||||
// TabsMenu height + TabsMenu bottom padding - grid item top padding
|
// TabsMenu height + TabsMenu bottom padding - grid item top padding
|
||||||
@@ -264,13 +264,14 @@ export function Library() {
|
|||||||
</StyledGridWrapper>
|
</StyledGridWrapper>
|
||||||
{isSelectModeActive && (
|
{isSelectModeActive && (
|
||||||
<SelectionFAB selectedItemsCount={selectedItemIds.length} title="manga.title">
|
<SelectionFAB selectedItemsCount={selectedItemIds.length} title="manga.title">
|
||||||
{(handleClose) => (
|
{(handleClose, setHideMenu) => (
|
||||||
<MangasSelectionFABActionItems
|
<MangaActionMenuItems
|
||||||
selectedMangas={selectedMangas}
|
selectedMangas={selectedMangas}
|
||||||
handleClose={(selectionModeState) => {
|
onClose={(selectionModeState) => {
|
||||||
handleClose();
|
handleClose();
|
||||||
setIsSelectModeActive(selectionModeState);
|
setIsSelectModeActive(selectionModeState);
|
||||||
}}
|
}}
|
||||||
|
setHideMenu={setHideMenu}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</SelectionFAB>
|
</SelectionFAB>
|
||||||
|
|||||||
Reference in New Issue
Block a user