From f5642af219f7fb56ef4200a768ea5728979b2793 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 21 Dec 2024 03:46:55 +0100 Subject: [PATCH] Use HOC for reader context usage --- src/UtilTypes.d.ts | 4 + src/modules/core/hoc/withPropsFrom.tsx | 32 ++ .../reader/components/ReaderPageNumber.tsx | 63 ++- .../reader/components/ReaderRGBAFilter.tsx | 27 +- .../reader/components/TapZoneLayout.tsx | 32 +- .../components/overlay/ReaderOverlay.tsx | 17 +- .../overlay/ReaderOverlayHeaderMobile.tsx | 31 +- .../desktop/ReaderNavBarDesktop.tsx | 61 ++- .../ReaderNavBarDesktopChapterNavigation.tsx | 25 +- .../ReaderNavBarDesktopPageNavigation.tsx | 21 +- .../mobile/ReaderBottomBarMobile.tsx | 21 +- .../ReaderBottomBarMobileQuickSettings.tsx | 17 +- .../progress-bar/ReaderProgressBar.tsx | 76 +-- .../ReaderProgressBarDirectionWrapper.tsx | 16 +- .../variants/MobileReaderProgressBar.tsx | 23 +- .../variants/StandardReaderProgressBar.tsx | 55 +- .../settings/ReaderSettingsTabs.tsx | 32 +- .../reader/components/viewer/ReaderPage.tsx | 62 ++- .../viewer/ReaderTransitionPage.tsx | 45 +- .../reader/components/viewer/ReaderViewer.tsx | 504 ++++++++++-------- .../components/viewer/pager/BasePager.tsx | 59 +- .../viewer/pager/ReaderDoublePagedPager.tsx | 23 +- .../viewer/pager/ReaderHorizontalPager.tsx | 20 +- .../viewer/pager/ReaderVerticalPager.tsx | 20 +- src/modules/reader/screens/Reader.tsx | 123 ++++- src/modules/reader/services/ReaderControls.ts | 8 +- src/modules/reader/services/ReaderService.ts | 39 +- 27 files changed, 997 insertions(+), 459 deletions(-) create mode 100644 src/modules/core/hoc/withPropsFrom.tsx diff --git a/src/UtilTypes.d.ts b/src/UtilTypes.d.ts index a40f511b..609bd7c4 100644 --- a/src/UtilTypes.d.ts +++ b/src/UtilTypes.d.ts @@ -33,3 +33,7 @@ type TupleUnion = { type TransformRecordToWithDefaultFlag> = { [K in keyof T]: { value: T[K]; isDefault: boolean }; }; + +type MergeObjectsArray = T extends [infer F, ...infer R] + ? F & MergeObjects + : {}; diff --git a/src/modules/core/hoc/withPropsFrom.tsx b/src/modules/core/hoc/withPropsFrom.tsx new file mode 100644 index 00000000..0dcb69a2 --- /dev/null +++ b/src/modules/core/hoc/withPropsFrom.tsx @@ -0,0 +1,32 @@ +/* + * 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 { ComponentType, forwardRef } from 'react'; + +type PropsSourceCreator = () => T; + +export const withPropsFrom = < + ComponentProps extends Record, + SourceProps extends Record[], + SourcePropKeys extends keyof (ComponentProps | MergeObjectsArray), +>( + Component: ComponentType, + propsSources: { [K in keyof SourceProps]: PropsSourceCreator }, + sourcePropKeys: SourcePropKeys[], +) => + forwardRef>((props, ref) => { + const sourceProps = propsSources.reduce((acc, propsSource) => ({ ...acc, ...propsSource() }), {}); + + const selectedProps = Object.fromEntries( + Object.entries(sourceProps).filter(([key]) => sourcePropKeys.includes(key as SourcePropKeys)), + ) as Pick, SourcePropKeys>; + + const combinedProps = { ...props, ...selectedProps } as unknown as ComponentProps; + + return ; + }); diff --git a/src/modules/reader/components/ReaderPageNumber.tsx b/src/modules/reader/components/ReaderPageNumber.tsx index 72bb91a9..744f0eb0 100644 --- a/src/modules/reader/components/ReaderPageNumber.tsx +++ b/src/modules/reader/components/ReaderPageNumber.tsx @@ -11,29 +11,46 @@ import Typography from '@mui/material/Typography'; import Stack from '@mui/material/Stack'; import Box from '@mui/material/Box'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; -import { ProgressBarType, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; +import { + IReaderSettings, + ProgressBarType, + ReadingDirection, + TReaderScrollbarContext, +} from '@/modules/reader/types/Reader.types.ts'; import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx'; import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx'; import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; import { reverseString } from '@/util/Strings.ts'; +import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts'; +import { ReaderStatePages, TReaderProgressBarContext } from '@/modules/reader/types/ReaderProgressBar.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const ReaderPageNumber = () => { - const { isDesktop } = ReaderService.useOverlayMode(); - const { scrollbarXSize } = useReaderScrollbarContext(); - const { readerNavBarWidth } = useNavBarContext(); - const { isMaximized } = useReaderProgressBarContext(); - const { currentPageIndex, pages, totalPages } = userReaderStatePagesContext(); - const { progressBarType, shouldShowPageNumber, readingDirection } = ReaderService.useSettings(); - +const BaseReaderPageNumber = ({ + isDesktop, + scrollbarXSize, + readerNavBarWidth, + isMaximized, + currentPageIndex, + pages, + totalPages, + progressBarType, + shouldShowPageNumber, + readingDirection, +}: Pick & + Pick, 'isDesktop'> & + Pick & + Pick & + Pick & + Pick) => { const pageName = useMemo(() => { const currentPageName = getPage(currentPageIndex, pages).name; const SEPARATOR = '/'; const tmpPageName = `${currentPageName}${SEPARATOR}${totalPages}`; - return readingDirection.value === ReadingDirection.LTR ? tmpPageName : reverseString(tmpPageName, SEPARATOR); - }, [currentPageIndex, pages, totalPages, readingDirection.value]); + return readingDirection === ReadingDirection.LTR ? tmpPageName : reverseString(tmpPageName, SEPARATOR); + }, [currentPageIndex, pages, totalPages, readingDirection]); if (!shouldShowPageNumber) { return null; @@ -74,3 +91,27 @@ export const ReaderPageNumber = () => { ); }; + +export const ReaderPageNumber = withPropsFrom( + BaseReaderPageNumber, + [ + ReaderService.useOverlayMode, + useReaderScrollbarContext, + useNavBarContext, + useReaderProgressBarContext, + userReaderStatePagesContext, + ReaderService.useSettingsWithoutDefaultFlag, + ], + [ + 'isDesktop', + 'scrollbarXSize', + 'readerNavBarWidth', + 'isMaximized', + 'currentPageIndex', + 'pages', + 'totalPages', + 'progressBarType', + 'shouldShowPageNumber', + 'readingDirection', + ], +); diff --git a/src/modules/reader/components/ReaderRGBAFilter.tsx b/src/modules/reader/components/ReaderRGBAFilter.tsx index 4c33b2f4..6edde30a 100644 --- a/src/modules/reader/components/ReaderRGBAFilter.tsx +++ b/src/modules/reader/components/ReaderRGBAFilter.tsx @@ -9,18 +9,19 @@ import Box from '@mui/material/Box'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; +import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; +import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts'; -export const ReaderRGBAFilter = () => { - const { readerNavBarWidth } = useNavBarContext(); - const { - customFilter: { - rgba: { - value: { red, green, blue, alpha }, - enabled, - }, +const BaseReaderRGBAFilter = ({ + readerNavBarWidth, + customFilter: { + rgba: { + value: { red, green, blue, alpha }, + enabled, }, - } = ReaderService.useSettings(); - + }, +}: Pick & Pick) => { if (!enabled) { return null; } @@ -39,3 +40,9 @@ export const ReaderRGBAFilter = () => { /> ); }; + +export const ReaderRGBAFilter = withPropsFrom( + BaseReaderRGBAFilter, + [useNavBarContext, ReaderService.useSettingsWithoutDefaultFlag], + ['readerNavBarWidth', 'customFilter'], +); diff --git a/src/modules/reader/components/TapZoneLayout.tsx b/src/modules/reader/components/TapZoneLayout.tsx index fb0c3530..58890969 100644 --- a/src/modules/reader/components/TapZoneLayout.tsx +++ b/src/modules/reader/components/TapZoneLayout.tsx @@ -14,15 +14,23 @@ import { ReaderTapZoneService } from '@/modules/reader/services/ReaderTapZoneSer import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; -import { ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; +import { IReaderSettings, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; +import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts'; +import { TReaderTapZoneContext } from '@/modules/reader/types/TapZoneLayout.types.ts'; const CANVAS_ID = 'reader-tap-zone-layout-canvas'; -export const TapZoneLayout = () => { +const BaseTapZoneLayout = ({ + readerNavBarWidth, + showPreview, + tapZoneLayout, + tapZoneInvertMode, + readingDirection, +}: Pick & + Pick & + Pick) => { const theme = useTheme(); - const { readerNavBarWidth } = useNavBarContext(); - const { showPreview } = useReaderTapZoneContext(); - const { tapZoneLayout, readingDirection, tapZoneInvertMode } = ReaderService.useSettings(); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); @@ -35,10 +43,10 @@ export const TapZoneLayout = () => { }, [tapZoneLayoutElement]), ); - const canvas = ReaderTapZoneService.getOrCreateCanvas(tapZoneLayout.value, width, height, theme.typography.h3, { - vertical: tapZoneInvertMode.value.vertical, - horizontal: tapZoneInvertMode.value.horizontal, - isRTL: readingDirection.value === ReadingDirection.RTL, + const canvas = ReaderTapZoneService.getOrCreateCanvas(tapZoneLayout, width, height, theme.typography.h3, { + vertical: tapZoneInvertMode.vertical, + horizontal: tapZoneInvertMode.horizontal, + isRTL: readingDirection === ReadingDirection.RTL, }); useLayoutEffect(() => { @@ -62,3 +70,9 @@ export const TapZoneLayout = () => { ); }; + +export const TapZoneLayout = withPropsFrom( + BaseTapZoneLayout, + [useNavBarContext, useReaderTapZoneContext, ReaderService.useSettingsWithoutDefaultFlag], + ['readerNavBarWidth', 'showPreview', 'tapZoneLayout', 'tapZoneInvertMode', 'readingDirection'], +); diff --git a/src/modules/reader/components/overlay/ReaderOverlay.tsx b/src/modules/reader/components/overlay/ReaderOverlay.tsx index 85f567c4..3042230d 100644 --- a/src/modules/reader/components/overlay/ReaderOverlay.tsx +++ b/src/modules/reader/components/overlay/ReaderOverlay.tsx @@ -16,10 +16,15 @@ import { ReaderNavBarDesktop } from '@/modules/reader/components/overlay/navigat import { ReaderOverlayHeaderMobile } from '@/modules/reader/components/overlay/ReaderOverlayHeaderMobile.tsx'; import { ReaderBottomBarMobile } from '@/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const ReaderOverlay = ({ isVisible }: BaseReaderOverlayProps & MobileHeaderProps) => { - const { isDesktop, isMobile } = ReaderService.useOverlayMode(); - +const BaseReaderOverlay = ({ + isVisible, + isDesktop, + isMobile, +}: BaseReaderOverlayProps & + MobileHeaderProps & + Pick, 'isDesktop' | 'isMobile'>) => { const [areSettingsOpen, setAreSettingsOpen] = useState(false); return ( @@ -44,3 +49,9 @@ export const ReaderOverlay = ({ isVisible }: BaseReaderOverlayProps & MobileHead ); }; + +export const ReaderOverlay = withPropsFrom( + BaseReaderOverlay, + [ReaderService.useOverlayMode], + ['isDesktop', 'isMobile'], +); diff --git a/src/modules/reader/components/overlay/ReaderOverlayHeaderMobile.tsx b/src/modules/reader/components/overlay/ReaderOverlayHeaderMobile.tsx index 1338d973..acc90642 100644 --- a/src/modules/reader/components/overlay/ReaderOverlayHeaderMobile.tsx +++ b/src/modules/reader/components/overlay/ReaderOverlayHeaderMobile.tsx @@ -28,21 +28,32 @@ import { actionToTranslationKey, ChapterAction, Chapters } from '@/modules/chapt import { useBackButton } from '@/modules/core/hooks/useBackButton.ts'; import { makeToast } from '@/modules/core/utils/Toast.ts'; import { MobileHeaderProps } from '@/modules/reader/types/ReaderOverlay.types.ts'; -import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; -import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx'; -import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { + ReaderStateChapters, + TReaderScrollbarContext, + TReaderStateMangaContext, +} from '@/modules/reader/types/Reader.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; +import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx'; +import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; +import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; const DEFAULT_MANGA = { id: -1, title: '' }; const DEFAULT_CHAPTER = { id: -1, name: '', realUrl: '', isBookmarked: false }; -export const ReaderOverlayHeaderMobile = ({ isVisible }: MobileHeaderProps) => { +const BaseReaderOverlayHeaderMobile = ({ + isVisible, + manga, + currentChapter, + scrollbarYSize, +}: MobileHeaderProps & + Pick & + Pick & + Pick) => { const { t } = useTranslation(); - const { manga } = useReaderStateMangaContext(); - const { currentChapter } = useReaderStateChaptersContext(); const getOptionForDirection = useGetOptionForDirection(); - const { scrollbarYSize } = useReaderScrollbarContext(); const handleBack = useBackButton(); const popupState = usePopupState({ popupId: 'reader-overlay-more-menu', variant: 'popover' }); @@ -128,3 +139,9 @@ export const ReaderOverlayHeaderMobile = ({ isVisible }: MobileHeaderProps) => { ); }; + +export const ReaderOverlayHeaderMobile = withPropsFrom( + BaseReaderOverlayHeaderMobile, + [useReaderStateMangaContext, useReaderStateChaptersContext, useReaderScrollbarContext], + ['manga', 'currentChapter', 'scrollbarYSize'], +); diff --git a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktop.tsx b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktop.tsx index 689f226b..18cb2cf4 100644 --- a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktop.tsx +++ b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktop.tsx @@ -32,6 +32,10 @@ import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/R 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 { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; const useGetPreviousNavBarStaticValue = (isVisible: boolean, isStaticNav: boolean) => { const wasNavBarStaticRef = useRef(isStaticNav); @@ -52,17 +56,35 @@ const useGetPreviousNavBarStaticValue = (isVisible: boolean, isStaticNav: boolea }; const DEFAULT_MANGA: MangaIdInfo = { id: -1 }; -export const ReaderNavBarDesktop = ({ isVisible, openSettings }: ReaderNavBarDesktopProps) => { +const BaseReaderNavBarDesktop = ({ + isVisible, + openSettings, + setReaderNavBarWidth, + manga, + chapters, + currentChapter, + previousChapter, + nextChapter, + pages, + currentPageIndex, + pageLoadStates, + setPageLoadStates, + setRetryFailedPagesKeyPrefix, + exit, +}: ReaderNavBarDesktopProps & + Pick & + Pick & + Pick & + Pick< + ReaderStatePages, + 'pages' | 'currentPageIndex' | 'pageLoadStates' | 'setPageLoadStates' | 'setRetryFailedPagesKeyPrefix' + > & { + exit: ReturnType; + }) => { const { t } = useTranslation(); - const { setReaderNavBarWidth } = useNavBarContext(); - const { manga } = useReaderStateMangaContext(); - const { chapters, currentChapter, nextChapter, previousChapter } = useReaderStateChaptersContext(); - const { pages, currentPageIndex, pageLoadStates, setPageLoadStates, setRetryFailedPagesKeyPrefix } = - userReaderStatePagesContext(); const getOptionForDirection = useGetOptionForDirection(); - const exit = ReaderService.useExit(); const updateReaderSettings = ReaderService.useCreateUpdateSetting(manga ?? DEFAULT_MANGA); const settings = ReaderService.useSettings(); @@ -154,3 +176,28 @@ export const ReaderNavBarDesktop = ({ isVisible, openSettings }: ReaderNavBarDes ); }; + +export const ReaderNavBarDesktop = withPropsFrom( + BaseReaderNavBarDesktop, + [ + useNavBarContext, + useReaderStateMangaContext, + useReaderStateChaptersContext, + userReaderStatePagesContext, + () => ({ exit: ReaderService.useExit() }), + ], + [ + 'setReaderNavBarWidth', + 'manga', + 'chapters', + 'currentChapter', + 'previousChapter', + 'nextChapter', + 'pages', + 'currentPageIndex', + 'pageLoadStates', + 'setPageLoadStates', + 'setRetryFailedPagesKeyPrefix', + 'exit', + ], +); diff --git a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopChapterNavigation.tsx b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopChapterNavigation.tsx index 7032a7da..4807f35f 100644 --- a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopChapterNavigation.tsx +++ b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopChapterNavigation.tsx @@ -23,16 +23,20 @@ import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts' import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { ReaderControls } from '@/modules/reader/services/ReaderControls.ts'; import { ReaderStateChapters } from '@/modules/reader/types/Reader.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const ReaderNavBarDesktopChapterNavigation = ({ +const BaseReaderNavBarDesktopChapterNavigation = ({ currentChapter, previousChapter, nextChapter, chapters = [], -}: Pick) => { + readerThemeDirection, + openChapter, +}: Pick & { + readerThemeDirection: ReturnType; + openChapter: ReturnType; +}) => { const { t } = useTranslation(); - const readerThemeDirection = ReaderService.useGetThemeDirection(); - const openChapter = ReaderControls.useOpenChapter(); const popupState = usePopupState({ variant: 'popover', popupId: 'reader-nav-bar-desktop-chapter-list' }); @@ -111,3 +115,16 @@ export const ReaderNavBarDesktopChapterNavigation = ({ ); }; + +export const ReaderNavBarDesktopChapterNavigation = withPropsFrom( + BaseReaderNavBarDesktopChapterNavigation, + [ + () => ({ + readerThemeDirection: ReaderService.useGetThemeDirection(), + }), + () => ({ + openChapter: ReaderControls.useOpenChapter(), + }), + ], + ['readerThemeDirection', 'openChapter'], +); diff --git a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopPageNavigation.tsx b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopPageNavigation.tsx index abcb4aa8..640f9e68 100644 --- a/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopPageNavigation.tsx +++ b/src/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopPageNavigation.tsx @@ -20,18 +20,23 @@ import { useGetOptionForDirection } from '@/modules/theme/services/ThemeCreator. import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { ReaderNavBarDesktopNextPreviousButton } from '@/modules/reader/components/overlay/navigation/desktop/ReaderNavBarDesktopNextPreviousButton.tsx'; 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'; -export const ReaderNavBarDesktopPageNavigation = ({ +const BaseReaderNavBarDesktopPageNavigation = ({ currentPageIndex, pages, -}: Pick) => { + readingDirection, + openPage, +}: Pick & + Pick & { + openPage: ReturnType; + }) => { const { t } = useTranslation(); - const openPage = ReaderControls.useOpenPage(); - const { readingDirection } = ReaderService.useSettings(); const getOptionForDirection = useGetOptionForDirection(); const currentPage = useMemo(() => getPage(currentPageIndex, pages), [currentPageIndex, pages]); - const direction = READING_DIRECTION_TO_THEME_DIRECTION[readingDirection.value]; + const direction = READING_DIRECTION_TO_THEME_DIRECTION[readingDirection]; return ( @@ -73,3 +78,9 @@ export const ReaderNavBarDesktopPageNavigation = ({ ); }; + +export const ReaderNavBarDesktopPageNavigation = withPropsFrom( + BaseReaderNavBarDesktopPageNavigation, + [() => ({ openPage: ReaderControls.useOpenPage() }), ReaderService.useSettingsWithoutDefaultFlag], + ['readingDirection', 'openPage'], +); diff --git a/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx b/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx index cfe32101..3217d843 100644 --- a/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx +++ b/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx @@ -25,11 +25,20 @@ import { ReaderChapterList } from '@/modules/reader/components/overlay/navigatio import { ReaderBottomBarMobileQuickSettings } from '@/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobileQuickSettings.tsx'; import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; +import { ReaderStateChapters, TReaderScrollbarContext } from '@/modules/reader/types/Reader.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const ReaderBottomBarMobile = ({ openSettings, isVisible }: ReaderBottomBarMobileProps) => { +const BaseReaderBottomBarMobile = ({ + openSettings, + isVisible, + currentChapter, + chapters, + scrollbarXSize, + scrollbarYSize, +}: ReaderBottomBarMobileProps & + Pick & + Pick) => { const { t } = useTranslation(); - const { currentChapter, chapters } = useReaderStateChaptersContext(); - const { scrollbarXSize, scrollbarYSize } = useReaderScrollbarContext(); const chapterListPopupState = usePopupState({ variant: 'dialog', popupId: 'reader-chapter-list-dialog' }); const quickSettingsPopupState = usePopupState({ variant: 'dialog', popupId: 'reader-quick-settings-dialog' }); @@ -112,3 +121,9 @@ export const ReaderBottomBarMobile = ({ openSettings, isVisible }: ReaderBottomB ); }; + +export const ReaderBottomBarMobile = withPropsFrom( + BaseReaderBottomBarMobile, + [useReaderStateChaptersContext, useReaderScrollbarContext], + ['currentChapter', 'chapters', 'scrollbarXSize', 'scrollbarYSize'], +); diff --git a/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobileQuickSettings.tsx b/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobileQuickSettings.tsx index 21ac507f..e3fa5888 100644 --- a/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobileQuickSettings.tsx +++ b/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobileQuickSettings.tsx @@ -13,11 +13,16 @@ import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { DefaultSettingFootnote } from '@/modules/reader/components/settings/DefaultSettingFootnote.tsx'; +import { IReaderSettingsWithDefaultFlag, TReaderStateMangaContext } from '@/modules/reader/types/Reader.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; const DEFAULT_MANGA: MangaIdInfo = { id: -1 }; -export const ReaderBottomBarMobileQuickSettings = () => { - const { manga } = useReaderStateMangaContext(); - const { readingMode, readingDirection } = ReaderService.useSettings(); +const BaseReaderBottomBarMobileQuickSettings = ({ + manga, + readingMode, + readingDirection, +}: Pick & + Pick) => { const deleteSetting = ReaderService.useCreateDeleteSetting(manga ?? DEFAULT_MANGA); if (!manga) { @@ -42,3 +47,9 @@ export const ReaderBottomBarMobileQuickSettings = () => { ); }; + +export const ReaderBottomBarMobileQuickSettings = withPropsFrom( + BaseReaderBottomBarMobileQuickSettings, + [useReaderStateMangaContext, ReaderService.useSettings], + ['manga', 'readingMode', 'readingDirection'], +); diff --git a/src/modules/reader/components/overlay/progress-bar/ReaderProgressBar.tsx b/src/modules/reader/components/overlay/progress-bar/ReaderProgressBar.tsx index de36ce1f..b2db140e 100644 --- a/src/modules/reader/components/overlay/progress-bar/ReaderProgressBar.tsx +++ b/src/modules/reader/components/overlay/progress-bar/ReaderProgressBar.tsx @@ -11,8 +11,7 @@ import { ReactNode, useCallback, useMemo, useRef } from 'react'; import ClickAwayListener from '@mui/material/ClickAwayListener'; import { TypographyProps } from '@mui/material/Typography'; import { StackProps } from '@mui/material/Stack'; -import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx'; -import { ReaderProgressBarProps } from '@/modules/reader/types/ReaderProgressBar.types.ts'; +import { ReaderProgressBarProps, TReaderProgressBarContext } from '@/modules/reader/types/ReaderProgressBar.types.ts'; import { ReaderProgressBarPageNumber } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarPageNumber.tsx'; import { ReaderProgressBarContainer } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarContainer.tsx'; import { ReaderProgressBarRoot } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarRoot.tsx'; @@ -30,8 +29,10 @@ import { getOptionForDirection as getOptionForDirectionImpl } from '@/modules/th import { ReaderProgressBarSlotsActionArea } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarSlotsActionArea.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { ReaderControls } from '@/modules/reader/services/ReaderControls.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; +import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx'; -export const ReaderProgressBar = ({ +const BaseReaderProgressBar = ({ totalPages, pages, pageLoadStates, @@ -40,34 +41,37 @@ export const ReaderProgressBar = ({ slots, createProgressBarSlot, progressBarPosition, -}: ReaderProgressBarProps & { - createProgressBarSlot: ( - page: ReaderProgressBarProps['pages'][number], - pageLoadStates: ReaderProgressBarProps['pageLoadStates'], - pagesIndex: number, - ) => ReactNode; - slotProps?: { - container?: StackProps; - progressBarRoot?: StackProps; - progressBarSlotsActionArea?: StackProps; - progressBarSlotsContainer?: StackProps; - progressBarSlot?: BoxProps; - progressBarReadPages?: BoxProps; - progressBarCurrentPageSlot?: BoxProps; - progressBarPageTexts?: { - base?: TypographyProps; - current?: TypographyProps; - total?: TypographyProps; + isDragging, + setIsDragging, + openPage, + direction, +}: ReaderProgressBarProps & + Pick & { + createProgressBarSlot: ( + page: ReaderProgressBarProps['pages'][number], + pageLoadStates: ReaderProgressBarProps['pageLoadStates'], + pagesIndex: number, + ) => ReactNode; + slotProps?: { + container?: StackProps; + progressBarRoot?: StackProps; + progressBarSlotsActionArea?: StackProps; + progressBarSlotsContainer?: StackProps; + progressBarSlot?: BoxProps; + progressBarReadPages?: BoxProps; + progressBarCurrentPageSlot?: BoxProps; + progressBarPageTexts?: { + base?: TypographyProps; + current?: TypographyProps; + total?: TypographyProps; + }; }; - }; - slots?: { - progressBarCurrentPage?: ReactNode; - }; -}) => { - const { isDragging, setIsDragging } = useReaderProgressBarContext(); - const openPage = ReaderControls.useOpenPage(); - const direction = ReaderService.useGetThemeDirection(); - + slots?: { + progressBarCurrentPage?: ReactNode; + }; + openPage: ReturnType; + direction: ReturnType; + }) => { const progressBarRef = useRef(null); const isHorizontalPosition = getProgressBarPositionInfo(progressBarPosition).isHorizontal; @@ -212,3 +216,15 @@ export const ReaderProgressBar = ({ ); }; + +export const ReaderProgressBar = withPropsFrom( + BaseReaderProgressBar, + [ + useReaderProgressBarContext, + () => ({ openPage: ReaderControls.useOpenPage() }), + () => ({ + direction: ReaderService.useGetThemeDirection(), + }), + ], + ['isDragging', 'setIsDragging', 'openPage', 'direction'], +); diff --git a/src/modules/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx b/src/modules/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx index 46770f94..fa24fe74 100644 --- a/src/modules/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx +++ b/src/modules/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx @@ -16,9 +16,15 @@ import { ThemeMode } from '@/modules/theme/contexts/ThemeModeContext.tsx'; import { createTheme } from '@/modules/theme/services/ThemeCreator.ts'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { DIRECTION_TO_CACHE } from '@/modules/theme/ThemeDirectionCache.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const ReaderProgressBarDirectionWrapper = ({ children }: { children: ReactNode }) => { - const direction = ReaderService.useGetThemeDirection(); +const BaseReaderProgressBarDirectionWrapper = ({ + children, + direction, +}: { + children: ReactNode; + direction: ReturnType; +}) => { const [appTheme] = useLocalStorage('appTheme', 'default'); const [themeMode] = useLocalStorage('themeMode', ThemeMode.SYSTEM); const [pureBlackMode] = useLocalStorage('pureBlackMode', false); @@ -40,3 +46,9 @@ export const ReaderProgressBarDirectionWrapper = ({ children }: { children: Reac ); }; + +export const ReaderProgressBarDirectionWrapper = withPropsFrom( + BaseReaderProgressBarDirectionWrapper, + [() => ({ direction: ReaderService.useGetThemeDirection() })], + ['direction'], +); diff --git a/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx b/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx index e0f6617c..81842d1e 100644 --- a/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx +++ b/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx @@ -20,15 +20,22 @@ import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/R import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; import { getOptionForDirection } from '@/modules/theme/services/ThemeCreator.ts'; -import { ProgressBarPosition, ReaderResumeMode } from '@/modules/reader/types/Reader.types.ts'; +import { ProgressBarPosition, ReaderResumeMode, ReaderStateChapters } from '@/modules/reader/types/Reader.types.ts'; import { ReaderProgressBarDirectionWrapper } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx'; import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx'; import { useReaderOverlayContext } from '@/modules/reader/contexts/ReaderOverlayContext.tsx'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; +import { TReaderOverlayContext } from '@/modules/reader/types/ReaderOverlay.types.ts'; +import { TReaderProgressBarContext } from '@/modules/reader/types/ReaderProgressBar.types.ts'; -export const MobileReaderProgressBar = () => { - const { nextChapter, previousChapter } = useReaderStateChaptersContext(); - const { isVisible } = useReaderOverlayContext(); - const { setIsMaximized } = useReaderProgressBarContext(); +const BaseMobileReaderProgressBar = ({ + previousChapter, + nextChapter, + isVisible, + setIsMaximized, +}: Pick & + Pick & + Pick) => { const pagesState = userReaderStatePagesContext(); const { currentPageIndex, pages } = pagesState; @@ -177,3 +184,9 @@ export const MobileReaderProgressBar = () => { ); }; + +export const MobileReaderProgressBar = withPropsFrom( + BaseMobileReaderProgressBar, + [useReaderStateChaptersContext, useReaderOverlayContext, useReaderProgressBarContext], + ['previousChapter', 'nextChapter', 'isVisible', 'setIsMaximized'], +); diff --git a/src/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx b/src/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx index 972860ca..36564d02 100644 --- a/src/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx +++ b/src/modules/reader/components/overlay/progress-bar/variants/StandardReaderProgressBar.tsx @@ -9,26 +9,40 @@ import { alpha, darken, lighten, useTheme } from '@mui/material/styles'; import Box from '@mui/material/Box'; import { ReaderProgressBar } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBar.tsx'; -import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx'; import { ReaderProgressBarSlot } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarSlot.tsx'; -import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; -import { ProgressBarType } from '@/modules/reader/types/Reader.types.ts'; +import { IReaderSettings, ProgressBarType, TReaderScrollbarContext } from '@/modules/reader/types/Reader.types.ts'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { getPage, getProgressBarPositionInfo } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; import { ReaderProgressBarDirectionWrapper } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx'; +import { TReaderProgressBarContext } from '@/modules/reader/types/ReaderProgressBar.types.ts'; +import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; +import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; +import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx'; import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; -export const StandardReaderProgressBar = () => { +const BaseStandardReaderProgressBar = ({ + readerNavBarWidth, + isMaximized, + setIsMaximized, + isDragging, + progressBarType, + progressBarSize, + progressBarPosition, + readerDirection, + scrollbarXSize, + scrollbarYSize, +}: Pick & + Pick & + Pick & + Pick & { + readerDirection: ReturnType; + }) => { const theme = useTheme(); const pagesState = userReaderStatePagesContext(); const { currentPageIndex, pages } = pagesState; - const { readerNavBarWidth } = useNavBarContext(); - const { isMaximized, setIsMaximized, isDragging } = useReaderProgressBarContext(); - const { progressBarType, progressBarSize, progressBarPosition } = ReaderService.useSettings(); - const readerDirection = ReaderService.useGetThemeDirection(); - const { scrollbarXSize, scrollbarYSize } = useReaderScrollbarContext(); const currentPagesIndex = getPage(currentPageIndex, pages).pagesIndex; @@ -255,3 +269,26 @@ export const StandardReaderProgressBar = () => { ); }; + +export const StandardReaderProgressBar = withPropsFrom( + BaseStandardReaderProgressBar, + [ + useNavBarContext, + useReaderProgressBarContext, + ReaderService.useSettingsWithoutDefaultFlag, + () => ({ readerDirection: ReaderService.useGetThemeDirection() }), + useReaderScrollbarContext, + ], + [ + 'readerNavBarWidth', + 'isMaximized', + 'setIsMaximized', + 'isDragging', + 'progressBarType', + 'progressBarSize', + 'progressBarPosition', + 'readerDirection', + 'scrollbarXSize', + 'scrollbarYSize', + ], +); diff --git a/src/modules/reader/components/settings/ReaderSettingsTabs.tsx b/src/modules/reader/components/settings/ReaderSettingsTabs.tsx index 6bf37828..945bbf65 100644 --- a/src/modules/reader/components/settings/ReaderSettingsTabs.tsx +++ b/src/modules/reader/components/settings/ReaderSettingsTabs.tsx @@ -23,28 +23,30 @@ import { ReaderFilterSettings } from '@/modules/reader/components/settings/filte import { ReaderBehaviourSettings } from '@/modules/reader/components/settings/behaviour/ReaderBehaviourSettings.tsx'; import { ReaderDefaultLayoutSettings } from '@/modules/reader/components/settings/layout/ReaderDefaultLayoutSettings.tsx'; import { ReaderHotkeysSettings } from '@/modules/reader/components/settings/hotkeys/ReaderHotkeysSettings.tsx'; +import { TReaderTapZoneContext } from '@/modules/reader/types/TapZoneLayout.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const ReaderSettingsTabs = ({ +const BaseReaderSettingsTabs = ({ activeTab, setActiveTab, areDefaultSettings, settings, updateSetting, deleteSetting, -}: { - activeTab: number; - setActiveTab: (tab: number) => void; - settings: IReaderSettingsWithDefaultFlag; - updateSetting: (...args: OmitFirst>) => void; - areDefaultSettings?: boolean; - deleteSetting: (setting: keyof IReaderSettings) => void; -}) => { + setShowPreview, + mode: overlayMode, +}: Pick & + Pick, 'mode'> & { + activeTab: number; + setActiveTab: (tab: number) => void; + settings: IReaderSettingsWithDefaultFlag; + updateSetting: (...args: OmitFirst>) => void; + areDefaultSettings?: boolean; + deleteSetting: (setting: keyof IReaderSettings) => void; + }) => { const { t } = useTranslation(); - const { setShowPreview } = useReaderTapZoneContext(); const isTouchDevice = MediaQuery.useIsTouchDevice(); - const { mode: overlayMode } = ReaderService.useOverlayMode(); - return ( <> ); }; + +export const ReaderSettingsTabs = withPropsFrom( + BaseReaderSettingsTabs, + [useReaderTapZoneContext, ReaderService.useOverlayMode], + ['setShowPreview', 'mode'], +); diff --git a/src/modules/reader/components/viewer/ReaderPage.tsx b/src/modules/reader/components/viewer/ReaderPage.tsx index ac1f553b..c087b187 100644 --- a/src/modules/reader/components/viewer/ReaderPage.tsx +++ b/src/modules/reader/components/viewer/ReaderPage.tsx @@ -9,7 +9,7 @@ import { ComponentProps, ForwardedRef, forwardRef } from 'react'; import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; -import { ReaderCustomFilter } from '@/modules/reader/types/Reader.types.ts'; +import { IReaderSettings, ReaderCustomFilter, TReaderScrollbarContext } from '@/modules/reader/types/Reader.types.ts'; import { getImageMarginStyling, getImagePlaceholderStyling, @@ -18,6 +18,7 @@ import { import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; const getCustomFilterString = (customFilter: ReaderCustomFilter): string => Object.keys(customFilter) @@ -48,7 +49,7 @@ const getCustomFilterString = (customFilter: ReaderCustomFilter): string => }) .join(' '); -export const ReaderPage = forwardRef( +const BaseReaderPage = forwardRef( ( { display, @@ -56,18 +57,27 @@ export const ReaderPage = forwardRef( position, marginTop, shouldLoad, + readingMode, + customFilter, + pageScaleMode, + shouldStretchPage, + readerWidth, + scrollbarXSize, + scrollbarYSize, ...props - }: Omit, 'ref' | 'spinnerStyle' | 'imgStyle'> & { - display: boolean; - doublePage?: boolean; - position?: 'left' | 'right'; - marginTop?: number; - }, + }: Omit, 'ref' | 'spinnerStyle' | 'imgStyle'> & + Pick< + IReaderSettings, + 'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth' + > & + Pick & { + display: boolean; + doublePage?: boolean; + position?: 'left' | 'right'; + marginTop?: number; + }, ref: ForwardedRef, ) => { - const { readingMode, customFilter, pageScaleMode, shouldStretchPage, readerWidth } = - ReaderService.useSettings(); - const { scrollbarXSize, scrollbarYSize } = useReaderScrollbarContext(); const isTabletWidth = MediaQuery.useIsTabletWidth(); if (!display && !shouldLoad) { @@ -83,10 +93,10 @@ export const ReaderPage = forwardRef( spinnerStyle={{ backgroundColor: 'background.paper', ...getImagePlaceholderStyling( - readingMode.value, - shouldStretchPage.value, - pageScaleMode.value, - readerWidth.value, + readingMode, + shouldStretchPage, + pageScaleMode, + readerWidth, scrollbarXSize, scrollbarYSize, doublePage, @@ -99,11 +109,11 @@ export const ReaderPage = forwardRef( }} imgStyle={{ ...getImageWidthStyling( - readingMode.value, - shouldStretchPage.value, - pageScaleMode.value, + readingMode, + shouldStretchPage, + pageScaleMode, doublePage, - readerWidth.value, + readerWidth, true, ), display: 'block', @@ -130,3 +140,17 @@ export const ReaderPage = forwardRef( ); }, ); + +export const ReaderPage = withPropsFrom( + BaseReaderPage, + [ReaderService.useSettingsWithoutDefaultFlag, useReaderScrollbarContext], + [ + 'readingMode', + 'customFilter', + 'pageScaleMode', + 'shouldStretchPage', + 'readerWidth', + 'scrollbarXSize', + 'scrollbarYSize', + ], +); diff --git a/src/modules/reader/components/viewer/ReaderTransitionPage.tsx b/src/modules/reader/components/viewer/ReaderTransitionPage.tsx index 321dad23..689ffc00 100644 --- a/src/modules/reader/components/viewer/ReaderTransitionPage.tsx +++ b/src/modules/reader/components/viewer/ReaderTransitionPage.tsx @@ -18,12 +18,14 @@ import { TChapterReader } from '@/modules/chapter/Chapter.types.ts'; import { IReaderSettings, ReaderPageScaleMode, + ReaderStateChapters, ReaderTransitionPageMode, ReadingMode, + TReaderScrollbarContext, + TReaderStateMangaContext, } from '@/modules/reader/types/Reader.types.ts'; import { isTransitionPageVisible } from '@/modules/reader/utils/ReaderPager.utils.tsx'; import { useBackButton } from '@/modules/core/hooks/useBackButton.ts'; -import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { isContinuousReadingMode, @@ -31,6 +33,8 @@ import { } from '@/modules/reader/utils/ReaderSettings.utils.tsx'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { AppRoutes } from '@/modules/core/AppRoute.constants.ts'; +import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; const ChapterInfo = ({ title, @@ -58,22 +62,29 @@ const ChapterInfo = ({ ); }; -export const ReaderTransitionPage = ({ +const BaseReaderTransitionPage = ({ type, mode, readingMode, pageScaleMode, -}: Pick & { - type: Exclude; - mode: ReaderTransitionPageMode; -}) => { + manga, + currentChapter, + previousChapter, + nextChapter, + scrollbarXSize, + scrollbarYSize, + readerNavBarWidth, +}: Pick & + Pick & + Pick & + Pick & + Pick & { + type: Exclude; + mode: ReaderTransitionPageMode; + }) => { const { t } = useTranslation(); const handleBack = useBackButton(); - const { manga } = useReaderStateMangaContext(); - const { currentChapter, nextChapter, previousChapter } = useReaderStateChaptersContext(); - const { scrollbarXSize, scrollbarYSize } = useReaderScrollbarContext(); - const { readerNavBarWidth } = useNavBarContext(); const isPreviousType = type === ReaderTransitionPageMode.PREVIOUS; const isNextType = type === ReaderTransitionPageMode.NEXT; @@ -191,3 +202,17 @@ export const ReaderTransitionPage = ({ ); }; + +export const ReaderTransitionPage = withPropsFrom( + BaseReaderTransitionPage, + [useReaderStateChaptersContext, useReaderScrollbarContext, useNavBarContext], + [ + 'manga', + 'currentChapter', + 'previousChapter', + 'nextChapter', + 'scrollbarXSize', + 'scrollbarYSize', + 'readerNavBarWidth', + ], +); diff --git a/src/modules/reader/components/viewer/ReaderViewer.tsx b/src/modules/reader/components/viewer/ReaderViewer.tsx index 0adc8e76..eae2cad6 100644 --- a/src/modules/reader/components/viewer/ReaderViewer.tsx +++ b/src/modules/reader/components/viewer/ReaderViewer.tsx @@ -19,7 +19,13 @@ import { import Stack from '@mui/material/Stack'; import { useTheme } from '@mui/material/styles'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; -import { PageInViewportType, ReadingDirection, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; +import { + IReaderSettings, + PageInViewportType, + ReadingDirection, + ReadingMode, + TReaderScrollbarContext, +} from '@/modules/reader/types/Reader.types.ts'; import { ReaderPagedPager } from '@/modules/reader/components/viewer/pager/ReaderPagedPager.tsx'; import { ReaderDoublePagedPager } from '@/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx'; import { ReaderHorizontalPager } from '@/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx'; @@ -43,6 +49,9 @@ import { useMouseDragScroll } from '@/modules/core/hooks/useMouseDragScroll.tsx' import { DirectionOffset } from '@/Base.types.ts'; import { useReaderOverlayContext } from '@/modules/reader/contexts/ReaderOverlayContext.tsx'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; +import { TReaderOverlayContext } from '@/modules/reader/types/ReaderOverlay.types.ts'; +import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; const READING_MODE_TO_IN_VIEWPORT_TYPE: Record = { [ReadingMode.SINGLE_PAGE]: PageInViewportType.X, @@ -52,251 +61,308 @@ const READING_MODE_TO_IN_VIEWPORT_TYPE: Record [ReadingMode.WEBTOON]: PageInViewportType.Y, }; -export const ReaderViewer = forwardRef((_, ref: ForwardedRef) => { - const { - currentPageIndex, - pageToScrollToIndex, - pages, - setPages, - totalPages, - pageUrls, - pageLoadStates, - setPageLoadStates, - transitionPageMode, - retryFailedPagesKeyPrefix, - } = userReaderStatePagesContext(); - const { direction: themeDirection } = useTheme(); - const { readingMode, shouldOffsetDoubleSpreads, readingDirection } = ReaderService.useSettings(); - const { setScrollbarXSize, setScrollbarYSize } = useReaderScrollbarContext(); - const { isVisible: isOverlayVisible, setIsVisible: setIsOverlayVisible } = useReaderOverlayContext(); - const updateCurrentPageIndex = ReaderControls.useUpdateCurrentPageIndex(); +const BaseReaderViewer = forwardRef( + ( + { + currentPageIndex, + pageToScrollToIndex, + pages, + setPages, + totalPages, + pageUrls, + pageLoadStates, + setPageLoadStates, + transitionPageMode, + retryFailedPagesKeyPrefix, + readingMode, + shouldOffsetDoubleSpreads, + readingDirection, + setScrollbarXSize, + setScrollbarYSize, + isVisible: isOverlayVisible, + setIsVisible: setIsOverlayVisible, + updateCurrentPageIndex, + }: Pick< + ReaderStatePages, + | 'currentPageIndex' + | 'pageToScrollToIndex' + | 'pages' + | 'setPages' + | 'totalPages' + | 'pageUrls' + | 'pageLoadStates' + | 'setPageLoadStates' + | 'transitionPageMode' + | 'retryFailedPagesKeyPrefix' + > & + Pick & + Pick & + Pick & { + updateCurrentPageIndex: ReturnType; + }, - const scrollElementRef = useRef(null); - useImperativeHandle(ref, () => scrollElementRef.current!); + ref: ForwardedRef, + ) => { + const { direction: themeDirection } = useTheme(); - const isContinuousReadingModeActive = isContinuousReadingMode(readingMode.value); - const isDragging = useMouseDragScroll(isContinuousReadingModeActive ? scrollElementRef : undefined); + const scrollElementRef = useRef(null); + useImperativeHandle(ref, () => scrollElementRef.current!); - const scrollbarXSize = MediaQuery.useGetScrollbarSize('width', scrollElementRef.current); - const scrollbarYSize = MediaQuery.useGetScrollbarSize('height', scrollElementRef.current); - useLayoutEffect(() => { - setScrollbarXSize(scrollbarXSize); - setScrollbarYSize(scrollbarYSize); - }, [scrollbarXSize, scrollbarYSize]); + const isContinuousReadingModeActive = isContinuousReadingMode(readingMode); + const isDragging = useMouseDragScroll(isContinuousReadingModeActive ? scrollElementRef : undefined); - const handleClick = ReaderControls.useHandleClick(scrollElementRef.current); + const scrollbarXSize = MediaQuery.useGetScrollbarSize('width', scrollElementRef.current); + const scrollbarYSize = MediaQuery.useGetScrollbarSize('height', scrollElementRef.current); + useLayoutEffect(() => { + setScrollbarXSize(scrollbarXSize); + setScrollbarYSize(scrollbarYSize); + }, [scrollbarXSize, scrollbarYSize]); - const [wasDoublePageMode, setWasDoublePageMode] = useState(readingMode.value === ReadingMode.DOUBLE_PAGE); - const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(totalPages).fill(false)); + const handleClick = ReaderControls.useHandleClick(scrollElementRef.current); - const imageRefs = useRef<(HTMLElement | null)[]>(pages.map(() => null)); + const [wasDoublePageMode, setWasDoublePageMode] = useState(readingMode === ReadingMode.DOUBLE_PAGE); + const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(totalPages).fill(false)); + + const imageRefs = useRef<(HTMLElement | null)[]>(pages.map(() => null)); + + const actualPages = useMemo(() => { + const arePagesLoaded = pageUrls.length; + if (!arePagesLoaded) { + return pages; + } + + if (readingMode === ReadingMode.DOUBLE_PAGE) { + return getDoublePageModePages( + pageUrls, + pagesToSpreadState, + shouldOffsetDoubleSpreads, + readingDirection, + ); + } - const actualPages = useMemo(() => { - const arePagesLoaded = pageUrls.length; - if (!arePagesLoaded) { return pages; - } + }, [pagesToSpreadState, readingMode, shouldOffsetDoubleSpreads, readingDirection]); - if (readingMode.value === ReadingMode.DOUBLE_PAGE) { - return getDoublePageModePages( - pageUrls, - pagesToSpreadState, - shouldOffsetDoubleSpreads.value, - readingDirection.value, - ); - } + const Pager = useMemo(() => { + switch (readingMode) { + case ReadingMode.SINGLE_PAGE: + return ReaderPagedPager; + case ReadingMode.DOUBLE_PAGE: + return ReaderDoublePagedPager; + case ReadingMode.CONTINUOUS_VERTICAL: + case ReadingMode.WEBTOON: + return ReaderVerticalPager; + case ReadingMode.CONTINUOUS_HORIZONTAL: + return ReaderHorizontalPager; + default: + throw new Error(`Unexpected "ReadingMode" (${readingMode})`); + } + }, [readingMode]); - return pages; - }, [pagesToSpreadState, readingMode.value, shouldOffsetDoubleSpreads.value, readingDirection.value]); + const inViewportType = READING_MODE_TO_IN_VIEWPORT_TYPE[readingMode]; - const Pager = useMemo(() => { - switch (readingMode.value) { - case ReadingMode.SINGLE_PAGE: - return ReaderPagedPager; - case ReadingMode.DOUBLE_PAGE: - return ReaderDoublePagedPager; - case ReadingMode.CONTINUOUS_VERTICAL: - case ReadingMode.WEBTOON: - return ReaderVerticalPager; - case ReadingMode.CONTINUOUS_HORIZONTAL: - return ReaderHorizontalPager; - default: - throw new Error(`Unexpected "ReadingMode" (${readingMode.value})`); - } - }, [readingMode.value]); - - const inViewportType = READING_MODE_TO_IN_VIEWPORT_TYPE[readingMode.value]; - - // reset spread state - useLayoutEffect(() => { - setPagesToSpreadState(Array(totalPages).fill(false)); - }, [totalPages]); - - // optionally convert pages to normal or double page mode - useLayoutEffect(() => { - const convertPagesToNormalPageMode = wasDoublePageMode && readingMode.value !== ReadingMode.DOUBLE_PAGE; - if (convertPagesToNormalPageMode) { - setWasDoublePageMode(false); - setPages(createPagesData(pageUrls)); + // reset spread state + useLayoutEffect(() => { setPagesToSpreadState(Array(totalPages).fill(false)); - return; - } + }, [totalPages]); - const convertPagesToDoublePageMode = readingMode.value === ReadingMode.DOUBLE_PAGE; - if (convertPagesToDoublePageMode) { - if (!wasDoublePageMode) { - updateCurrentPageIndex(getNextIndexFromPage(getPage(currentPageIndex, actualPages))); - } - setPages(actualPages); - setWasDoublePageMode(readingMode.value === ReadingMode.DOUBLE_PAGE); - } - }, [actualPages, readingMode.value]); - - // handle user page selection - useLayoutEffect(() => { - const pageToScrollTo = getPage(pageToScrollToIndex, pages); - - if (isContinuousReadingModeActive) { - const directionOffset = - pageToScrollToIndex > currentPageIndex ? DirectionOffset.PREVIOUS : DirectionOffset.NEXT; - const imageRef = imageRefs.current[pageToScrollTo.pagesIndex]; - - imageRef?.scrollIntoView({ - block: 'start', - inline: getScrollIntoViewInlineOption(directionOffset, themeDirection, readingDirection.value), - }); - } - - const newPageIndex = getNextIndexFromPage(pageToScrollTo); - const isLastPage = newPageIndex === totalPages - 1; - - updateCurrentPageIndex(newPageIndex, !isLastPage); - }, [pageToScrollToIndex]); - - // hide cursor on mouse inactivity - const mouseInactiveTimeout = useRef(); - useEffect(() => { - const setCursorVisibility = (visible: boolean) => { - const scrollElement = scrollElementRef.current; - if (!scrollElement) { + // optionally convert pages to normal or double page mode + useLayoutEffect(() => { + const convertPagesToNormalPageMode = wasDoublePageMode && readingMode !== ReadingMode.DOUBLE_PAGE; + if (convertPagesToNormalPageMode) { + setWasDoublePageMode(false); + setPages(createPagesData(pageUrls)); + setPagesToSpreadState(Array(totalPages).fill(false)); return; } - scrollElement.style.cursor = visible ? 'default' : 'none'; - }; + const convertPagesToDoublePageMode = readingMode === ReadingMode.DOUBLE_PAGE; + if (convertPagesToDoublePageMode) { + if (!wasDoublePageMode) { + updateCurrentPageIndex(getNextIndexFromPage(getPage(currentPageIndex, actualPages))); + } + setPages(actualPages); + setWasDoublePageMode(readingMode === ReadingMode.DOUBLE_PAGE); + } + }, [actualPages, readingMode]); - const handleMouseMove = () => { - setCursorVisibility(true); - clearTimeout(mouseInactiveTimeout.current); - mouseInactiveTimeout.current = setTimeout(() => { - setCursorVisibility(false); - }, 5000); - }; + // handle user page selection + useLayoutEffect(() => { + const pageToScrollTo = getPage(pageToScrollToIndex, pages); - handleMouseMove(); - window.addEventListener('mousemove', handleMouseMove); - return () => { - setCursorVisibility(true); - window.removeEventListener('mousemove', handleMouseMove); - clearTimeout(mouseInactiveTimeout.current); - }; - }, []); + if (isContinuousReadingModeActive) { + const directionOffset = + pageToScrollToIndex > currentPageIndex ? DirectionOffset.PREVIOUS : DirectionOffset.NEXT; + const imageRef = imageRefs.current[pageToScrollTo.pagesIndex]; - // invert x and y scrolling for the continuous horizontal reading mode - useEffect(() => { - if (readingMode.value !== ReadingMode.CONTINUOUS_HORIZONTAL) { - return () => {}; - } - - if (!scrollElementRef.current) { - return () => {}; - } - - const handleScroll = (e: WheelEvent) => { - e.preventDefault(); - - if (e.shiftKey) { - scrollElementRef.current?.scrollBy({ - top: e.deltaY, + imageRef?.scrollIntoView({ + block: 'start', + inline: getScrollIntoViewInlineOption(directionOffset, themeDirection, readingDirection), }); - return; } - scrollElementRef.current?.scrollBy({ - left: readingDirection.value === ReadingDirection.LTR ? e.deltaY : e.deltaY * -1, - }); - }; + const newPageIndex = getNextIndexFromPage(pageToScrollTo); + const isLastPage = newPageIndex === totalPages - 1; - scrollElementRef.current.addEventListener('wheel', handleScroll); - return () => scrollElementRef.current?.removeEventListener('wheel', handleScroll); - }, [readingMode.value, readingDirection.value]); + updateCurrentPageIndex(newPageIndex, !isLastPage); + }, [pageToScrollToIndex]); - // hide overlay on user triggered scroll - useEffect(() => { - const handleScroll = () => { - if (isOverlayVisible) { - setIsOverlayVisible(false); + // hide cursor on mouse inactivity + const mouseInactiveTimeout = useRef(); + useEffect(() => { + const setCursorVisibility = (visible: boolean) => { + const scrollElement = scrollElementRef.current; + if (!scrollElement) { + return; + } + + scrollElement.style.cursor = visible ? 'default' : 'none'; + }; + + const handleMouseMove = () => { + setCursorVisibility(true); + clearTimeout(mouseInactiveTimeout.current); + mouseInactiveTimeout.current = setTimeout(() => { + setCursorVisibility(false); + }, 5000); + }; + + handleMouseMove(); + window.addEventListener('mousemove', handleMouseMove); + return () => { + setCursorVisibility(true); + window.removeEventListener('mousemove', handleMouseMove); + clearTimeout(mouseInactiveTimeout.current); + }; + }, []); + + // invert x and y scrolling for the continuous horizontal reading mode + useEffect(() => { + if (readingMode !== ReadingMode.CONTINUOUS_HORIZONTAL) { + return () => {}; } - }; - scrollElementRef.current?.addEventListener('wheel', handleScroll); - scrollElementRef.current?.addEventListener('touchmove', handleScroll); - return () => { - scrollElementRef.current?.removeEventListener('wheel', handleScroll); - scrollElementRef.current?.removeEventListener('touchmove', handleScroll); - }; - }, [isOverlayVisible]); - - return ( - !isDragging && handleClick(e)} - onScroll={() => - ReaderControls.updateCurrentPageOnScroll( - imageRefs, - totalPages - 1, - updateCurrentPageIndex, - inViewportType, - readingDirection.value, - ) + if (!scrollElementRef.current) { + return () => {}; } - > - { - const page = actualPages[pagesIndex]; - const { index, url } = isPrimary ? page.primary : page.secondary!; - if (readingMode.value === ReadingMode.DOUBLE_PAGE) { - const img = new Image(); - img.onload = () => { - setPagesToSpreadState((prevState) => prevState.toSpliced(index, 1, isSpreadPage(img))); - }; - img.src = url; - } + const handleScroll = (e: WheelEvent) => { + e.preventDefault(); - setPageLoadStates((statePageLoadStates) => - statePageLoadStates.toSpliced(index, 1, { loaded: true }), - ); + if (e.shiftKey) { + scrollElementRef.current?.scrollBy({ + top: e.deltaY, + }); + return; + } + + scrollElementRef.current?.scrollBy({ + left: readingDirection === ReadingDirection.LTR ? e.deltaY : e.deltaY * -1, + }); + }; + + scrollElementRef.current.addEventListener('wheel', handleScroll); + return () => scrollElementRef.current?.removeEventListener('wheel', handleScroll); + }, [readingMode, readingDirection]); + + // hide overlay on user triggered scroll + useEffect(() => { + const handleScroll = () => { + if (isOverlayVisible) { + setIsOverlayVisible(false); + } + }; + + scrollElementRef.current?.addEventListener('wheel', handleScroll); + scrollElementRef.current?.addEventListener('touchmove', handleScroll); + return () => { + scrollElementRef.current?.removeEventListener('wheel', handleScroll); + scrollElementRef.current?.removeEventListener('touchmove', handleScroll); + }; + }, [isOverlayVisible]); + + return ( + { - setPageLoadStates((statePageLoadStates) => - statePageLoadStates.toSpliced(pageIndex, 1, { loaded: false, error: true }), - ); - }} - /> - - ); -}); + onClick={(e) => !isDragging && handleClick(e)} + onScroll={() => + ReaderControls.updateCurrentPageOnScroll( + imageRefs, + totalPages - 1, + updateCurrentPageIndex, + inViewportType, + readingDirection, + ) + } + > + { + const page = actualPages[pagesIndex]; + const { index, url } = isPrimary ? page.primary : page.secondary!; + + if (readingMode === ReadingMode.DOUBLE_PAGE) { + const img = new Image(); + img.onload = () => { + setPagesToSpreadState((prevState) => prevState.toSpliced(index, 1, isSpreadPage(img))); + }; + img.src = url; + } + + setPageLoadStates((statePageLoadStates) => + statePageLoadStates.toSpliced(index, 1, { loaded: true }), + ); + }} + onError={(pageIndex) => { + setPageLoadStates((statePageLoadStates) => + statePageLoadStates.toSpliced(pageIndex, 1, { loaded: false, error: true }), + ); + }} + /> + + ); + }, +); + +export const ReaderViewer = withPropsFrom( + BaseReaderViewer, + [ + userReaderStatePagesContext, + ReaderService.useSettingsWithoutDefaultFlag, + useReaderScrollbarContext, + useReaderOverlayContext, + () => ({ updateCurrentPageIndex: ReaderControls.useUpdateCurrentPageIndex() }), + ], + [ + 'currentPageIndex', + 'pageToScrollToIndex', + 'pages', + 'setPages', + 'totalPages', + 'pageUrls', + 'pageLoadStates', + 'setPageLoadStates', + 'transitionPageMode', + 'retryFailedPagesKeyPrefix', + 'readingMode', + 'shouldOffsetDoubleSpreads', + 'readingDirection', + 'setScrollbarXSize', + 'setScrollbarYSize', + 'isVisible', + 'setIsVisible', + 'updateCurrentPageIndex', + ], +); diff --git a/src/modules/reader/components/viewer/pager/BasePager.tsx b/src/modules/reader/components/viewer/pager/BasePager.tsx index 51abe701..7afe23aa 100644 --- a/src/modules/reader/components/viewer/pager/BasePager.tsx +++ b/src/modules/reader/components/viewer/pager/BasePager.tsx @@ -11,29 +11,36 @@ import Box, { BoxProps } from '@mui/material/Box'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { getImageWidthStyling, getPageIndexesToLoad } from '@/modules/reader/utils/ReaderPager.utils.tsx'; import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types.ts'; -import { ReaderPagerProps, ReaderTransitionPageMode } from '@/modules/reader/types/Reader.types.ts'; +import { IReaderSettings, ReaderPagerProps, ReaderTransitionPageMode } from '@/modules/reader/types/Reader.types.ts'; import { ReaderTransitionPage } from '@/modules/reader/components/viewer/ReaderTransitionPage.tsx'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const BasePager = ({ +const BaseBasePager = ({ currentPageIndex, pages, transitionPageMode, imageRefs, createPage, slots, -}: Omit & { - createPage: ( - page: ReaderStatePages['pages'][number], - pagesIndex: number, - shouldLoad: boolean, - shouldDisplay: boolean, - setRef: (element: HTMLElement | null) => void, - ) => ReactNode; - slots?: { boxProps?: BoxProps }; -}) => { - const { readingMode, pageScaleMode, shouldStretchPage, readerWidth, imagePreLoadAmount } = - ReaderService.useSettings(); - + readingMode, + pageScaleMode, + shouldStretchPage, + readerWidth, + imagePreLoadAmount, +}: Omit & + Pick< + IReaderSettings, + 'readingMode' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth' | 'imagePreLoadAmount' + > & { + createPage: ( + page: ReaderStatePages['pages'][number], + pagesIndex: number, + shouldLoad: boolean, + shouldDisplay: boolean, + setRef: (element: HTMLElement | null) => void, + ) => ReactNode; + slots?: { boxProps?: BoxProps }; + }) => { const previousCurrentPageIndex = useRef(-1); const pagesIndexesToRender = useMemo( () => getPageIndexesToLoad(currentPageIndex, pages, previousCurrentPageIndex.current, imagePreLoadAmount), @@ -48,20 +55,14 @@ export const BasePager = ({ {...slots?.boxProps} sx={[ ...(Array.isArray(slots?.boxProps?.sx) ? (slots?.boxProps?.sx ?? []) : [slots?.boxProps?.sx]), - getImageWidthStyling( - readingMode.value, - shouldStretchPage.value, - pageScaleMode.value, - false, - readerWidth.value, - ), + getImageWidthStyling(readingMode, shouldStretchPage, pageScaleMode, false, readerWidth), ]} > {pages.map((page, pagesIndex) => createPage( @@ -78,9 +79,15 @@ export const BasePager = ({ ); }; + +export const BasePager = withPropsFrom( + BaseBasePager, + [ReaderService.useSettingsWithoutDefaultFlag], + ['readingMode', 'pageScaleMode', 'shouldStretchPage', 'readerWidth', 'imagePreLoadAmount'], +); diff --git a/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx b/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx index 75f6bfec..fe01c3e7 100644 --- a/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderDoublePagedPager.tsx @@ -10,10 +10,11 @@ import { Direction, useTheme } from '@mui/material/styles'; import { Fragment, useMemo } from 'react'; import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; -import { ReaderPagerProps, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; +import { IReaderSettings, ReaderPagerProps, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; import { getNextIndexFromPage, getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; const getPagePosition = ( pageType: 'first' | 'second', @@ -37,20 +38,20 @@ const getPagePosition = ( return isLtrReadingDirection ? 'right' : 'left'; }; -export const ReaderDoublePagedPager = ({ +const BaseReaderDoublePagedPager = ({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, + readingDirection, ...props -}: ReaderPagerProps) => { +}: ReaderPagerProps & Pick) => { const { currentPageIndex, pages, totalPages } = props; - const { readingDirection } = ReaderService.useSettings(); const { direction: themeDirection } = useTheme(); const currentPage = useMemo(() => getPage(currentPageIndex, pages), [currentPageIndex, pages]); - const isLtrReadingDirection = readingDirection.value === ReadingDirection.LTR; + const isLtrReadingDirection = readingDirection === ReadingDirection.LTR; return ( @@ -115,3 +114,9 @@ export const ReaderDoublePagedPager = ({ /> ); }; + +export const ReaderDoublePagedPager = withPropsFrom( + BaseReaderDoublePagedPager, + [ReaderService.useSettingsWithoutDefaultFlag], + ['readingDirection'], +); diff --git a/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx b/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx index 1bbaa913..f353e12a 100644 --- a/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderHorizontalPager.tsx @@ -10,22 +10,24 @@ import { useTheme } from '@mui/material/styles'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; -import { ReaderPagerProps, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; +import { IReaderSettings, ReaderPagerProps, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const ReaderHorizontalPager = ({ +const BaseReaderHorizontalPager = ({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, + pageGap, + readingDirection, ...props -}: ReaderPagerProps) => { +}: ReaderPagerProps & Pick) => { const { currentPageIndex, totalPages } = props; - const { pageGap, readingDirection } = ReaderService.useSettings(); const { direction: themeDirection } = useTheme(); - const isLtrReadingDirection = readingDirection.value === ReadingDirection.LTR; + const isLtrReadingDirection = readingDirection === ReadingDirection.LTR; return ( ); }; + +export const ReaderHorizontalPager = withPropsFrom( + BaseReaderHorizontalPager, + [ReaderService.useSettingsWithoutDefaultFlag], + ['pageGap', 'readingDirection'], +); diff --git a/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx b/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx index 1fabbfdb..7ba9b073 100644 --- a/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx +++ b/src/modules/reader/components/viewer/pager/ReaderVerticalPager.tsx @@ -7,22 +7,24 @@ */ import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx'; -import { ReaderPagerProps, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; +import { IReaderSettings, ReaderPagerProps, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; +import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; -export const ReaderVerticalPager = ({ +const BaseReaderVerticalPager = ({ onLoad, onError, pageLoadStates, retryFailedPagesKeyPrefix, + readingMode, + pageGap, ...props -}: ReaderPagerProps) => { +}: ReaderPagerProps & Pick) => { const { currentPageIndex, totalPages } = props; - const { readingMode, pageGap } = ReaderService.useSettings(); - const isWebtoonMode = readingMode.value === ReadingMode.WEBTOON; - const actualPageGap = isWebtoonMode ? 0 : pageGap.value; + const isWebtoonMode = readingMode === ReadingMode.WEBTOON; + const actualPageGap = isWebtoonMode ? 0 : pageGap; return ( ); }; + +export const ReaderVerticalPager = withPropsFrom( + BaseReaderVerticalPager, + [ReaderService.useSettingsWithoutDefaultFlag], + ['pageGap', 'readingMode'], +); diff --git a/src/modules/reader/screens/Reader.tsx b/src/modules/reader/screens/Reader.tsx index a7212ede..f0d50c53 100644 --- a/src/modules/reader/screens/Reader.tsx +++ b/src/modules/reader/screens/Reader.tsx @@ -7,7 +7,7 @@ */ import Box from '@mui/material/Box'; -import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { useLocation, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { @@ -24,7 +24,6 @@ import { LoadingPlaceholder } from '@/modules/core/components/placeholder/Loadin import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx'; import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx'; -import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; import { GET_CHAPTERS_READER } from '@/lib/graphql/queries/ChapterQuery.ts'; import { Chapters } from '@/modules/chapter/services/Chapters.ts'; import { DirectionOffset } from '@/Base.types.ts'; @@ -38,38 +37,72 @@ import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { READER_BACKGROUND_TO_COLOR } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import { createPageData, createPagesData } from '@/modules/reader/utils/ReaderPager.utils.tsx'; import { ReaderHotkeys } from '@/modules/reader/components/ReaderHotkeys.tsx'; -import { ReaderResumeMode, ReaderTransitionPageMode } from '@/modules/reader/types/Reader.types.ts'; +import { + IReaderSettings, + ReaderResumeMode, + ReaderStateChapters, + ReaderTransitionPageMode, + TReaderStateMangaContext, + TReaderStateSettingsContext, +} from '@/modules/reader/types/Reader.types.ts'; import { getInitialReaderPageIndex } from '@/modules/reader/utils/Reader.utils.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; +import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts'; +import { TReaderOverlayContext } from '@/modules/reader/types/ReaderOverlay.types.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'; -export const Reader = () => { +const BaseReader = ({ + setTitle, + setOverride, + readerNavBarWidth, + isVisible: isOverlayVisible, + setIsVisible: setIsOverlayVisible, + manga, + setManga, + shouldSkipDupChapters, + backgroundColor, + setSettings, + initialChapter, + currentChapter, + chapters, + setReaderStateChapters, + firstPageUrl, + totalPages, + setTotalPages, + setCurrentPageIndex, + setPageToScrollToIndex, + setPages, + setPageUrls, + setPageLoadStates, + setTransitionPageMode, +}: Pick & + Pick & + Pick & + Pick & + Pick & + Pick & + Pick< + ReaderStatePages, + | 'totalPages' + | 'setTotalPages' + | 'setCurrentPageIndex' + | 'setPageToScrollToIndex' + | 'setPages' + | 'setPageUrls' + | 'setPageLoadStates' + | 'setTransitionPageMode' + > & { + firstPageUrl?: string; + }) => { const { t } = useTranslation(); - const { setTitle, setOverride, readerNavBarWidth } = useNavBarContext(); - const { isVisible: isOverlayVisible, setIsVisible: setIsOverlayVisible } = useReaderOverlayContext(); - const { manga, setManga } = useReaderStateMangaContext(); - const { - settings: { shouldSkipDupChapters }, - setSettings, - } = useReaderStateSettingsContext(); - const { initialChapter, currentChapter, chapters, setReaderStateChapters } = useReaderStateChaptersContext(); - const { - setTotalPages, - setCurrentPageIndex, - setPageToScrollToIndex, - pages, - setPages, - setPageUrls, - setPageLoadStates, - setTransitionPageMode, - } = userReaderStatePagesContext(); const { resumeMode } = useLocation<{ resumeMode: ReaderResumeMode; }>().state ?? { resumeMode: ReaderResumeMode.START }; const scrollElementRef = useRef(null); - const { backgroundColor } = ReaderService.useSettings(); - const { chapterIndex: paramChapterIndex, mangaId: paramMangaId } = useParams<{ chapterIndex: string; mangaId: string; @@ -321,7 +354,7 @@ export const Reader = () => { return null; } - const isPlaceholderPageState = pages.length === 1 && pages[0].primary.url === requestManager.getBaseUrl(); + const isPlaceholderPageState = totalPages === 1 && firstPageUrl === requestManager.getBaseUrl(); if (isPlaceholderPageState) { return null; } @@ -350,3 +383,43 @@ export const Reader = () => { ); }; + +export const Reader = withPropsFrom( + memo(BaseReader), + [ + useNavBarContext, + useReaderOverlayContext, + useReaderStateMangaContext, + useReaderStateChaptersContext, + useReaderStateSettingsContext, + ReaderService.useSettingsWithoutDefaultFlag, + userReaderStatePagesContext, + () => ({ + firstPageUrl: userReaderStatePagesContext().pages[0].primary.url, + }), + ], + [ + 'setTitle', + 'setOverride', + 'readerNavBarWidth', + 'isVisible', + 'setIsVisible', + 'manga', + 'setManga', + 'shouldSkipDupChapters', + 'setSettings', + 'initialChapter', + 'currentChapter', + 'chapters', + 'setReaderStateChapters', + 'firstPageUrl', + 'totalPages', + 'setTotalPages', + 'setCurrentPageIndex', + 'setPageToScrollToIndex', + 'setPages', + 'setPageUrls', + 'setPageLoadStates', + 'setTransitionPageMode', + ], +); diff --git a/src/modules/reader/services/ReaderControls.ts b/src/modules/reader/services/ReaderControls.ts index 60300cd6..9b504c83 100644 --- a/src/modules/reader/services/ReaderControls.ts +++ b/src/modules/reader/services/ReaderControls.ts @@ -412,9 +412,9 @@ export class ReaderControls { }); }; - clearTimeout(this.updateCurrentPageTimeout); + clearTimeout(ReaderControls.updateCurrentPageTimeout); if (debounceChapterUpdate) { - this.updateCurrentPageTimeout = setTimeout(handleCurrentPageIndexChange, 1000); + ReaderControls.updateCurrentPageTimeout = setTimeout(handleCurrentPageIndexChange, 1000); return; } @@ -480,7 +480,7 @@ export class ReaderControls { break; case TapZoneRegionType.PREVIOUS: if (isContinuousReadingModeActive) { - this.scroll( + ReaderControls.scroll( ScrollOffset.BACKWARD, scrollDirection, readingMode.value, @@ -496,7 +496,7 @@ export class ReaderControls { break; case TapZoneRegionType.NEXT: if (isContinuousReadingModeActive) { - this.scroll( + ReaderControls.scroll( ScrollOffset.FORWARD, scrollDirection, readingMode.value, diff --git a/src/modules/reader/services/ReaderService.ts b/src/modules/reader/services/ReaderService.ts index 1f1e4c49..69285dcc 100644 --- a/src/modules/reader/services/ReaderService.ts +++ b/src/modules/reader/services/ReaderService.ts @@ -22,7 +22,10 @@ import { ReadingMode, } from '@/modules/reader/types/Reader.types.ts'; import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx'; -import { updateReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts'; +import { + convertFromReaderSettingsWithDefaultFlag, + updateReaderSettings, +} from '@/modules/reader/services/ReaderSettingsMetadata.ts'; import { MangaIdInfo } from '@/modules/manga/Manga.types.ts'; import { getMetadataKey } from '@/modules/metadata/services/MetadataReader.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts'; @@ -64,11 +67,11 @@ export class ReaderService { private static chapterUpdateQueues: Map = new Map(); private static getOrCreateChapterUpdateQueue(id: ChapterIdInfo['id']): Queue { - if (!this.chapterUpdateQueues.has(id)) { - this.chapterUpdateQueues.set(id, new Queue(1)); + if (!ReaderService.chapterUpdateQueues.has(id)) { + ReaderService.chapterUpdateQueues.set(id, new Queue(1)); } - return this.chapterUpdateQueues.get(id)!; + return ReaderService.chapterUpdateQueues.get(id)!; } static useNavigateToChapter(chapter?: TChapterReader, resumeMode?: ReaderResumeMode): () => void { @@ -95,7 +98,7 @@ export class ReaderService { ): void { const key = `${currentChapter.id}_${nextChapter?.id}_${pageIndex}_${downloadAheadLimit}`; - this.downloadAheadQueue.enqueue(key, async () => { + ReaderService.downloadAheadQueue.enqueue(key, async () => { const chapterIdsForDownloadAhead = getChapterIdsForDownloadAhead( currentChapter, nextChapter, @@ -119,7 +122,7 @@ export class ReaderService { static useUpdateChapter(): (patch: UpdateChapterPatchInput) => void { const { manga } = useReaderStateMangaContext(); const { initialChapter, currentChapter, mangaChapters } = useReaderStateChaptersContext(); - const { shouldSkipDupChapters } = this.useSettings(); + const { shouldSkipDupChapters } = ReaderService.useSettings(); const { settings: { deleteChaptersWhileReading, deleteChaptersWithBookmark, updateProgressAfterReading }, } = useMetadataServerSettings(); @@ -193,7 +196,9 @@ export class ReaderService { .response.catch(defaultPromiseErrorHandler('ReaderService::useUpdateChapter')); }; - this.getOrCreateChapterUpdateQueue(currentChapter.id).enqueue(`${currentChapter.id}`, () => update()); + ReaderService.getOrCreateChapterUpdateQueue(currentChapter.id).enqueue(`${currentChapter.id}`, () => + update(), + ); }, [ manga?.id, @@ -212,6 +217,10 @@ export class ReaderService { return useReaderStateSettingsContext().settings; } + static useSettingsWithoutDefaultFlag(): IReaderSettings { + return convertFromReaderSettingsWithDefaultFlag(ReaderService.useSettings()); + } + static useGetThemeDirection(): Direction { const { direction } = useTheme(); const { readingDirection } = ReaderService.useSettings(); @@ -323,11 +332,11 @@ export class ReaderService { manga: MangaIdInfo, profile?: ReadingMode, ): ( - ...args: OmitFirst>> - ) => ReturnType> { + ...args: OmitFirst>> + ) => ReturnType> { return useCallback( (setting, value, commit, isGlobal) => - this.updateSetting(manga, setting, value, commit, isGlobal, profile), + ReaderService.updateSetting(manga, setting, value, commit, isGlobal, profile), [manga, profile], ); } @@ -336,17 +345,17 @@ export class ReaderService { manga: MangaIdInfo, profile?: ReadingMode, ): ( - ...args: OmitFirst>> - ) => ReturnType> { + ...args: OmitFirst>> + ) => ReturnType> { return useCallback( - (setting, isGlobal) => this.deleteSetting(manga, setting, isGlobal, profile?.toString()), + (setting, isGlobal) => ReaderService.deleteSetting(manga, setting, isGlobal, profile?.toString()), [manga, profile], ); } static useOverlayMode(): { mode: ReaderOverlayMode; isDesktop: boolean; isMobile: boolean } { const isTouchDevice = MediaQuery.useIsTouchDevice(); - const { overlayMode } = this.useSettings(); + const { overlayMode } = ReaderService.useSettings(); const isAutoModeSelected = overlayMode === ReaderOverlayMode.AUTO; const isDesktopModeSelected = overlayMode === ReaderOverlayMode.DESKTOP; @@ -364,7 +373,7 @@ export class ReaderService { static useExit(): () => void { const { manga } = useReaderStateMangaContext(); - const { exitMode } = this.useSettings(); + const { exitMode } = ReaderService.useSettings(); const handleBack = useBackButton(); const navigate = useNavigate();