Improve "DownloadQueue" render performance

This commit is contained in:
schroda
2025-01-12 18:57:33 +01:00
parent 10a77722cc
commit e7c3c8609e

View File

@@ -16,7 +16,7 @@ import Stack from '@mui/material/Stack';
import Box, { BoxProps } from '@mui/material/Box'; import Box, { BoxProps } from '@mui/material/Box';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import React, { useContext, useEffect, useLayoutEffect } from 'react'; import React, { memo, useCallback, useContext, useEffect, useLayoutEffect } from 'react';
import { DragDropContext, Draggable, DraggableProvided, DropResult } from 'react-beautiful-dnd'; import { DragDropContext, Draggable, DraggableProvided, DropResult } from 'react-beautiful-dnd';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@@ -45,79 +45,86 @@ const HeightPreservingItem = ({ children, ...props }: BoxProps) => (
</Box> </Box>
); );
const DownloadChapterItem = ({ const DownloadChapterItem = memo(
provided, ({
item, provided,
handleDelete, item,
handleRetry, handleDelete,
}: { handleRetry,
provided: DraggableProvided; }: {
item: ChapterDownloadStatus; provided: DraggableProvided;
handleDelete: (chapter: ChapterIdInfo) => void; item: ChapterDownloadStatus;
handleRetry: (chapter: ChapterIdInfo) => void; handleDelete: (chapter: ChapterIdInfo) => void;
}) => { handleRetry: (chapter: ChapterIdInfo) => void;
const { t } = useTranslation(); }) => {
const { t } = useTranslation();
return ( return (
<Box {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef} sx={{ p: 1, pb: 0 }}> <Box
<Card> {...provided.draggableProps}
<CardActionArea component={Link} to={AppRoutes.manga.path(item.manga.id)}> {...provided.dragHandleProps}
<CardContent ref={provided.innerRef}
sx={{ sx={{ p: 1, pb: 0 }}
display: 'flex', >
alignItems: 'center', <Card>
p: 1.5, <CardActionArea component={Link} to={AppRoutes.manga.path(item.manga.id)}>
}} <CardContent
> sx={{
<IconButton sx={{ pointerEvents: 'none' }}> display: 'flex',
<DragHandle /> alignItems: 'center',
</IconButton> p: 1.5,
<Stack sx={{ flex: 1, ml: 1 }} direction="column"> }}
<Typography variant="h6" component="h3"> >
{item.manga.title} <IconButton sx={{ pointerEvents: 'none' }}>
</Typography> <DragHandle />
<Typography </IconButton>
variant="caption" <Stack sx={{ flex: 1, ml: 1 }} direction="column">
sx={{ <Typography variant="h6" component="h3">
display: 'block', {item.manga.title}
}} </Typography>
> <Typography
{item.chapter.name} variant="caption"
</Typography> sx={{
</Stack> display: 'block',
<DownloadStateIndicator chapterId={item.chapter.id} /> }}
{item.state === DownloadState.Error && ( >
<Tooltip title={t('global.button.retry')}> {item.chapter.name}
</Typography>
</Stack>
<DownloadStateIndicator chapterId={item.chapter.id} />
{item.state === DownloadState.Error && (
<Tooltip title={t('global.button.retry')}>
<IconButton
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleRetry(item.chapter);
}}
size="large"
>
<Refresh />
</IconButton>
</Tooltip>
)}
<Tooltip title={t('chapter.action.download.delete.label.action')}>
<IconButton <IconButton
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
handleRetry(item.chapter); handleDelete(item.chapter);
}} }}
size="large" size="large"
> >
<Refresh /> <DeleteIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} </CardContent>
<Tooltip title={t('chapter.action.download.delete.label.action')}> </CardActionArea>
<IconButton </Card>
onClick={(e) => { </Box>
e.preventDefault(); );
e.stopPropagation(); },
handleDelete(item.chapter); );
}}
size="large"
>
<DeleteIcon />
</IconButton>
</Tooltip>
</CardContent>
</CardActionArea>
</Card>
</Box>
);
};
export const DownloadQueue: React.FC = () => { export const DownloadQueue: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -213,15 +220,15 @@ export const DownloadQueue: React.FC = () => {
categoryReorder(queue, result.source.index, result.destination.index); categoryReorder(queue, result.source.index, result.destination.index);
}; };
const handleRetry = async (chapter: ChapterIdInfo) => { const handleRetry = useCallback(async (chapter: ChapterIdInfo) => {
try { try {
await requestManager.addChapterToDownloadQueue(chapter.id).response; await requestManager.addChapterToDownloadQueue(chapter.id).response;
} catch (e) { } catch (e) {
makeToast(t('download.queue.error.label.failed_to_remove'), 'error', getErrorMessage(e)); makeToast(t('download.queue.error.label.failed_to_remove'), 'error', getErrorMessage(e));
} }
}; }, []);
const handleDelete = async (chapter: ChapterIdInfo) => { const handleDelete = useCallback(async (chapter: ChapterIdInfo) => {
const isRunning = status === DownloaderState.Started; const isRunning = status === DownloaderState.Started;
try { try {
@@ -246,7 +253,7 @@ export const DownloadQueue: React.FC = () => {
} }
requestManager.startDownloads().response.catch(defaultPromiseErrorHandler('DownloadQueue::startDownloads')); requestManager.startDownloads().response.catch(defaultPromiseErrorHandler('DownloadQueue::startDownloads'));
}; }, []);
if (isLoading) { if (isLoading) {
return <LoadingPlaceholder />; return <LoadingPlaceholder />;
@@ -285,17 +292,17 @@ export const DownloadQueue: React.FC = () => {
<Virtuoso <Virtuoso
useWindowScroll useWindowScroll
overscan={window.innerHeight * 0.5} overscan={window.innerHeight * 0.5}
data={queue}
components={{ components={{
Item: HeightPreservingItem, Item: HeightPreservingItem,
}} }}
computeItemKey={(_, item) => item.chapter.id} totalCount={queue.length}
itemContent={(index, item) => ( computeItemKey={(index) => queue[index].chapter.id}
<Draggable draggableId={`${item.chapter.id}`} index={index}> itemContent={(index) => (
<Draggable draggableId={`${queue[index].chapter.id}`} index={index}>
{(draggableProvided) => ( {(draggableProvided) => (
<DownloadChapterItem <DownloadChapterItem
provided={draggableProvided} provided={draggableProvided}
item={item} item={queue[index]}
handleDelete={handleDelete} handleDelete={handleDelete}
handleRetry={handleRetry} handleRetry={handleRetry}
/> />