Move download status subscription to "ChapterCard"

Only re-render chapters whose download status has changed instead of the whole list everytime a download status change was received
This commit is contained in:
schroda
2025-01-12 15:05:09 +01:00
parent f7b26767a3
commit 257c0c8e10
11 changed files with 107 additions and 205 deletions

View File

@@ -28,6 +28,7 @@ import { ChaptersToolbarMenu } from '@/modules/chapter/components/ChaptersToolba
import { SelectionFAB } from '@/modules/collection/components/SelectionFAB.tsx';
import { DEFAULT_FULL_FAB_HEIGHT } from '@/modules/core/components/buttons/StyledFab.tsx';
import {
ChapterListFieldsFragment,
GetChaptersMangaQuery,
GetChaptersMangaQueryVariables,
MangaScreenFieldsFragment,
@@ -35,7 +36,6 @@ import {
import { useSelectableCollection } from '@/modules/collection/hooks/useSelectableCollection.ts';
import { SelectableCollectionSelectAll } from '@/modules/collection/components/SelectableCollectionSelectAll.tsx';
import { Chapters } from '@/modules/chapter/services/Chapters.ts';
import { ChaptersWithMeta, ChapterWithMetaType } from '@/modules/chapter/services/ChaptersWithMeta.ts';
import { ChapterActionMenuItems } from '@/modules/chapter/components/actions/ChapterActionMenuItems.tsx';
import { ChaptersDownloadActionMenuItems } from '@/modules/chapter/components/actions/ChaptersDownloadActionMenuItems.tsx';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
@@ -75,9 +75,27 @@ const StyledVirtuoso = styled(Virtuoso, {
},
}));
export interface IChapterWithMeta extends ChapterWithMetaType<ComponentProps<typeof ChapterCard>['chapter']> {
selected: boolean | null;
}
const ChapterListFAB = ({
selectedChapters,
firstUnreadChapter,
}: {
selectedChapters: ChapterListFieldsFragment[];
firstUnreadChapter: ComponentProps<typeof ResumeFab>['chapter'] | null | undefined;
}) => {
if (selectedChapters.length) {
return (
<SelectionFAB selectedItemsCount={selectedChapters.length} title="chapter.title_one">
{(handleClose) => <ChapterActionMenuItems selectedChapters={selectedChapters} onClose={handleClose} />}
</SelectionFAB>
);
}
if (firstUnreadChapter) {
return <ResumeFab chapter={firstUnreadChapter} />;
}
return null;
};
export const ChapterList = ({
manga,
@@ -100,8 +118,6 @@ export const ChapterList = ({
const scrollbarWidth = MediaQuery.useGetScrollbarSize('width');
const downloadSubscription = requestManager.useDownloadSubscription();
const [options, dispatch] = useChapterOptions(manga.id);
const {
data: chaptersData,
@@ -128,39 +144,6 @@ export const ChapterList = ({
const noChaptersFound = chapters.length === 0;
const noChaptersMatchingFilter = !noChaptersFound && visibleChapters.length === 0;
const chaptersWithMeta: IChapterWithMeta[] = useMemo(
() =>
visibleChapters.map((chapter) => {
const selected = !areNoItemsSelected ? selectedItemIds.includes(chapter.id) : null;
return {
chapter,
downloadChapter: Chapters.getDownloadStatusFromCache(chapter.id),
selected,
};
}),
[downloadSubscription.data?.downloadStatusChanged, selectedItemIds, visibleChapters],
);
const chapterListFAB = useMemo(() => {
const selectedChapters = chaptersWithMeta.filter((chapter) => chapter.selected);
if (selectedChapters.length) {
return (
<SelectionFAB selectedItemsCount={selectedChapters.length} title="chapter.title_one">
{(handleClose) => (
<ChapterActionMenuItems selectedChapters={selectedChapters} onClose={handleClose} />
)}
</SelectionFAB>
);
}
if (manga.firstUnreadChapter) {
return <ResumeFab chapter={manga.firstUnreadChapter} />;
}
return null;
}, [chaptersWithMeta]);
if (isLoading || (noChaptersFound && isRefreshing)) {
return (
<Stack sx={{ justifyContent: 'center', alignItems: 'center', position: 'relative', flexGrow: 1 }}>
@@ -201,13 +184,7 @@ export const ChapterList = ({
<Tooltip title={t('chapter.action.mark_as_read.add.label.action.current')}>
<IconButton
disabled={areAllChaptersRead}
onClick={() =>
Chapters.markAsRead(
ChaptersWithMeta.getChapters(ChaptersWithMeta.getNonRead(chaptersWithMeta)),
true,
manga.id,
)
}
onClick={() => Chapters.markAsRead(Chapters.getNonRead(chapters), true, manga.id)}
>
<DoneAllIcon />
</IconButton>
@@ -259,11 +236,12 @@ export const ChapterList = ({
computeItemKey={(index) => visibleChapters[index].id}
itemContent={(index: number) => (
<ChapterCard
{...chaptersWithMeta[index]}
chapter={chapters[index]}
selected={!areNoItemsSelected ? selectedItemIds.includes(chapters[index].id) : null}
allChapters={chapters}
showChapterNumber={options.showChapterNumber}
onSelect={(selected, selectRange) =>
handleSelection(chaptersWithMeta[index].chapter.id, selected, { selectRange })
handleSelection(chapters[index].id, selected, { selectRange })
}
/>
)}
@@ -271,7 +249,12 @@ export const ChapterList = ({
overscan={window.innerHeight * 0.5}
/>
</Stack>
{chapterListFAB}
<ChapterListFAB
selectedChapters={selectedItemIds
.map((id) => chapters.find((chapter) => chapter.id === id))
.filter((chapter) => chapter != null)}
firstUnreadChapter={manga.firstUnreadChapter}
/>
</>
);
};

View File

@@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next';
import BookmarkRemove from '@mui/icons-material/BookmarkRemove';
import BookmarkAdd from '@mui/icons-material/BookmarkAdd';
import DoneAll from '@mui/icons-material/DoneAll';
import { useMemo } from 'react';
import { ComponentProps, useMemo } from 'react';
import LaunchIcon from '@mui/icons-material/Launch';
import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSelectableCollection.ts';
import {
@@ -30,8 +30,6 @@ import {
Chapters,
} from '@/modules/chapter/services/Chapters.ts';
import { MenuItem } from '@/modules/core/components/menu/MenuItem.tsx';
import { IChapterWithMeta } from '@/modules/chapter/components/ChapterList.tsx';
import { ChaptersWithMeta } from '@/modules/chapter/services/ChaptersWithMeta.ts';
import {
createGetMenuItemTitle,
createIsMenuItemDisabled,
@@ -39,6 +37,7 @@ import {
} from '@/modules/core/components/menu/Menu.utils.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
import { ChapterCard } from '@/modules/chapter/components/cards/ChapterCard.tsx';
type BaseProps = { onClose: () => void; selectable?: boolean };
@@ -57,7 +56,7 @@ type SingleModeProps = {
};
type SelectModeProps = {
selectedChapters: IChapterWithMeta[];
selectedChapters: ComponentProps<typeof ChapterCard>['chapter'][];
};
type Props =
@@ -95,12 +94,12 @@ export const ChapterActionMenuItems = ({
readChapters,
} = useMemo(
() => ({
downloadableChapters: ChaptersWithMeta.getDownloadable(selectedChapters),
downloadedChapters: ChaptersWithMeta.getDownloaded(selectedChapters),
unbookmarkedChapters: ChaptersWithMeta.getNonBookmarked(selectedChapters),
bookmarkedChapters: ChaptersWithMeta.getBookmarked(selectedChapters),
unreadChapters: ChaptersWithMeta.getNonRead(selectedChapters),
readChapters: ChaptersWithMeta.getRead(selectedChapters),
downloadableChapters: Chapters.getDownloadable(selectedChapters),
downloadedChapters: Chapters.getDownloaded(selectedChapters),
unbookmarkedChapters: Chapters.getNonBookmarked(selectedChapters),
bookmarkedChapters: Chapters.getBookmarked(selectedChapters),
unreadChapters: Chapters.getNonRead(selectedChapters),
readChapters: Chapters.getRead(selectedChapters),
}),
[selectedChapters],
);
@@ -110,7 +109,7 @@ export const ChapterActionMenuItems = ({
onClose();
};
const performAction = (action: ChapterAction | 'mark_prev_as_read', chaptersWithMeta: IChapterWithMeta[]) => {
const performAction = (action: ChapterAction | 'mark_prev_as_read', chapters: TChapter[]) => {
const isMarkPrevAsRead = action === 'mark_prev_as_read';
const actualAction: ChapterAction = isMarkPrevAsRead ? 'mark_as_read' : action;
@@ -125,7 +124,7 @@ export const ChapterActionMenuItems = ({
const getChapters = (): SingleModeProps['chapter'][] => {
// select mode
if (!chapter) {
return ChaptersWithMeta.getChapters(chaptersWithMeta);
return chapters;
}
if (!isMarkPrevAsRead) {
@@ -142,17 +141,17 @@ export const ChapterActionMenuItems = ({
return allChapters.slice(index + 1);
};
const chapters = getChapters();
const chaptersToUpdate = getChapters();
if (!chapters.length) {
if (!chaptersToUpdate.length) {
onClose();
return;
}
Chapters.performAction(actualAction, Chapters.getIds(chapters), {
chapters,
Chapters.performAction(actualAction, Chapters.getIds(chaptersToUpdate), {
chapters: chaptersToUpdate,
wasManuallyMarkedAsRead: true,
trackProgressMangaId: chapters[0]?.mangaId,
trackProgressMangaId: chaptersToUpdate[0]?.mangaId,
}).catch(defaultPromiseErrorHandler('ChapterActionMenuItems::performAction'));
onClose();
};
@@ -186,10 +185,7 @@ export const ChapterActionMenuItems = ({
Icon={Delete}
disabled={isMenuItemDisabled(!downloadedChapters.length)}
onClick={() =>
performAction(
'delete',
ChaptersWithMeta.getDeletable(downloadedChapters, deleteChaptersWithBookmark),
)
performAction('delete', Chapters.getDeletable(downloadedChapters, deleteChaptersWithBookmark))
}
title={getMenuItemTitle('delete', downloadedChapters.length)}
/>

View File

@@ -38,7 +38,6 @@ import {
Chapters,
ChapterScanlatorInfo,
} from '@/modules/chapter/services/Chapters.ts';
import { ChaptersWithMeta, ChapterWithMetaType } from '@/modules/chapter/services/ChaptersWithMeta.ts';
type TChapter = ChapterIdInfo &
ChapterMangaInfo &
@@ -53,7 +52,6 @@ interface IProps {
mode?: 'manga.page' | 'reader';
chapter: TChapter;
allChapters: TChapter[];
downloadChapter: ChapterWithMetaType['downloadChapter'];
showChapterNumber: boolean;
onSelect: (selected: boolean, isShiftKey?: boolean) => void;
selected: boolean | null;
@@ -71,7 +69,6 @@ export const ChapterCard: React.FC<IProps> = (props: IProps) => {
mode = 'manga.page',
chapter,
allChapters,
downloadChapter: dc,
showChapterNumber,
onSelect,
selected,
@@ -175,7 +172,7 @@ export const ChapterCard: React.FC<IProps> = (props: IProps) => {
</Typography>
</Stack>
{dc && <DownloadStateIndicator download={dc} />}
<DownloadStateIndicator chapterId={chapter.id} />
<Stack sx={{ minHeight: '48px' }}>
{selected === null ? (
@@ -209,10 +206,7 @@ export const ChapterCard: React.FC<IProps> = (props: IProps) => {
chapter={chapter}
allChapters={allChapters}
handleSelection={() => onSelect(true)}
canBeDownloaded={ChaptersWithMeta.isDownloadable({
chapter,
downloadChapter: dc,
})}
canBeDownloaded={Chapters.isDownloadable(chapter)}
selectable={selectable}
/>
)}