Move reader "chapters state" to "reader store"

This commit is contained in:
schroda
2025-09-20 01:33:27 +02:00
parent bada26ead6
commit 5177f9efdc
16 changed files with 220 additions and 299 deletions

View File

@@ -24,9 +24,6 @@ import { makeToast } from '@/base/utils/Toast.ts';
import { MobileHeaderProps } from '@/features/reader/overlay/ReaderOverlay.types.ts';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
import { AppRoutes } from '@/base/AppRoute.constants.ts';
import { ReaderStateChapters } from '@/features/reader/Reader.types.ts';
import { withPropsFrom } from '@/base/hoc/withPropsFrom.tsx';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderLibraryButton } from '@/features/reader/overlay/navigation/components/ReaderLibraryButton.tsx';
import { ReaderBookmarkButton } from '@/features/reader/overlay/navigation/components/ReaderBookmarkButton.tsx';
import { FALLBACK_CHAPTER } from '@/features/chapter/Chapter.constants.ts';
@@ -37,12 +34,10 @@ import { useReaderStoreShallow } from '@/features/reader/ReaderStore.ts';
const DEFAULT_MANGA = { ...FALLBACK_MANGA, title: '' };
const BaseReaderOverlayHeaderMobile = forwardRef<
HTMLDivElement,
MobileHeaderProps & Pick<ReaderStateChapters, 'currentChapter'>
>(({ isVisible, currentChapter }, ref) => {
const BaseReaderOverlayHeaderMobile = forwardRef<HTMLDivElement, MobileHeaderProps>(({ isVisible }, ref) => {
const { t } = useTranslation();
const popupState = usePopupState({ popupId: 'reader-overlay-more-menu', variant: 'popover' });
const currentChapter = useReaderStoreShallow((state) => state.chapters.currentChapter);
const manga = useReaderStoreShallow((state) => state.manga);
const scrollbar = useReaderStoreShallow((state) => state.scrollbar);
@@ -129,8 +124,4 @@ const BaseReaderOverlayHeaderMobile = forwardRef<
);
});
export const ReaderOverlayHeaderMobile = withPropsFrom(
memo(BaseReaderOverlayHeaderMobile),
[useReaderStateChaptersContext],
['currentChapter'],
);
export const ReaderOverlayHeaderMobile = memo(BaseReaderOverlayHeaderMobile);

View File

@@ -24,11 +24,10 @@ import { ReaderNavBarDesktopQuickSettings } from '@/features/reader/overlay/navi
import { ReaderNavBarDesktopActions } from '@/features/reader/overlay/navigation/desktop/components/ReaderNavBarDesktopActions.tsx';
import { useNavBarContext } from '@/features/navigation-bar/NavbarContext.tsx';
import { useResizeObserver } from '@/base/hooks/useResizeObserver.tsx';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderService } from '@/features/reader/services/ReaderService.ts';
import { LoadingPlaceholder } from '@/base/components/feedback/LoadingPlaceholder.tsx';
import { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts';
import { IReaderSettings, ReaderStateChapters } from '@/features/reader/Reader.types.ts';
import { IReaderSettings } from '@/features/reader/Reader.types.ts';
import { withPropsFrom } from '@/base/hoc/withPropsFrom.tsx';
import { ReaderExitButton } from '@/features/reader/overlay/navigation/components/ReaderExitButton.tsx';
import { useReaderStoreShallow } from '@/features/reader/ReaderStore.ts';
@@ -55,17 +54,18 @@ const BaseReaderNavBarDesktop = ({
isVisible,
openSettings,
setReaderNavBarWidth,
chapters,
currentChapter,
previousChapter,
nextChapter,
isStaticNav,
}: ReaderNavBarDesktopProps &
Pick<NavbarContextType, 'setReaderNavBarWidth'> &
Pick<ReaderStateChapters, 'currentChapter' | 'previousChapter' | 'nextChapter' | 'chapters'> &
Pick<IReaderSettings, 'isStaticNav'>) => {
const { t } = useTranslation();
const manga = useReaderStoreShallow((state) => state.manga);
const { chapters, currentChapter, previousChapter, nextChapter } = useReaderStoreShallow((state) => ({
chapters: state.chapters.chapters,
currentChapter: state.chapters.currentChapter,
previousChapter: state.chapters.previousChapter,
nextChapter: state.chapters.nextChapter,
}));
const [navBarElement, setNavBarElement] = useState<HTMLDivElement | null>();
useResizeObserver(
@@ -146,6 +146,6 @@ const BaseReaderNavBarDesktop = ({
export const ReaderNavBarDesktop = withPropsFrom(
memo(BaseReaderNavBarDesktop),
[useNavBarContext, useReaderStateChaptersContext, ReaderService.useSettingsWithoutDefaultFlag],
['setReaderNavBarWidth', 'chapters', 'currentChapter', 'previousChapter', 'nextChapter', 'isStaticNav'],
[useNavBarContext, ReaderService.useSettingsWithoutDefaultFlag],
['setReaderNavBarWidth', 'isStaticNav'],
);

View File

@@ -15,10 +15,7 @@ import { memo, useMemo, useRef } from 'react';
import DeleteIcon from '@mui/icons-material/Delete';
import { CustomTooltip } from '@/base/components/CustomTooltip.tsx';
import { Chapters } from '@/features/chapter/services/Chapters.ts';
import { ReaderStateChapters } from '@/features/reader/Reader.types.ts';
import { DownloadStateIndicator } from '@/base/components/downloads/DownloadStateIndicator.tsx';
import { withPropsFrom } from '@/base/hoc/withPropsFrom.tsx';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderLibraryButton } from '@/features/reader/overlay/navigation/components/ReaderLibraryButton.tsx';
import { ReaderBookmarkButton } from '@/features/reader/overlay/navigation/components/ReaderBookmarkButton.tsx';
import { CHAPTER_ACTION_TO_TRANSLATION, FALLBACK_CHAPTER } from '@/features/chapter/Chapter.constants.ts';
@@ -26,8 +23,13 @@ import { IconBrowser } from '@/assets/icons/IconBrowser.tsx';
import { IconWebView } from '@/assets/icons/IconWebView.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { useReaderStoreShallow } from '@/features/reader/ReaderStore.ts';
import { ChapterDownloadInfo, ChapterIdInfo } from '@/features/chapter/Chapter.types.ts';
const DownloadButton = ({ currentChapter }: Required<Pick<ReaderStateChapters, 'currentChapter'>>) => {
const DownloadButton = ({
currentChapter,
}: {
currentChapter: NullAndUndefined<ChapterIdInfo & ChapterDownloadInfo>;
}) => {
const { t } = useTranslation();
const downloadStatus = Chapters.useDownloadStatusFromCache(currentChapter?.id ?? -1);
@@ -59,76 +61,64 @@ const DownloadButton = ({ currentChapter }: Required<Pick<ReaderStateChapters, '
);
};
const BaseReaderNavBarDesktopActions = memo(
({ currentChapter }: Required<Pick<ReaderStateChapters, 'currentChapter'>>) => {
const { id, isBookmarked, realUrl } = currentChapter ?? FALLBACK_CHAPTER;
export const ReaderNavBarDesktopActions = memo(() => {
const currentChapter = useReaderStoreShallow((state) => state.chapters.currentChapter);
const { t } = useTranslation();
const { pageLoadStates, setPageLoadStates, setRetryFailedPagesKeyPrefix } = useReaderStoreShallow((state) => ({
pageLoadStates: state.pages.pageLoadStates,
setPageLoadStates: state.pages.setPageLoadStates,
setRetryFailedPagesKeyPrefix: state.pages.setRetryFailedPagesKeyPrefix,
}));
const { id, isBookmarked, realUrl } = currentChapter ?? FALLBACK_CHAPTER;
const pageRetryKeyPrefix = useRef<number>(0);
const { t } = useTranslation();
const { pageLoadStates, setPageLoadStates, setRetryFailedPagesKeyPrefix } = useReaderStoreShallow((state) => ({
pageLoadStates: state.pages.pageLoadStates,
setPageLoadStates: state.pages.setPageLoadStates,
setRetryFailedPagesKeyPrefix: state.pages.setRetryFailedPagesKeyPrefix,
}));
const haveSomePagesFailedToLoad = useMemo(
() => pageLoadStates.some((pageLoadState) => pageLoadState.error),
[pageLoadStates],
);
const pageRetryKeyPrefix = useRef<number>(0);
return (
<Stack sx={{ flexDirection: 'row', justifyContent: 'center', gap: 1 }}>
<ReaderLibraryButton />
<ReaderBookmarkButton id={id} isBookmarked={isBookmarked} />
<CustomTooltip title={t('reader.button.retry_load_pages')} disabled={!haveSomePagesFailedToLoad}>
<IconButton
onClick={() => {
setPageLoadStates((statePageLoadStates) =>
statePageLoadStates.map((pageLoadState) => ({
url: pageLoadState.url,
loaded: pageLoadState.loaded,
})),
);
setRetryFailedPagesKeyPrefix(`${pageRetryKeyPrefix.current}`);
pageRetryKeyPrefix.current = (pageRetryKeyPrefix.current + 1) % 1000;
}}
disabled={!haveSomePagesFailedToLoad}
color="inherit"
>
<ReplayIcon />
</IconButton>
</CustomTooltip>
<DownloadButton currentChapter={currentChapter} />
<CustomTooltip title={t('global.button.open_browser')} disabled={!realUrl}>
<IconButton
disabled={!realUrl}
href={realUrl ?? ''}
rel="noreferrer"
target="_blank"
color="inherit"
>
<IconBrowser />
</IconButton>
</CustomTooltip>
<CustomTooltip title={t('global.button.open_webview')} disabled={!realUrl}>
<IconButton
disabled={!realUrl}
href={realUrl ? requestManager.getWebviewUrl(realUrl) : ''}
rel="noreferrer"
target="_blank"
color="inherit"
>
<IconWebView />
</IconButton>
</CustomTooltip>
</Stack>
);
},
);
const haveSomePagesFailedToLoad = useMemo(
() => pageLoadStates.some((pageLoadState) => pageLoadState.error),
[pageLoadStates],
);
export const ReaderNavBarDesktopActions = withPropsFrom(
BaseReaderNavBarDesktopActions,
[useReaderStateChaptersContext],
['currentChapter'],
);
return (
<Stack sx={{ flexDirection: 'row', justifyContent: 'center', gap: 1 }}>
<ReaderLibraryButton />
<ReaderBookmarkButton id={id} isBookmarked={isBookmarked} />
<CustomTooltip title={t('reader.button.retry_load_pages')} disabled={!haveSomePagesFailedToLoad}>
<IconButton
onClick={() => {
setPageLoadStates((statePageLoadStates) =>
statePageLoadStates.map((pageLoadState) => ({
url: pageLoadState.url,
loaded: pageLoadState.loaded,
})),
);
setRetryFailedPagesKeyPrefix(`${pageRetryKeyPrefix.current}`);
pageRetryKeyPrefix.current = (pageRetryKeyPrefix.current + 1) % 1000;
}}
disabled={!haveSomePagesFailedToLoad}
color="inherit"
>
<ReplayIcon />
</IconButton>
</CustomTooltip>
<DownloadButton currentChapter={currentChapter} />
<CustomTooltip title={t('global.button.open_browser')} disabled={!realUrl}>
<IconButton disabled={!realUrl} href={realUrl ?? ''} rel="noreferrer" target="_blank" color="inherit">
<IconBrowser />
</IconButton>
</CustomTooltip>
<CustomTooltip title={t('global.button.open_webview')} disabled={!realUrl}>
<IconButton
disabled={!realUrl}
href={realUrl ? requestManager.getWebviewUrl(realUrl) : ''}
rel="noreferrer"
target="_blank"
color="inherit"
>
<IconWebView />
</IconButton>
</CustomTooltip>
</Stack>
);
});

View File

@@ -23,20 +23,19 @@ import { ReaderBottomBarMobileProps } from '@/features/reader/overlay/ReaderOver
import { MobileReaderProgressBar } from '@/features/reader/overlay/progress-bar/mobile/MobileReaderProgressBar.tsx';
import { ReaderChapterList } from '@/features/reader/overlay/navigation/components/ReaderChapterList.tsx';
import { ReaderBottomBarMobileQuickSettings } from '@/features/reader/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderStateChapters } from '@/features/reader/Reader.types.ts';
import { withPropsFrom } from '@/base/hoc/withPropsFrom.tsx';
import { useResizeObserver } from '@/base/hooks/useResizeObserver.tsx';
import { useReaderStoreShallow } from '@/features/reader/ReaderStore.ts';
const BaseReaderBottomBarMobile = ({
openSettings,
isVisible,
currentChapter,
chapters,
topOffset = 0,
}: ReaderBottomBarMobileProps & Pick<ReaderStateChapters, 'currentChapter' | 'chapters'> & { topOffset?: number }) => {
}: ReaderBottomBarMobileProps & { topOffset?: number }) => {
const { t } = useTranslation();
const { currentChapter, chapters } = useReaderStoreShallow((state) => ({
currentChapter: state.chapters.currentChapter,
chapters: state.chapters.chapters,
}));
const chapterListPopupState = usePopupState({ variant: 'dialog', popupId: 'reader-chapter-list-dialog' });
const quickSettingsPopupState = usePopupState({ variant: 'dialog', popupId: 'reader-quick-settings-dialog' });
@@ -130,8 +129,4 @@ const BaseReaderBottomBarMobile = ({
);
};
export const ReaderBottomBarMobile = withPropsFrom(
memo(BaseReaderBottomBarMobile),
[useReaderStateChaptersContext],
['currentChapter', 'chapters'],
);
export const ReaderBottomBarMobile = memo(BaseReaderBottomBarMobile);

View File

@@ -15,14 +15,13 @@ import Box from '@mui/material/Box';
import { ComponentProps, memo, useCallback, useLayoutEffect, useMemo, useState } from 'react';
import Slide, { SlideProps } from '@mui/material/Slide';
import { ReaderProgressBar } from '@/features/reader/overlay/progress-bar/ReaderProgressBar.tsx';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderService } from '@/features/reader/services/ReaderService.ts';
import {
getPage,
getProgressBarPositionInfo,
} from '@/features/reader/overlay/progress-bar/ReaderProgressBar.utils.tsx';
import { getOptionForDirection } from '@/features/theme/services/ThemeCreator.ts';
import { IReaderSettings, ProgressBarPosition, ReaderStateChapters } from '@/features/reader/Reader.types.ts';
import { IReaderSettings, ProgressBarPosition } from '@/features/reader/Reader.types.ts';
import { ReaderProgressBarDirectionWrapper } from '@/features/reader/overlay/progress-bar/components/ReaderProgressBarDirectionWrapper.tsx';
import { useReaderProgressBarContext } from '@/features/reader/overlay/progress-bar/ReaderProgressBarContext.tsx';
import { withPropsFrom } from '@/base/hoc/withPropsFrom.tsx';
@@ -43,8 +42,6 @@ const PROGRESS_BAR_POSITION_TO_SLIDE_DIRECTION: Record<ProgressBarPosition, Slid
};
const BaseMobileReaderProgressBar = ({
previousChapter,
nextChapter,
setIsMaximized,
isDragging,
direction: readerDirection,
@@ -52,8 +49,7 @@ const BaseMobileReaderProgressBar = ({
progressBarPositionAutoVertical,
topOffset = 0,
bottomOffset = 0,
}: Pick<ReaderStateChapters, 'previousChapter' | 'nextChapter'> &
Pick<TReaderProgressBarContext, 'setIsMaximized' | 'isDragging'> &
}: Pick<TReaderProgressBarContext, 'setIsMaximized' | 'isDragging'> &
Pick<IReaderSettings, 'progressBarPosition' | 'progressBarPositionAutoVertical'> & {
direction: ReturnType<typeof ReaderService.useGetThemeDirection>;
topOffset?: number;
@@ -66,6 +62,10 @@ const BaseMobileReaderProgressBar = ({
currentPageIndex: state.pages.currentPageIndex,
pages: state.pages.pages,
}));
const { previousChapter, nextChapter } = useReaderStore((state) => ({
previousChapter: state.chapters.previousChapter,
nextChapter: state.chapters.nextChapter,
}));
const [, setRefreshProgressBarPosition] = useState({});
useResizeObserver(
@@ -354,18 +354,9 @@ const BaseMobileReaderProgressBar = ({
export const MobileReaderProgressBar = withPropsFrom(
memo(BaseMobileReaderProgressBar),
[
useReaderStateChaptersContext,
useReaderProgressBarContext,
() => ({ direction: ReaderService.useGetThemeDirection() }),
ReaderService.useSettingsWithoutDefaultFlag,
],
[
'previousChapter',
'nextChapter',
'setIsMaximized',
'isDragging',
'direction',
'progressBarPosition',
'progressBarPositionAutoVertical',
],
['setIsMaximized', 'isDragging', 'direction', 'progressBarPosition', 'progressBarPositionAutoVertical'],
);