Feature/cleanup chapter actions (#525)
* Extract chapter actions into "Chapters" * Extract chapter action menu into component * Rename "SelectionFABActionItem" to "MenuItem" * Decouple "MenuItem" from "SelectionFAB" * Use "MenuItem" in "ActionMenus" * Merge chapter action menus * Simplify chapter "mark as read" action
This commit is contained in:
@@ -8,27 +8,27 @@
|
||||
|
||||
import { Box, CircularProgress, Stack, styled, Tooltip } from '@mui/material';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React, { ComponentProps, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
import DoneAllIcon from '@mui/icons-material/DoneAll';
|
||||
import { TChapter, TManga, TranslationKey } from '@/typings';
|
||||
import { TChapter, TManga } from '@/typings';
|
||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { ChapterCard } from '@/components/manga/ChapterCard';
|
||||
import { ResumeFab } from '@/components/manga/ResumeFAB';
|
||||
import { filterAndSortChapters, useChapterOptions } from '@/components/manga/util';
|
||||
import { EmptyView } from '@/components/util/EmptyView';
|
||||
import { makeToast } from '@/components/util/Toast';
|
||||
import { ChaptersToolbarMenu } from '@/components/manga/ChaptersToolbarMenu';
|
||||
import { SelectionFAB } from '@/components/manga/SelectionFAB';
|
||||
import { DEFAULT_FULL_FAB_HEIGHT } from '@/components/util/StyledFab';
|
||||
import { DownloadType, UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
|
||||
import { DownloadType } from '@/lib/graphql/generated/graphql.ts';
|
||||
import { useSelectableCollection } from '@/components/collection/useSelectableCollection.ts';
|
||||
import { SelectableCollectionSelectAll } from '@/components/collection/SelectableCollectionSelectAll.tsx';
|
||||
import { ChapterSelectionFABActionItems } from '@/components/manga/ChapterSelectionFABActionItems.tsx';
|
||||
import { Chapters } from '@/lib/data/Chapters.ts';
|
||||
import { ChaptersWithMeta } from '@/lib/data/ChaptersWithMeta.ts';
|
||||
import { ChapterActionMenuItems } from '@/components/manga/ChapterActionMenuItems.tsx';
|
||||
|
||||
const ChapterListHeader = styled(Stack)(({ theme }) => ({
|
||||
margin: 8,
|
||||
@@ -52,38 +52,6 @@ const StyledVirtuoso = styled(Virtuoso)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const actionsStrings: {
|
||||
[key in 'download' | 'delete' | 'bookmark' | 'unbookmark' | 'mark_as_read' | 'mark_as_unread']: {
|
||||
success: TranslationKey;
|
||||
error: TranslationKey;
|
||||
};
|
||||
} = {
|
||||
download: {
|
||||
success: 'chapter.action.download.add.label.success',
|
||||
error: 'chapter.action.download.add.label.error',
|
||||
},
|
||||
delete: {
|
||||
success: 'chapter.action.download.delete.label.success',
|
||||
error: 'chapter.action.download.delete.label.error',
|
||||
},
|
||||
bookmark: {
|
||||
success: 'chapter.action.bookmark.add.label.success',
|
||||
error: 'chapter.action.bookmark.add.label.error',
|
||||
},
|
||||
unbookmark: {
|
||||
success: 'chapter.action.bookmark.remove.label.success',
|
||||
error: 'chapter.action.bookmark.remove.label.error',
|
||||
},
|
||||
mark_as_read: {
|
||||
success: 'chapter.action.mark_as_read.add.label.success',
|
||||
error: 'chapter.action.mark_as_read.add.label.error',
|
||||
},
|
||||
mark_as_unread: {
|
||||
success: 'chapter.action.mark_as_read.remove.label.success',
|
||||
error: 'chapter.action.mark_as_read.remove.label.error',
|
||||
},
|
||||
};
|
||||
|
||||
export interface IChapterWithMeta {
|
||||
chapter: TChapter;
|
||||
downloadChapter: DownloadType | undefined;
|
||||
@@ -108,8 +76,6 @@ export const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
|
||||
const { areNoItemsSelected, areAllItemsSelected, selectedItemIds, handleSelectAll, handleSelection } =
|
||||
useSelectableCollection(chapters.length, { currentKey: 'default' });
|
||||
|
||||
const { settings: metadataServerSettings } = useMetadataServerSettings();
|
||||
|
||||
const visibleChapters = useMemo(() => filterAndSortChapters(chapters, options), [chapters, options]);
|
||||
|
||||
const nextChapterIndexToRead = (manga.lastReadChapter?.sourceOrder ?? 0) + 1;
|
||||
@@ -118,70 +84,6 @@ export const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
|
||||
const areAllChaptersRead = manga.unreadCount === 0;
|
||||
const areAllChaptersDownloaded = manga.downloadCount === manga.chapters.totalCount;
|
||||
|
||||
const handleFabAction: ComponentProps<typeof ChapterSelectionFABActionItems>['onAction'] = (
|
||||
action,
|
||||
actionChapters,
|
||||
) => {
|
||||
if (actionChapters.length === 0) return;
|
||||
const chapterIds = actionChapters
|
||||
.filter(({ chapter }) => {
|
||||
switch (action) {
|
||||
case 'download':
|
||||
return !chapter.isDownloaded;
|
||||
case 'delete':
|
||||
return chapter.isDownloaded;
|
||||
case 'bookmark':
|
||||
return !chapter.isBookmarked;
|
||||
case 'unbookmark':
|
||||
return chapter.isBookmarked;
|
||||
case 'mark_as_read':
|
||||
return !chapter.isRead;
|
||||
case 'mark_as_unread':
|
||||
return chapter.isRead;
|
||||
default:
|
||||
throw new Error(`ChapterList::handleFabAction: unknown action "${action}"`);
|
||||
}
|
||||
})
|
||||
.map(({ chapter }) => chapter.id);
|
||||
|
||||
let actionPromise: Promise<any>;
|
||||
|
||||
if (action === 'download') {
|
||||
actionPromise = requestManager.addChaptersToDownloadQueue(chapterIds).response;
|
||||
} else {
|
||||
const change: UpdateChapterPatchInput = {};
|
||||
|
||||
if (action === 'bookmark') change.isBookmarked = true;
|
||||
else if (action === 'unbookmark') change.isBookmarked = false;
|
||||
else if (action === 'mark_as_read' || action === 'mark_as_unread') {
|
||||
change.isRead = action === 'mark_as_read';
|
||||
change.lastPageRead = 0;
|
||||
}
|
||||
|
||||
if (action === 'delete') {
|
||||
actionPromise = requestManager.deleteDownloadedChapters(chapterIds).response;
|
||||
} else {
|
||||
const shouldDeleteChapters =
|
||||
action === 'mark_as_read' && metadataServerSettings.deleteChaptersManuallyMarkedRead;
|
||||
const chapterIdsToDelete = shouldDeleteChapters
|
||||
? actionChapters
|
||||
.filter(
|
||||
({ chapter }) =>
|
||||
chapter.isDownloaded &&
|
||||
(!chapter.isBookmarked || metadataServerSettings.deleteChaptersWithBookmark),
|
||||
)
|
||||
.map(({ chapter }) => chapter.id)
|
||||
: [];
|
||||
|
||||
actionPromise = requestManager.updateChapters(chapterIds, { ...change, chapterIdsToDelete }).response;
|
||||
}
|
||||
}
|
||||
|
||||
actionPromise
|
||||
.then(() => makeToast(t(actionsStrings[action].success, { count: chapterIds.length }), 'success'))
|
||||
.catch(() => makeToast(t(actionsStrings[action].error, { count: chapterIds.length }), 'error'));
|
||||
};
|
||||
|
||||
const noChaptersFound = chapters.length === 0;
|
||||
const noChaptersMatchingFilter = !noChaptersFound && visibleChapters.length === 0;
|
||||
|
||||
@@ -208,11 +110,7 @@ export const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
|
||||
return (
|
||||
<SelectionFAB selectedItemsCount={selectedChapters.length} title="chapter.title">
|
||||
{(handleClose) => (
|
||||
<ChapterSelectionFABActionItems
|
||||
selectedChapters={selectedChapters}
|
||||
onAction={handleFabAction}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
<ChapterActionMenuItems selectedChapters={selectedChapters} onClose={handleClose} />
|
||||
)}
|
||||
</SelectionFAB>
|
||||
);
|
||||
@@ -253,7 +151,12 @@ export const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
|
||||
<Tooltip title={t('chapter.action.mark_as_read.add.label.action.current')}>
|
||||
<IconButton
|
||||
disabled={areAllChaptersRead}
|
||||
onClick={() => handleFabAction('mark_as_read', chaptersWithMeta)}
|
||||
onClick={() =>
|
||||
Chapters.markAsRead(
|
||||
ChaptersWithMeta.getChapters(ChaptersWithMeta.getNonRead(chaptersWithMeta)),
|
||||
true,
|
||||
)
|
||||
}
|
||||
>
|
||||
<DoneAllIcon />
|
||||
</IconButton>
|
||||
@@ -261,7 +164,11 @@ export const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
|
||||
<Tooltip title={t('chapter.action.download.add.label.action')}>
|
||||
<IconButton
|
||||
disabled={areAllChaptersDownloaded}
|
||||
onClick={() => handleFabAction('download', chaptersWithMeta)}
|
||||
onClick={() =>
|
||||
Chapters.download(
|
||||
ChaptersWithMeta.getIds(ChaptersWithMeta.getNonDownloaded(chaptersWithMeta)),
|
||||
)
|
||||
}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
|
||||
Reference in New Issue
Block a user