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

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { ReaderStatePages, ReaderTransitionPageMode } from '@/features/reader/Reader.types.ts';
import { ReaderStateChapters, ReaderStatePages, ReaderTransitionPageMode } from '@/features/reader/Reader.types.ts';
import { requestManager } from '@/lib/requests/RequestManager.ts';
export const READER_DEFAULT_PAGES_STATE: Omit<
@@ -39,3 +39,22 @@ export const READER_DEFAULT_PAGES_STATE: Omit<
transitionPageMode: ReaderTransitionPageMode.NONE,
retryFailedPagesKeyPrefix: '',
};
export const READER_DEFAULT_CHAPTERS_STATE = {
chapters: {
chapters: [],
isCurrentChapterReady: false,
visibleChapters: {
leading: 0,
trailing: 0,
lastLeadingChapterSourceOrder: 99999,
lastTrailingChapterSourceOrder: -1,
isLeadingChapterPreloadMode: true,
isTrailingChapterPreloadMode: true,
scrollIntoView: false,
resumeMode: undefined,
},
},
} satisfies {
chapters: Omit<ReaderStateChapters, 'setReaderStateChapters'>;
};

View File

@@ -18,9 +18,9 @@ import {
createReaderAutoScrollStoreSlice,
ReaderAutoScrollStoreSlice,
} from '@/features/reader/auto-scroll/ReaderAutoScrollStore.ts';
import { ReaderStatePages } from '@/features/reader/Reader.types.ts';
import { ReaderStateChapters, ReaderStatePages } from '@/features/reader/Reader.types.ts';
import { ImmerStateCreator } from '@/lib/zustand/Zustand.types.ts';
import { READER_DEFAULT_PAGES_STATE } from '@/features/reader/ReaderStore.constants.ts';
import { READER_DEFAULT_CHAPTERS_STATE, READER_DEFAULT_PAGES_STATE } from '@/features/reader/ReaderStore.constants.ts';
interface ReaderPagesStoreSlice {
pages: ReaderStatePages & {
@@ -28,7 +28,17 @@ interface ReaderPagesStoreSlice {
};
}
interface ReaderStore extends ReaderOverlayStoreSlice, ReaderAutoScrollStoreSlice, ReaderPagesStoreSlice {
interface ReaderChaptersStoreSlice {
chapters: ReaderStateChapters & {
reset: () => void;
};
}
interface ReaderStore
extends ReaderOverlayStoreSlice,
ReaderAutoScrollStoreSlice,
ReaderPagesStoreSlice,
ReaderChaptersStoreSlice {
reset: () => void;
manga: TMangaReader | undefined;
setManga: (manga: TMangaReader | undefined) => void;
@@ -94,6 +104,30 @@ const createReaderPagesStoreSlice = <T extends ReaderPagesStoreSlice>(
},
});
const createReaderChaptersStoreSlice = <T extends ReaderChaptersStoreSlice>(
...[set, get]: Parameters<ImmerStateCreator<T>>
): ReaderChaptersStoreSlice => ({
chapters: {
...READER_DEFAULT_CHAPTERS_STATE.chapters,
reset: () => set(() => ({ chapters: { ...get().chapters, ...READER_DEFAULT_CHAPTERS_STATE.chapters } })),
setReaderStateChapters: (state) =>
set((draft) => {
if (typeof state === 'function') {
draft.chapters = {
...get().chapters,
...state(get().chapters),
};
return;
}
draft.chapters = {
...get().chapters,
...state,
};
}),
},
});
export const useReaderStore = create<ReaderStore>()(
immer((set, get, store) => ({
...DEFAULT_STATE,
@@ -104,6 +138,7 @@ export const useReaderStore = create<ReaderStore>()(
get().overlay.reset();
get().autoScroll.reset();
get().pages.reset();
get().chapters.reset();
}),
setManga: (manga) =>
set((draft) => {
@@ -123,6 +158,7 @@ export const useReaderStore = create<ReaderStore>()(
...createReaderOverlayStoreSlice(set, get, store),
...createReaderAutoScrollStoreSlice(set, get, store),
...createReaderPagesStoreSlice(set, get, store),
...createReaderChaptersStoreSlice(set, get, store),
})),
);
export const useReaderStoreShallow = <T>(selector: (state: ReaderStore) => T): T =>

View File

@@ -1,47 +0,0 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { createContext, ReactNode, useContext, useMemo, useState } from 'react';
import { ReaderStateChapters } from '@/features/reader/Reader.types.ts';
export const READER_STATE_CHAPTERS_DEFAULTS: Omit<ReaderStateChapters, 'setReaderStateChapters'> = {
chapters: [],
isCurrentChapterReady: false,
visibleChapters: {
leading: 0,
trailing: 0,
lastLeadingChapterSourceOrder: 99999,
lastTrailingChapterSourceOrder: -1,
isLeadingChapterPreloadMode: true,
isTrailingChapterPreloadMode: true,
scrollIntoView: false,
resumeMode: undefined,
},
};
export const ReaderStateChaptersContext = createContext<ReaderStateChapters>({
...READER_STATE_CHAPTERS_DEFAULTS,
setReaderStateChapters: () => {},
});
export const useReaderStateChaptersContext = () => useContext(ReaderStateChaptersContext);
export const ReaderStateChaptersContextProvider = ({ children }: { children: ReactNode }) => {
const [state, setState] =
useState<Omit<ReaderStateChapters, 'setReaderStateChapters'>>(READER_STATE_CHAPTERS_DEFAULTS);
const value = useMemo(
() => ({
...state,
setReaderStateChapters: setState,
}),
[state],
);
return <ReaderStateChaptersContext.Provider value={value}>{children}</ReaderStateChaptersContext.Provider>;
};

View File

@@ -7,11 +7,8 @@
*/
import { ReactNode } from 'react';
import { ReaderStateChaptersContextProvider } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderStateSettingsContextProvider } from '@/features/reader/contexts/state/ReaderStateSettingsContext.tsx';
export const ReaderStateContextProvider = ({ children }: { children: ReactNode }) => (
<ReaderStateChaptersContextProvider>
<ReaderStateSettingsContextProvider>{children}</ReaderStateSettingsContextProvider>
</ReaderStateChaptersContextProvider>
<ReaderStateSettingsContextProvider>{children}</ReaderStateSettingsContextProvider>
);

View File

@@ -7,19 +7,14 @@
*/
import { useEffect } from 'react';
import { ReaderStateChapters, TReaderStateSettingsContext } from '@/features/reader/Reader.types.ts';
import { TReaderStateSettingsContext } from '@/features/reader/Reader.types.ts';
import { DEFAULT_READER_SETTINGS_WITH_DEFAULT_FLAG } from '@/features/reader/settings/ReaderSettingsMetadata.ts';
import { READER_STATE_CHAPTERS_DEFAULTS } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { getReaderStore } from '@/features/reader/ReaderStore.ts';
export const useReaderResetStates = (
setReaderStateChapters: ReaderStateChapters['setReaderStateChapters'],
setSettings: TReaderStateSettingsContext['setSettings'],
) => {
export const useReaderResetStates = (setSettings: TReaderStateSettingsContext['setSettings']) => {
useEffect(
() => () => {
getReaderStore().reset();
setReaderStateChapters(READER_STATE_CHAPTERS_DEFAULTS);
setSettings(DEFAULT_READER_SETTINGS_WITH_DEFAULT_FLAG);
},

View File

@@ -16,11 +16,12 @@ import {
ReaderOpenChapterLocationState,
ReaderStateChapters,
} from '@/features/reader/Reader.types.ts';
import { READER_STATE_CHAPTERS_DEFAULTS } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { filterChapters } from '@/features/chapter/utils/ChapterList.util.tsx';
import { ChapterListOptions } from '@/features/chapter/Chapter.types.ts';
import { getReaderChapterFromCache } from '@/features/reader/Reader.utils.ts';
import { DirectionOffset } from '@/base/Base.types.ts';
import { getReaderStore } from '@/features/reader/ReaderStore.ts';
import { READER_DEFAULT_CHAPTERS_STATE } from '@/features/reader/ReaderStore.constants.ts';
export const useReaderSetChaptersState = (
chaptersResponse: ReturnType<typeof requestManager.useGetMangaChapters<GetChaptersReaderQuery>>,
@@ -28,7 +29,6 @@ export const useReaderSetChaptersState = (
mangaChapters: ReaderStateChapters['mangaChapters'],
initialChapter: ReaderStateChapters['initialChapter'],
chapterForDuplicatesHandling: ReaderStateChapters['chapterForDuplicatesHandling'],
setReaderStateChapters: ReaderStateChapters['setReaderStateChapters'],
shouldSkipDupChapters: IReaderSettings['shouldSkipDupChapters'],
shouldSkipFilteredChapters: IReaderSettings['shouldSkipFilteredChapters'],
chapterListOptions: ChapterListOptions,
@@ -78,7 +78,7 @@ export const useReaderSetChaptersState = (
navigate('', { replace: true, state: { ...locationState, updateInitialChapter: undefined } });
}
setReaderStateChapters((prevState) => {
getReaderStore().chapters.setReaderStateChapters((prevState) => {
const hasCurrentChapterChanged = newCurrentChapter?.id !== prevState.currentChapter?.id;
return {
@@ -93,13 +93,13 @@ export const useReaderSetChaptersState = (
isCurrentChapterReady: hasCurrentChapterChanged ? false : prevState.isCurrentChapterReady,
visibleChapters: hasInitialChapterChanged
? {
...READER_STATE_CHAPTERS_DEFAULTS.visibleChapters,
...READER_DEFAULT_CHAPTERS_STATE.chapters.visibleChapters,
lastLeadingChapterSourceOrder:
newInitialChapter?.sourceOrder ??
READER_STATE_CHAPTERS_DEFAULTS.visibleChapters.lastLeadingChapterSourceOrder,
READER_DEFAULT_CHAPTERS_STATE.chapters.visibleChapters.lastLeadingChapterSourceOrder,
lastTrailingChapterSourceOrder:
newInitialChapter?.sourceOrder ??
READER_STATE_CHAPTERS_DEFAULTS.visibleChapters.lastTrailingChapterSourceOrder,
READER_DEFAULT_CHAPTERS_STATE.chapters.visibleChapters.lastTrailingChapterSourceOrder,
isLeadingChapterPreloadMode: false,
isTrailingChapterPreloadMode: false,
// do not set "scrollIntoView" to "true" for the initial render

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'],
);

View File

@@ -30,13 +30,11 @@ import { ReaderHotkeys } from '@/features/reader/hotkeys/ReaderHotkeys.tsx';
import {
IReaderSettings,
IReaderSettingsWithDefaultFlag,
ReaderStateChapters,
TReaderStateSettingsContext,
} from '@/features/reader/Reader.types.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import { NavbarContextType } from '@/features/navigation-bar/NavigationBar.types.ts';
import { withPropsFrom } from '@/base/hoc/withPropsFrom.tsx';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { TReaderTapZoneContext } from '@/features/reader/tap-zones/TapZoneLayout.types.ts';
import { useReaderTapZoneContext } from '@/features/reader/tap-zones/ReaderTapZoneContext.tsx';
import { useReaderResetStates } from '@/features/reader/hooks/useReaderResetStates.ts';
@@ -61,11 +59,6 @@ const BaseReader = ({
shouldShowReadingModePreview,
shouldShowTapZoneLayoutPreview,
setSettings,
mangaChapters,
initialChapter,
chapterForDuplicatesHandling,
currentChapter,
setReaderStateChapters,
setShowPreview,
}: Pick<NavbarContextType, 'setOverride' | 'readerNavBarWidth'> &
Pick<TReaderStateSettingsContext, 'setSettings'> &
@@ -78,18 +71,18 @@ const BaseReader = ({
| 'shouldShowTapZoneLayoutPreview'
> &
Pick<IReaderSettingsWithDefaultFlag, 'readingMode' | 'tapZoneLayout' | 'tapZoneInvertMode'> &
Pick<
ReaderStateChapters,
| 'mangaChapters'
| 'initialChapter'
| 'chapterForDuplicatesHandling'
| 'currentChapter'
| 'setReaderStateChapters'
> &
Pick<TReaderTapZoneContext, 'setShowPreview'>) => {
const { t } = useTranslation();
const manga = useReaderStoreShallow((state) => state.manga);
const overlay = useReaderStoreShallow((state) => state.overlay);
const { mangaChapters, initialChapter, chapterForDuplicatesHandling, currentChapter } = useReaderStoreShallow(
(state) => ({
mangaChapters: state.chapters.mangaChapters,
initialChapter: state.chapters.initialChapter,
chapterForDuplicatesHandling: state.chapters.chapterForDuplicatesHandling,
currentChapter: state.chapters.currentChapter,
}),
);
const scrollElementRef = useRef<HTMLDivElement | null>(null);
@@ -130,7 +123,7 @@ const BaseReader = ({
useReaderStore.getState().setManga(mangaResponse.data?.manga);
}, [mangaResponse.data?.manga]);
useReaderResetStates(setReaderStateChapters, setSettings);
useReaderResetStates(setSettings);
useReaderSetSettingsState(
mangaResponse,
defaultSettingsResponse,
@@ -156,7 +149,6 @@ const BaseReader = ({
mangaChapters,
initialChapter,
chapterForDuplicatesHandling,
setReaderStateChapters,
shouldSkipDupChapters,
shouldSkipFilteredChapters,
chapterListOptions,
@@ -263,7 +255,6 @@ export const Reader = withPropsFrom(
memo(BaseReader),
[
useNavBarContext,
useReaderStateChaptersContext,
useReaderStateSettingsContext,
() => {
const {
@@ -299,11 +290,6 @@ export const Reader = withPropsFrom(
'shouldShowReadingModePreview',
'shouldShowTapZoneLayoutPreview',
'setSettings',
'mangaChapters',
'initialChapter',
'chapterForDuplicatesHandling',
'currentChapter',
'setReaderStateChapters',
'setShowPreview',
],
);

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { MutableRefObject, RefObject, useCallback, useEffect, useMemo } from 'react';
import { MutableRefObject, RefObject, useCallback, useEffect } from 'react';
import { Direction, useTheme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import { TFunction } from 'i18next';
@@ -19,7 +19,6 @@ import {
} from '@/features/reader/overlay/progress-bar/ReaderProgressBar.utils.tsx';
import { getOptionForDirection } from '@/features/theme/services/ThemeCreator.ts';
import { ReaderService } from '@/features/reader/services/ReaderService.ts';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import {
PageInViewportType,
ProgressBarPosition,
@@ -173,19 +172,20 @@ export class ReaderControls {
shouldInformAboutScanlatorChange,
shouldUseInfiniteScroll,
} = ReaderService.useSettings();
const {
currentChapter,
previousChapter,
nextChapter,
chapters,
visibleChapters: { lastLeadingChapterSourceOrder, lastTrailingChapterSourceOrder },
setReaderStateChapters,
} = useReaderStateChaptersContext();
const openChapter = ReaderService.useNavigateToChapter();
return useCallback(
(offset, doTransitionCheck = true, scrollIntoView = true) => {
const {
currentChapter,
previousChapter,
nextChapter,
chapters,
visibleChapters: { lastLeadingChapterSourceOrder, lastTrailingChapterSourceOrder },
setReaderStateChapters,
} = getReaderStore().chapters;
if (!currentChapter) {
return;
}
@@ -266,13 +266,10 @@ export class ReaderControls {
},
[
t,
currentChapter?.id,
openChapter,
readingMode.value,
shouldInformAboutMissingChapter,
shouldInformAboutScanlatorChange,
lastLeadingChapterSourceOrder,
lastTrailingChapterSourceOrder,
shouldUseInfiniteScroll,
],
);
@@ -343,7 +340,6 @@ export class ReaderControls {
forceDirection?: Direction,
hideOverlay?: boolean,
) => void {
const { previousChapter, nextChapter } = useReaderStateChaptersContext();
const { setShowPreview } = useReaderTapZoneContext();
const { readingDirection, readingMode, shouldShowTransitionPage } = ReaderService.useSettings();
const openChapter = ReaderControls.useOpenChapter();
@@ -398,7 +394,7 @@ export class ReaderControls {
isFirstPage &&
(!shouldShowTransitionPage || isPreviousTransitionPageVisible) &&
convertedPage === 'previous' &&
!!previousChapter;
!!getReaderStore().chapters.previousChapter;
if (shouldOpenPreviousChapter) {
openChapter('previous');
return;
@@ -408,7 +404,7 @@ export class ReaderControls {
isLastPage &&
(!shouldShowTransitionPage || isNextTransitionPageVisible) &&
convertedPage === 'next' &&
!!nextChapter;
!!getReaderStore().chapters.nextChapter;
if (shouldOpenNextChapter) {
openChapter('next');
return;
@@ -442,7 +438,7 @@ export class ReaderControls {
setPageToScrollToIndex(isPreviousMode ? previousPageIndex : nextPageIndex);
},
[direction, openChapter, !!previousChapter, !!nextChapter, shouldShowTransitionPage, readingMode.value],
[direction, openChapter, shouldShowTransitionPage, readingMode.value],
);
}
@@ -451,26 +447,24 @@ export class ReaderControls {
debounceChapterUpdate?: boolean,
endReached?: boolean,
) => void {
const { currentChapter, chapters, previousChapter, nextChapter, visibleChapters, setReaderStateChapters } =
useReaderStateChaptersContext();
const updateChapter = ReaderService.useUpdateChapter();
const {
settings: { downloadAheadLimit },
} = useMetadataServerSettings();
const nextChapters = useMemo(() => {
if (!currentChapter) {
return [];
}
return Chapters.getNextChapters(currentChapter, chapters, {
offset: DirectionOffset.NEXT,
});
}, [currentChapter?.id, chapters]);
return useCallback(
(pageIndex, debounceChapterUpdate = true, endReached = false) => {
const { currentPageIndex, setCurrentPageIndex } = getReaderStore().pages;
const {
pages: { currentPageIndex, setCurrentPageIndex },
chapters: {
currentChapter,
chapters,
previousChapter,
nextChapter,
visibleChapters,
setReaderStateChapters,
},
} = getReaderStore();
if (pageIndex === currentPageIndex && !endReached) {
return;
@@ -482,6 +476,10 @@ export class ReaderControls {
return;
}
const nextChapters = Chapters.getNextChapters(currentChapter, chapters, {
offset: DirectionOffset.NEXT,
});
const direction = currentPageIndex > pageIndex ? DirectionOffset.PREVIOUS : DirectionOffset.NEXT;
ReaderService.downloadAhead(currentChapter, nextChapter, nextChapters, pageIndex, downloadAheadLimit);
@@ -518,14 +516,7 @@ export class ReaderControls {
handleCurrentPageIndexChange();
},
[
currentChapter?.id,
previousChapter?.id,
nextChapter?.id,
nextChapters,
downloadAheadLimit,
visibleChapters,
],
[downloadAheadLimit],
);
}

View File

@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { Direction, useTheme } from '@mui/material/styles';
import { t as translate } from 'i18next';
@@ -36,7 +36,6 @@ import { useBackButton } from '@/base/hooks/useBackButton.ts';
import { GLOBAL_READER_SETTING_KEYS } from '@/features/reader/settings/ReaderSettings.constants.tsx';
import { useReaderStateSettingsContext } from '@/features/reader/contexts/state/ReaderStateSettingsContext.tsx';
import { UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { useMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
import {
getChapterIdsForDownloadAhead,
@@ -155,30 +154,26 @@ export class ReaderService {
}
static useUpdateChapter(): (patch: UpdateChapterPatchInput) => void {
const { currentChapter, mangaChapters, chapters } = useReaderStateChaptersContext();
const { shouldSkipDupChapters } = ReaderService.useSettings();
const {
settings: { deleteChaptersWhileReading, deleteChaptersWithBookmark, updateProgressAfterReading },
} = useMetadataServerSettings();
const previousChapters = useMemo(() => {
if (!currentChapter) {
return [];
}
return Chapters.getNextChapters(currentChapter, chapters, {
offset: DirectionOffset.PREVIOUS,
});
}, [currentChapter?.id, chapters]);
return useCallback(
(patch) => {
const { manga } = getReaderStore();
const {
manga,
chapters: { currentChapter, mangaChapters, chapters },
} = getReaderStore();
if (!manga || !currentChapter || !mangaChapters) {
return;
}
const previousChapters = Chapters.getNextChapters(currentChapter, chapters, {
offset: DirectionOffset.PREVIOUS,
});
const update = async () => {
const chapterIdsToUpdate = Chapters.getIds(
shouldSkipDupChapters
@@ -234,15 +229,7 @@ export class ReaderService {
ReaderService.getOrCreateChapterUpdateQueue(currentChapter.id).enqueue(`${currentChapter.id}`, update);
},
[
currentChapter?.id,
mangaChapters,
previousChapters,
shouldSkipDupChapters,
deleteChaptersWhileReading,
deleteChaptersWithBookmark,
updateProgressAfterReading,
],
[shouldSkipDupChapters, deleteChaptersWhileReading, deleteChaptersWithBookmark, updateProgressAfterReading],
);
}

View File

@@ -27,7 +27,6 @@ import {
PageInViewportType,
ReaderOpenChapterLocationState,
ReaderResumeMode,
ReaderStateChapters,
ReadingDirection,
ReadingMode,
} from '@/features/reader/Reader.types.ts';
@@ -49,7 +48,6 @@ import { useReaderHorizontalModeInvertXYScrolling } from '@/features/reader/view
import { useReaderHideCursorOnInactivity } from '@/features/reader/viewer/hooks/useReaderHideCursorOnInactivity.ts';
import { useReaderScrollToStartOnPageChange } from '@/features/reader/viewer/hooks/useReaderScrollToStartOnPageChange.ts';
import { useReaderHandlePageSelection } from '@/features/reader/viewer/hooks/useReaderHandlePageSelection.ts';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { ReaderChapterViewer } from '@/features/reader/viewer/ReaderChapterViewer.tsx';
import {
getPreviousNextChapterVisibility,
@@ -90,12 +88,6 @@ const BaseReaderViewer = forwardRef(
updateCurrentPageIndex,
showPreview,
setShowPreview,
initialChapter,
currentChapter,
chapters,
visibleChapters,
setReaderStateChapters,
isCurrentChapterReady,
}: Pick<
IReaderSettings,
| 'readingMode'
@@ -111,15 +103,6 @@ const BaseReaderViewer = forwardRef(
| 'isStaticNav'
> &
Pick<NavbarContextType, 'readerNavBarWidth'> &
Pick<
ReaderStateChapters,
| 'initialChapter'
| 'currentChapter'
| 'chapters'
| 'visibleChapters'
| 'setReaderStateChapters'
| 'isCurrentChapterReady'
> &
TReaderTapZoneContext & {
updateCurrentPageIndex: ReturnType<typeof ReaderControls.useUpdateCurrentPageIndex>;
},
@@ -155,6 +138,21 @@ const BaseReaderViewer = forwardRef(
retryFailedPagesKeyPrefix: state.pages.retryFailedPagesKeyPrefix,
setTransitionPageMode: state.pages.setTransitionPageMode,
}));
const {
initialChapter,
currentChapter,
chapters,
visibleChapters,
setReaderStateChapters,
isCurrentChapterReady,
} = useReaderStoreShallow((state) => ({
initialChapter: state.chapters.initialChapter,
currentChapter: state.chapters.currentChapter,
chapters: state.chapters.chapters,
visibleChapters: state.chapters.visibleChapters,
setReaderStateChapters: state.chapters.setReaderStateChapters,
isCurrentChapterReady: state.chapters.isCurrentChapterReady,
}));
const { resumeMode = ReaderResumeMode.START } = useLocation<ReaderOpenChapterLocationState>().state ?? {
resumeMode: ReaderResumeMode.START,
};
@@ -446,7 +444,6 @@ export const ReaderViewer = withPropsFrom(
ReaderService.useSettingsWithoutDefaultFlag,
() => ({ updateCurrentPageIndex: ReaderControls.useUpdateCurrentPageIndex() }),
useReaderTapZoneContext,
useReaderStateChaptersContext,
useNavBarContext,
],
[
@@ -465,11 +462,5 @@ export const ReaderViewer = withPropsFrom(
'updateCurrentPageIndex',
'showPreview',
'setShowPreview',
'initialChapter',
'currentChapter',
'chapters',
'visibleChapters',
'setReaderStateChapters',
'isCurrentChapterReady',
],
);

View File

@@ -13,7 +13,6 @@ import Button from '@mui/material/Button';
import { Link } from 'react-router-dom';
import { ComponentProps, memo, useMemo } from 'react';
import { alpha, useTheme } from '@mui/material/styles';
import { useReaderStateChaptersContext } from '@/features/reader/contexts/state/ReaderStateChaptersContext.tsx';
import { IReaderSettings, ReaderTransitionPageMode, ReadingMode } from '@/features/reader/Reader.types.ts';
import { isTransitionPageVisible } from '@/features/reader/viewer/pager/ReaderPager.utils.tsx';
import { useBackButton } from '@/base/hooks/useBackButton.ts';
@@ -224,7 +223,7 @@ export const ReaderTransitionPage = withPropsFrom(
memo(BaseReaderTransitionPage) as typeof BaseReaderTransitionPage,
[
({ chapterId }: Pick<ComponentProps<typeof BaseReaderTransitionPage>, 'chapterId'>) => {
const { chapters } = useReaderStateChaptersContext();
const chapters = useReaderStoreShallow((state) => state.chapters.chapters);
const currentChapterIndex = useMemo(
() => chapters.findIndex((chapter) => chapter.id === chapterId),
@@ -248,7 +247,7 @@ export const ReaderTransitionPage = withPropsFrom(
ReaderService.useSettingsWithoutDefaultFlag,
({ chapterId, type }: Pick<ComponentProps<typeof BaseReaderTransitionPage>, 'chapterId' | 'type'>) => {
const handleBack = useBackButton();
const { chapters } = useReaderStateChaptersContext();
const chapters = useReaderStoreShallow((state) => state.chapters.chapters);
const currentChapterIndex = useMemo(
() => chapters.findIndex((chapter) => chapter.id === chapterId),