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