Improve memoization of reader desktop nav bar

This commit is contained in:
schroda
2024-12-21 15:36:23 +01:00
parent f1f660a546
commit 948e2564fa
5 changed files with 171 additions and 146 deletions

View File

@@ -33,8 +33,7 @@ import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts';
import { ReaderStateChapters, TReaderStateMangaContext } from '@/modules/reader/types/Reader.types.ts';
import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types.ts';
import { IReaderSettings, ReaderStateChapters, TReaderStateMangaContext } from '@/modules/reader/types/Reader.types.ts';
import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx';
const useGetPreviousNavBarStaticValue = (isVisible: boolean, isStaticNav: boolean) => {
@@ -65,20 +64,13 @@ const BaseReaderNavBarDesktop = ({
currentChapter,
previousChapter,
nextChapter,
pages,
currentPageIndex,
pageLoadStates,
setPageLoadStates,
setRetryFailedPagesKeyPrefix,
isStaticNav,
exit,
}: ReaderNavBarDesktopProps &
Pick<NavbarContextType, 'setReaderNavBarWidth'> &
Pick<TReaderStateMangaContext, 'manga'> &
Pick<ReaderStateChapters, 'currentChapter' | 'previousChapter' | 'nextChapter' | 'chapters'> &
Pick<
ReaderStatePages,
'pages' | 'currentPageIndex' | 'pageLoadStates' | 'setPageLoadStates' | 'setRetryFailedPagesKeyPrefix'
> & {
Pick<IReaderSettings, 'isStaticNav'> & {
exit: ReturnType<typeof ReaderService.useExit>;
}) => {
const { t } = useTranslation();
@@ -86,29 +78,28 @@ const BaseReaderNavBarDesktop = ({
const getOptionForDirection = useGetOptionForDirection();
const updateReaderSettings = ReaderService.useCreateUpdateSetting(manga ?? DEFAULT_MANGA);
const settings = ReaderService.useSettings();
const [navBarElement, setNavBarElement] = useState<HTMLDivElement | null>();
useResizeObserver(
navBarElement,
useCallback(() => {
if (!settings?.isStaticNav) {
if (!isStaticNav) {
return;
}
setReaderNavBarWidth(navBarElement!.offsetWidth);
}, [navBarElement, settings?.isStaticNav]),
}, [navBarElement, isStaticNav]),
);
useLayoutEffect(() => () => setReaderNavBarWidth(0), []);
const wasNavBarStatic = useGetPreviousNavBarStaticValue(isVisible, settings.isStaticNav);
const wasNavBarStatic = useGetPreviousNavBarStaticValue(isVisible, isStaticNav);
const changedNavBarStaticValue = wasNavBarStatic && isVisible;
const drawerTransitionDuration = changedNavBarStaticValue ? 0 : undefined;
return (
<Drawer
variant={settings.isStaticNav ? 'permanent' : 'persistent'}
open={isVisible || settings.isStaticNav}
variant={isStaticNav ? 'permanent' : 'persistent'}
open={isVisible || isStaticNav}
transitionDuration={drawerTransitionDuration}
PaperProps={{
ref: (ref: HTMLDivElement | null) => setNavBarElement(ref),
@@ -126,9 +117,9 @@ const BaseReaderNavBarDesktop = ({
<IconButton
onClick={() => {
setReaderNavBarWidth(0);
updateReaderSettings('isStaticNav', !settings.isStaticNav);
updateReaderSettings('isStaticNav', !isStaticNav);
}}
color={settings.isStaticNav ? 'primary' : 'inherit'}
color={isStaticNav ? 'primary' : 'inherit'}
>
<PushPinIcon />
</IconButton>
@@ -142,12 +133,7 @@ const BaseReaderNavBarDesktop = ({
chapterTitle={currentChapter.name}
scanlator={currentChapter.scanlator}
/>
<ReaderNavBarDesktopActions
currentChapter={currentChapter}
pageLoadStates={pageLoadStates}
setPageLoadStates={setPageLoadStates}
setRetryFailedPagesKeyPrefix={setRetryFailedPagesKeyPrefix}
/>
<ReaderNavBarDesktopActions />
</>
) : (
<LoadingPlaceholder />
@@ -155,7 +141,7 @@ const BaseReaderNavBarDesktop = ({
</Stack>
<Stack sx={{ p: 2, gap: 2 }}>
<Stack sx={{ gap: 1 }}>
<ReaderNavBarDesktopPageNavigation currentPageIndex={currentPageIndex} pages={pages} />
<ReaderNavBarDesktopPageNavigation />
<ReaderNavBarDesktopChapterNavigation
chapters={chapters}
currentChapter={currentChapter}
@@ -164,13 +150,7 @@ const BaseReaderNavBarDesktop = ({
/>
</Stack>
<Divider />
<ReaderNavBarDesktopQuickSettings
settings={settings}
updateSetting={updateReaderSettings}
openSettings={openSettings}
isDefaultable
onDefault={(...args) => manga && ReaderService.deleteSetting(manga, ...args)}
/>
<ReaderNavBarDesktopQuickSettings openSettings={openSettings} />
</Stack>
</ReaderNavContainer>
</Drawer>
@@ -184,6 +164,7 @@ export const ReaderNavBarDesktop = withPropsFrom(
useReaderStateMangaContext,
useReaderStateChaptersContext,
userReaderStatePagesContext,
ReaderService.useSettingsWithoutDefaultFlag,
() => ({ exit: ReaderService.useExit() }),
],
[
@@ -193,11 +174,7 @@ export const ReaderNavBarDesktop = withPropsFrom(
'currentChapter',
'previousChapter',
'nextChapter',
'pages',
'currentPageIndex',
'pageLoadStates',
'setPageLoadStates',
'setRetryFailedPagesKeyPrefix',
'isStaticNav',
'exit',
],
);

View File

@@ -15,7 +15,7 @@ import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import DownloadIcon from '@mui/icons-material/Download';
import ReplayIcon from '@mui/icons-material/Replay';
import { useMemo, useRef } from 'react';
import { memo, useMemo, useRef } from 'react';
import DeleteIcon from '@mui/icons-material/Delete';
import { actionToTranslationKey, ChapterAction, Chapters } from '@/modules/chapter/services/Chapters.ts';
import { ReaderStateChapters } from '@/modules/reader/types/Reader.types.ts';
@@ -23,6 +23,9 @@ import { requestManager } from '@/lib/requests/RequestManager.ts';
import { DownloadStateIndicator } from '@/modules/core/components/DownloadStateIndicator.tsx';
import { DownloadStatusFieldsFragment } from '@/lib/graphql/generated/graphql.ts';
import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types.ts';
import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx';
import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx';
const DownloadButton = ({
currentChapter,
@@ -59,62 +62,78 @@ const DownloadButton = ({
);
};
export const ReaderNavBarDesktopActions = ({
currentChapter,
pageLoadStates,
setPageLoadStates,
setRetryFailedPagesKeyPrefix,
}: Required<Pick<ReaderStateChapters, 'currentChapter'>> &
Pick<ReaderStatePages, 'pageLoadStates' | 'setPageLoadStates' | 'setRetryFailedPagesKeyPrefix'>) => {
const { id, isBookmarked, realUrl } = currentChapter ?? { id: -1, isBookmarked: false, realUrl: '' };
const BaseReaderNavBarDesktopActions = memo(
({
currentChapter,
pageLoadStates,
setPageLoadStates,
setRetryFailedPagesKeyPrefix,
}: Required<Pick<ReaderStateChapters, 'currentChapter'>> &
Pick<ReaderStatePages, 'pageLoadStates' | 'setPageLoadStates' | 'setRetryFailedPagesKeyPrefix'>) => {
const { id, isBookmarked, realUrl } = currentChapter ?? { id: -1, isBookmarked: false, realUrl: '' };
const { t } = useTranslation();
const { t } = useTranslation();
const pageRetryKeyPrefix = useRef<number>(0);
const pageRetryKeyPrefix = useRef<number>(0);
const { data: downloaderData } = requestManager.useGetDownloadStatus();
const queue = downloaderData?.downloadStatus.queue ?? [];
const { data: downloaderData } = requestManager.useGetDownloadStatus();
const queue = downloaderData?.downloadStatus.queue ?? [];
const downloadChapter = useMemo(
() => queue.find((queueItem) => queueItem.chapter.id === currentChapter?.id),
[queue, id],
);
const downloadChapter = useMemo(
() => queue.find((queueItem) => queueItem.chapter.id === currentChapter?.id),
[queue, id],
);
const haveSomePagesFailedToLoad = useMemo(
() => pageLoadStates.some((pageLoadState) => pageLoadState.error),
[pageLoadStates],
);
const haveSomePagesFailedToLoad = useMemo(
() => pageLoadStates.some((pageLoadState) => pageLoadState.error),
[pageLoadStates],
);
const bookmarkAction: Extract<ChapterAction, 'unbookmark' | 'bookmark'> = isBookmarked ? 'unbookmark' : 'bookmark';
const bookmarkAction: Extract<ChapterAction, 'unbookmark' | 'bookmark'> = isBookmarked
? 'unbookmark'
: 'bookmark';
return (
<Stack sx={{ flexDirection: 'row', justifyContent: 'center', gap: 1 }}>
<Tooltip title={t(actionToTranslationKey[bookmarkAction].action.single)}>
<IconButton onClick={() => Chapters.performAction(bookmarkAction, [id], {})} color="inherit">
{isBookmarked ? <BookmarkIcon /> : <BookmarkBorderIcon />}
</IconButton>
</Tooltip>
<Tooltip title={t('reader.button.retry_load_pages')}>
<IconButton
onClick={() => {
setPageLoadStates((statePageLoadStates) =>
statePageLoadStates.map((pageLoadState) => ({ loaded: pageLoadState.loaded })),
);
setRetryFailedPagesKeyPrefix(`${pageRetryKeyPrefix.current}`);
pageRetryKeyPrefix.current = (pageRetryKeyPrefix.current + 1) % 1000;
}}
disabled={!haveSomePagesFailedToLoad}
color="inherit"
>
<ReplayIcon />
</IconButton>
</Tooltip>
<DownloadButton currentChapter={currentChapter} downloadChapter={downloadChapter} />
<Tooltip title={t('chapter.action.label.open_on_source')}>
<IconButton disabled={!realUrl} href={realUrl ?? ''} rel="noreferrer" target="_blank" color="inherit">
<OpenInNewIcon />
</IconButton>
</Tooltip>
</Stack>
);
};
return (
<Stack sx={{ flexDirection: 'row', justifyContent: 'center', gap: 1 }}>
<Tooltip title={t(actionToTranslationKey[bookmarkAction].action.single)}>
<IconButton onClick={() => Chapters.performAction(bookmarkAction, [id], {})} color="inherit">
{isBookmarked ? <BookmarkIcon /> : <BookmarkBorderIcon />}
</IconButton>
</Tooltip>
<Tooltip title={t('reader.button.retry_load_pages')}>
<IconButton
onClick={() => {
setPageLoadStates((statePageLoadStates) =>
statePageLoadStates.map((pageLoadState) => ({ loaded: pageLoadState.loaded })),
);
setRetryFailedPagesKeyPrefix(`${pageRetryKeyPrefix.current}`);
pageRetryKeyPrefix.current = (pageRetryKeyPrefix.current + 1) % 1000;
}}
disabled={!haveSomePagesFailedToLoad}
color="inherit"
>
<ReplayIcon />
</IconButton>
</Tooltip>
<DownloadButton currentChapter={currentChapter} downloadChapter={downloadChapter} />
<Tooltip title={t('chapter.action.label.open_on_source')}>
<IconButton
disabled={!realUrl}
href={realUrl ?? ''}
rel="noreferrer"
target="_blank"
color="inherit"
>
<OpenInNewIcon />
</IconButton>
</Tooltip>
</Stack>
);
},
);
export const ReaderNavBarDesktopActions = withPropsFrom(
BaseReaderNavBarDesktopActions,
[useReaderStateChaptersContext, userReaderStatePagesContext],
['currentChapter', 'pageLoadStates', 'setPageLoadStates', 'setRetryFailedPagesKeyPrefix'],
);

View File

@@ -11,49 +11,52 @@ import Tooltip from '@mui/material/Tooltip';
import Link from '@mui/material/Link';
import { Link as RouterLink } from 'react-router-dom';
import { memo } from 'react';
import { TypographyMaxLines } from '@/modules/core/components/TypographyMaxLines';
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
export const ReaderNavBarDesktopMetadata = ({
mangaId,
mangaTitle,
chapterTitle,
scanlator,
}: {
mangaId: number;
mangaTitle: string;
chapterTitle: string;
scanlator?: string | null;
}) => (
<Stack>
<Tooltip title={mangaTitle} placement="right">
<TypographyMaxLines lines={3} variant="h6" component="h1" sx={{ textAlign: 'center' }}>
<Link
component={RouterLink}
to={AppRoutes.manga.path(mangaId)}
sx={{ textDecoration: 'none', color: 'inherit' }}
>
{mangaTitle}
</Link>
</TypographyMaxLines>
</Tooltip>
<Tooltip title={chapterTitle} placement="right">
<TypographyMaxLines lines={4} variant="body1" component="h2" sx={{ textAlign: 'center' }}>
{chapterTitle}
</TypographyMaxLines>
</Tooltip>
{scanlator && (
<Tooltip title={scanlator} placement="right">
<TypographyMaxLines
lines={4}
variant="body2"
component="h3"
color="textDisabled"
sx={{ textAlign: 'center' }}
>
{scanlator}
export const ReaderNavBarDesktopMetadata = memo(
({
mangaId,
mangaTitle,
chapterTitle,
scanlator,
}: {
mangaId: number;
mangaTitle: string;
chapterTitle: string;
scanlator?: string | null;
}) => (
<Stack>
<Tooltip title={mangaTitle} placement="right">
<TypographyMaxLines lines={3} variant="h6" component="h1" sx={{ textAlign: 'center' }}>
<Link
component={RouterLink}
to={AppRoutes.manga.path(mangaId)}
sx={{ textDecoration: 'none', color: 'inherit' }}
>
{mangaTitle}
</Link>
</TypographyMaxLines>
</Tooltip>
)}
</Stack>
<Tooltip title={chapterTitle} placement="right">
<TypographyMaxLines lines={4} variant="body1" component="h2" sx={{ textAlign: 'center' }}>
{chapterTitle}
</TypographyMaxLines>
</Tooltip>
{scanlator && (
<Tooltip title={scanlator} placement="right">
<TypographyMaxLines
lines={4}
variant="body2"
component="h3"
color="textDisabled"
sx={{ textAlign: 'center' }}
>
{scanlator}
</TypographyMaxLines>
</Tooltip>
)}
</Stack>
),
);

View File

@@ -22,6 +22,7 @@ import { ReaderNavBarDesktopNextPreviousButton } from '@/modules/reader/componen
import { READING_DIRECTION_TO_THEME_DIRECTION } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts';
import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx';
import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx';
const BaseReaderNavBarDesktopPageNavigation = ({
currentPageIndex,
@@ -81,6 +82,10 @@ const BaseReaderNavBarDesktopPageNavigation = ({
export const ReaderNavBarDesktopPageNavigation = withPropsFrom(
memo(BaseReaderNavBarDesktopPageNavigation),
[() => ({ openPage: ReaderControls.useOpenPage() }), ReaderService.useSettingsWithoutDefaultFlag],
['readingDirection', 'openPage'],
[
userReaderStatePagesContext,
() => ({ openPage: ReaderControls.useOpenPage() }),
ReaderService.useSettingsWithoutDefaultFlag,
],
['currentPageIndex', 'pages', 'readingDirection', 'openPage'],
);

View File

@@ -14,25 +14,40 @@ import { ReaderNavBarDesktopPageScale } from '@/modules/reader/components/overla
import { ReaderNavBarDesktopReadingMode } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopReadingMode.tsx';
import { ReaderNavBarDesktopOffsetDoubleSpread } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopOffsetDoubleSpread.tsx';
import { ReaderNavBarDesktopReadingDirection } from '@/modules/reader/components/overlay/navigation/desktop/quick-settings/ReaderNavBarDesktopReadingDirection.tsx';
import { ReaderSettingsTypeProps } from '@/modules/reader/types/Reader.types.ts';
import { IReaderSettingsWithDefaultFlag, TReaderStateMangaContext } from '@/modules/reader/types/Reader.types.ts';
import { ReaderNavBarDesktopProps } from '@/modules/reader/types/ReaderOverlay.types.ts';
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
import { MangaIdInfo } from '@/modules/manga/Manga.types.ts';
import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx';
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
export const ReaderNavBarDesktopQuickSettings = ({
settings: { readingMode, shouldOffsetDoubleSpreads, pageScaleMode, shouldStretchPage, readingDirection },
updateSetting,
const DEFAULT_MANGA: MangaIdInfo = { id: -1 };
const BaseReaderNavBarDesktopQuickSettings = ({
manga,
readingMode,
shouldOffsetDoubleSpreads,
pageScaleMode,
shouldStretchPage,
readingDirection,
openSettings,
isDefaultable,
onDefault,
}: ReaderSettingsTypeProps & Pick<ReaderNavBarDesktopProps, 'openSettings'>) => {
}: Pick<TReaderStateMangaContext, 'manga'> &
Pick<ReaderNavBarDesktopProps, 'openSettings'> &
Pick<
IReaderSettingsWithDefaultFlag,
'readingMode' | 'shouldOffsetDoubleSpreads' | 'pageScaleMode' | 'shouldStretchPage' | 'readingDirection'
>) => {
const { t } = useTranslation();
const updateSetting = ReaderService.useCreateUpdateSetting(manga ?? DEFAULT_MANGA);
const deleteSetting = ReaderService.useCreateDeleteSetting(manga ?? DEFAULT_MANGA);
return (
<Stack sx={{ gap: 1 }}>
<ReaderNavBarDesktopReadingMode
readingMode={readingMode}
setReadingMode={(value) => updateSetting('readingMode', value)}
isDefaultable={isDefaultable}
onDefault={() => onDefault?.('readingMode')}
isDefaultable
onDefault={() => deleteSetting('readingMode')}
/>
<ReaderNavBarDesktopOffsetDoubleSpread
readingMode={readingMode.value}
@@ -43,14 +58,14 @@ export const ReaderNavBarDesktopQuickSettings = ({
pageScaleMode={pageScaleMode}
shouldStretchPage={shouldStretchPage}
updateSetting={updateSetting}
isDefaultable={isDefaultable}
onDefault={() => onDefault?.('pageScaleMode')}
isDefaultable
onDefault={() => deleteSetting('pageScaleMode')}
/>
<ReaderNavBarDesktopReadingDirection
readingDirection={readingDirection}
setReadingDirection={(value) => updateSetting('readingDirection', value)}
isDefaultable={isDefaultable}
onDefault={() => onDefault?.('readingDirection')}
isDefaultable
onDefault={() => deleteSetting('readingDirection')}
/>
<Button
onClick={() => openSettings()}
@@ -64,3 +79,9 @@ export const ReaderNavBarDesktopQuickSettings = ({
</Stack>
);
};
export const ReaderNavBarDesktopQuickSettings = withPropsFrom(
BaseReaderNavBarDesktopQuickSettings,
[useReaderStateMangaContext, ReaderService.useSettings],
['manga', 'readingMode', 'shouldOffsetDoubleSpreads', 'pageScaleMode', 'shouldStretchPage', 'readingDirection'],
);