Reduce rerenders of reader desktop nav bar
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
import { ChapterAction, ChapterListOptions, ChapterSortMode } from '@/features/chapter/Chapter.types.ts';
|
||||
import { TranslationKey } from '@/base/Base.types.ts';
|
||||
|
||||
export const FALLBACK_CHAPTER = { id: -1, name: '', realUrl: '', isBookmarked: false };
|
||||
export const FALLBACK_CHAPTER = { id: -1, name: '', realUrl: '', isDownloaded: false, isBookmarked: false };
|
||||
|
||||
export const DEFAULT_CHAPTER_OPTIONS: ChapterListOptions = {
|
||||
unread: undefined,
|
||||
|
||||
@@ -10,18 +10,23 @@ import { Virtuoso, VirtuosoProps } from 'react-virtuoso';
|
||||
import { useMemo } from 'react';
|
||||
import { ReaderStateChapters } from '@/features/reader/Reader.types.ts';
|
||||
import { ChapterListCard } from '@/features/chapter/components/cards/ChapterListCard.tsx';
|
||||
import { ChapterIdInfo } from '@/features/chapter/Chapter.types.ts';
|
||||
|
||||
const onSelectNoop = () => {};
|
||||
|
||||
export const ReaderChapterList = ({
|
||||
currentChapter,
|
||||
currentChapterId,
|
||||
chapters,
|
||||
style,
|
||||
}: Pick<ReaderStateChapters, 'chapters' | 'currentChapter'> & Pick<VirtuosoProps<any, any>, 'style'>) => {
|
||||
const currentChapterIndex = useMemo(
|
||||
() => currentChapter && chapters.findIndex((chapter) => chapter.id === currentChapter.id),
|
||||
[currentChapter, chapters],
|
||||
);
|
||||
}: { currentChapterId: ChapterIdInfo['id'] | undefined } & Pick<ReaderStateChapters, 'chapters'> &
|
||||
Pick<VirtuosoProps<any, any>, 'style'>) => {
|
||||
const currentChapterIndex = useMemo(() => {
|
||||
if (currentChapterId === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return chapters.findIndex((chapter) => chapter.id === currentChapterId);
|
||||
}, [currentChapterId, chapters]);
|
||||
|
||||
return (
|
||||
<Virtuoso
|
||||
@@ -29,7 +34,7 @@ export const ReaderChapterList = ({
|
||||
height: `calc(${chapters.length} * 100px)`,
|
||||
...style,
|
||||
}}
|
||||
initialTopMostItemIndex={currentChapterIndex ?? 0}
|
||||
initialTopMostItemIndex={currentChapterIndex}
|
||||
totalCount={chapters.length}
|
||||
computeItemKey={(index) => chapters[index].id}
|
||||
itemContent={(index) => (
|
||||
|
||||
@@ -60,9 +60,20 @@ const BaseReaderNavBarDesktop = ({
|
||||
}: ReaderNavBarDesktopProps & Pick<NavbarContextType, 'setReaderNavBarWidth'>) => {
|
||||
const { t } = useTranslation();
|
||||
const manga = useReaderStore((state) => state.manga);
|
||||
const { chapters, currentChapter, previousChapter, nextChapter } = useReaderChaptersStore((state) => ({
|
||||
const {
|
||||
chapters,
|
||||
currentChapterId,
|
||||
currentChapterName,
|
||||
currentChapterNumber,
|
||||
currentChapterScanlator,
|
||||
previousChapter,
|
||||
nextChapter,
|
||||
} = useReaderChaptersStore((state) => ({
|
||||
chapters: state.chapters.chapters,
|
||||
currentChapter: state.chapters.currentChapter,
|
||||
currentChapterId: state.chapters.currentChapter?.id,
|
||||
currentChapterName: state.chapters.currentChapter?.name,
|
||||
currentChapterNumber: state.chapters.currentChapter?.chapterNumber,
|
||||
currentChapterScanlator: state.chapters.currentChapter?.scanlator,
|
||||
previousChapter: state.chapters.previousChapter,
|
||||
nextChapter: state.chapters.nextChapter,
|
||||
}));
|
||||
@@ -113,13 +124,13 @@ const BaseReaderNavBarDesktop = ({
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
</Stack>
|
||||
{manga && currentChapter ? (
|
||||
{manga && currentChapterId !== undefined ? (
|
||||
<>
|
||||
<ReaderNavBarDesktopMetadata
|
||||
mangaId={manga.id}
|
||||
mangaTitle={manga.title}
|
||||
chapterTitle={currentChapter.name}
|
||||
scanlator={currentChapter.scanlator}
|
||||
chapterTitle={currentChapterName ?? ''}
|
||||
scanlator={currentChapterScanlator}
|
||||
/>
|
||||
<ReaderNavBarDesktopActions />
|
||||
</>
|
||||
@@ -132,7 +143,9 @@ const BaseReaderNavBarDesktop = ({
|
||||
<ReaderNavBarDesktopPageNavigation />
|
||||
<ReaderNavBarDesktopChapterNavigation
|
||||
chapters={chapters}
|
||||
currentChapter={currentChapter}
|
||||
currentChapterId={currentChapterId}
|
||||
currentChapterName={currentChapterName}
|
||||
currentChapterNumber={currentChapterNumber}
|
||||
nextChapter={nextChapter}
|
||||
previousChapter={previousChapter}
|
||||
/>
|
||||
|
||||
@@ -25,19 +25,17 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||
import { useReaderChaptersStore, useReaderPagesStore } from '@/features/reader/stores/ReaderStore.ts';
|
||||
import { ChapterDownloadInfo, ChapterIdInfo } from '@/features/chapter/Chapter.types.ts';
|
||||
|
||||
const DownloadButton = ({
|
||||
currentChapter,
|
||||
}: {
|
||||
currentChapter: NullAndUndefined<ChapterIdInfo & ChapterDownloadInfo>;
|
||||
}) => {
|
||||
const DownloadButton = ({ id = -1, isDownloaded }: ChapterIdInfo & ChapterDownloadInfo) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const downloadStatus = Chapters.useDownloadStatusFromCache(currentChapter?.id ?? -1);
|
||||
const downloadStatus = Chapters.useDownloadStatusFromCache(id);
|
||||
|
||||
if (currentChapter && Chapters.isDownloaded(currentChapter)) {
|
||||
const isDisabled = id === undefined;
|
||||
|
||||
if (id !== undefined && isDownloaded) {
|
||||
return (
|
||||
<CustomTooltip title={t(CHAPTER_ACTION_TO_TRANSLATION.delete.action.single)}>
|
||||
<IconButton onClick={() => Chapters.performAction('delete', [currentChapter.id], {})} color="inherit">
|
||||
<IconButton onClick={() => Chapters.performAction('delete', [id], {})} color="inherit">
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
@@ -49,10 +47,10 @@ const DownloadButton = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomTooltip title={t(CHAPTER_ACTION_TO_TRANSLATION.download.action.single)} disabled={!currentChapter}>
|
||||
<CustomTooltip title={t(CHAPTER_ACTION_TO_TRANSLATION.download.action.single)} disabled={isDisabled}>
|
||||
<IconButton
|
||||
disabled={!currentChapter}
|
||||
onClick={() => Chapters.performAction('download', [currentChapter?.id ?? -1], {})}
|
||||
disabled={isDisabled}
|
||||
onClick={() => Chapters.performAction('download', [id], {})}
|
||||
color="inherit"
|
||||
>
|
||||
<DownloadIcon />
|
||||
@@ -62,9 +60,12 @@ const DownloadButton = ({
|
||||
};
|
||||
|
||||
export const ReaderNavBarDesktopActions = memo(() => {
|
||||
const currentChapter = useReaderChaptersStore((state) => state.chapters.currentChapter);
|
||||
|
||||
const { id, isBookmarked, realUrl } = currentChapter ?? FALLBACK_CHAPTER;
|
||||
const { id, isDownloaded, isBookmarked, realUrl } = useReaderChaptersStore((state) => ({
|
||||
id: state.chapters.currentChapter?.id ?? FALLBACK_CHAPTER.id,
|
||||
isDownloaded: state.chapters.currentChapter?.isDownloaded ?? FALLBACK_CHAPTER.isDownloaded,
|
||||
isBookmarked: state.chapters.currentChapter?.isBookmarked ?? FALLBACK_CHAPTER.isBookmarked,
|
||||
realUrl: state.chapters.currentChapter?.realUrl ?? FALLBACK_CHAPTER.realUrl,
|
||||
}));
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { pageLoadStates, setPageLoadStates, setRetryFailedPagesKeyPrefix } = useReaderPagesStore((state) => ({
|
||||
@@ -102,7 +103,7 @@ export const ReaderNavBarDesktopActions = memo(() => {
|
||||
<ReplayIcon />
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
<DownloadButton currentChapter={currentChapter} />
|
||||
<DownloadButton id={id} isDownloaded={isDownloaded} />
|
||||
<CustomTooltip title={t('global.button.open_browser')} disabled={!realUrl}>
|
||||
<IconButton disabled={!realUrl} href={realUrl ?? ''} rel="noreferrer" target="_blank" color="inherit">
|
||||
<IconBrowser />
|
||||
|
||||
@@ -24,14 +24,21 @@ import { ReaderService } from '@/features/reader/services/ReaderService.ts';
|
||||
import { ReaderControls } from '@/features/reader/services/ReaderControls.ts';
|
||||
import { ReaderStateChapters } from '@/features/reader/Reader.types.ts';
|
||||
import { withPropsFrom } from '@/base/hoc/withPropsFrom.tsx';
|
||||
import { ChapterIdInfo } from '@/features/chapter/Chapter.types.ts';
|
||||
|
||||
const BaseReaderNavBarDesktopChapterNavigation = ({
|
||||
currentChapter,
|
||||
currentChapterId,
|
||||
currentChapterName,
|
||||
currentChapterNumber,
|
||||
previousChapter,
|
||||
nextChapter,
|
||||
chapters = [],
|
||||
readerThemeDirection,
|
||||
}: Pick<ReaderStateChapters, 'chapters' | 'currentChapter' | 'previousChapter' | 'nextChapter'> & {
|
||||
}: {
|
||||
currentChapterId: ChapterIdInfo['id'] | undefined;
|
||||
currentChapterName: string | undefined;
|
||||
currentChapterNumber: number | undefined;
|
||||
} & Pick<ReaderStateChapters, 'chapters' | 'previousChapter' | 'nextChapter'> & {
|
||||
readerThemeDirection: ReturnType<typeof ReaderService.useGetThemeDirection>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -40,7 +47,7 @@ const BaseReaderNavBarDesktopChapterNavigation = ({
|
||||
|
||||
useLayoutEffect(() => {
|
||||
popupState.close();
|
||||
}, [currentChapter?.id]);
|
||||
}, [currentChapterId]);
|
||||
|
||||
return (
|
||||
<Stack sx={{ flexDirection: 'row', gap: 1 }} dir="ltr">
|
||||
@@ -63,15 +70,15 @@ const BaseReaderNavBarDesktopChapterNavigation = ({
|
||||
<Select
|
||||
{...bindTrigger(popupState)}
|
||||
open={popupState.isOpen}
|
||||
value={currentChapter?.id ?? 0}
|
||||
value={currentChapterId ?? 0}
|
||||
// hide actual select menu
|
||||
MenuProps={{ sx: { visibility: 'hidden' } }}
|
||||
label={t('chapter.title_one')}
|
||||
labelId="reader-nav-bar-desktop-chapter-select"
|
||||
>
|
||||
{/* hacky way to use the select component with a custom menu, the only possible value that is needed is the current chapter */}
|
||||
<MenuItem key={currentChapter?.id} value={currentChapter?.id ?? 0}>
|
||||
{currentChapter ? `#${currentChapter.chapterNumber} ${currentChapter.name}` : ''}
|
||||
<MenuItem key={currentChapterId} value={currentChapterId ?? 0}>
|
||||
{currentChapterNumber !== undefined ? `#${currentChapterNumber} ${currentChapterName}` : ''}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
@@ -105,7 +112,7 @@ const BaseReaderNavBarDesktopChapterNavigation = ({
|
||||
minHeight: '150px',
|
||||
maxHeight: '300px',
|
||||
}}
|
||||
currentChapter={currentChapter}
|
||||
currentChapterId={currentChapterId}
|
||||
chapters={chapters}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -32,8 +32,8 @@ const BaseReaderBottomBarMobile = ({
|
||||
topOffset = 0,
|
||||
}: ReaderBottomBarMobileProps & { topOffset?: number }) => {
|
||||
const { t } = useTranslation();
|
||||
const { currentChapter, chapters } = useReaderChaptersStore((state) => ({
|
||||
currentChapter: state.chapters.currentChapter,
|
||||
const { currentChapterId, chapters } = useReaderChaptersStore((state) => ({
|
||||
currentChapterId: state.chapters.currentChapter?.id,
|
||||
chapters: state.chapters.chapters,
|
||||
}));
|
||||
|
||||
@@ -50,7 +50,7 @@ const BaseReaderBottomBarMobile = ({
|
||||
|
||||
useLayoutEffect(() => {
|
||||
chapterListPopupState.close();
|
||||
}, [currentChapter?.id]);
|
||||
}, [currentChapterId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -112,7 +112,7 @@ const BaseReaderBottomBarMobile = ({
|
||||
minHeight: '15vh',
|
||||
maxHeight: '75vh',
|
||||
}}
|
||||
currentChapter={currentChapter}
|
||||
currentChapterId={currentChapterId}
|
||||
chapters={chapters}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user