Use HOC for reader context usage
This commit is contained in:
@@ -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<TReaderScrollbarContext, 'scrollbarXSize'> &
|
||||
Pick<ReturnType<typeof ReaderService.useOverlayMode>, 'isDesktop'> &
|
||||
Pick<NavbarContextType, 'readerNavBarWidth'> &
|
||||
Pick<TReaderProgressBarContext, 'isMaximized'> &
|
||||
Pick<ReaderStatePages, 'currentPageIndex' | 'pages' | 'totalPages'> &
|
||||
Pick<IReaderSettings, 'progressBarType' | 'shouldShowPageNumber' | 'readingDirection'>) => {
|
||||
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 = () => {
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderPageNumber = withPropsFrom(
|
||||
BaseReaderPageNumber,
|
||||
[
|
||||
ReaderService.useOverlayMode,
|
||||
useReaderScrollbarContext,
|
||||
useNavBarContext,
|
||||
useReaderProgressBarContext,
|
||||
userReaderStatePagesContext,
|
||||
ReaderService.useSettingsWithoutDefaultFlag,
|
||||
],
|
||||
[
|
||||
'isDesktop',
|
||||
'scrollbarXSize',
|
||||
'readerNavBarWidth',
|
||||
'isMaximized',
|
||||
'currentPageIndex',
|
||||
'pages',
|
||||
'totalPages',
|
||||
'progressBarType',
|
||||
'shouldShowPageNumber',
|
||||
'readingDirection',
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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<NavbarContextType, 'readerNavBarWidth'> & Pick<IReaderSettings, 'customFilter'>) => {
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
@@ -39,3 +40,9 @@ export const ReaderRGBAFilter = () => {
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderRGBAFilter = withPropsFrom(
|
||||
BaseReaderRGBAFilter,
|
||||
[useNavBarContext, ReaderService.useSettingsWithoutDefaultFlag],
|
||||
['readerNavBarWidth', 'customFilter'],
|
||||
);
|
||||
|
||||
@@ -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<NavbarContextType, 'readerNavBarWidth'> &
|
||||
Pick<TReaderTapZoneContext, 'showPreview'> &
|
||||
Pick<IReaderSettings, 'tapZoneLayout' | 'tapZoneInvertMode' | 'readingDirection'>) => {
|
||||
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 = () => {
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const TapZoneLayout = withPropsFrom(
|
||||
BaseTapZoneLayout,
|
||||
[useNavBarContext, useReaderTapZoneContext, ReaderService.useSettingsWithoutDefaultFlag],
|
||||
['readerNavBarWidth', 'showPreview', 'tapZoneLayout', 'tapZoneInvertMode', 'readingDirection'],
|
||||
);
|
||||
|
||||
@@ -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<ReturnType<typeof ReaderService.useOverlayMode>, 'isDesktop' | 'isMobile'>) => {
|
||||
const [areSettingsOpen, setAreSettingsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
@@ -44,3 +49,9 @@ export const ReaderOverlay = ({ isVisible }: BaseReaderOverlayProps & MobileHead
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderOverlay = withPropsFrom(
|
||||
BaseReaderOverlay,
|
||||
[ReaderService.useOverlayMode],
|
||||
['isDesktop', 'isMobile'],
|
||||
);
|
||||
|
||||
@@ -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<TReaderStateMangaContext, 'manga'> &
|
||||
Pick<ReaderStateChapters, 'currentChapter'> &
|
||||
Pick<TReaderScrollbarContext, 'scrollbarYSize'>) => {
|
||||
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) => {
|
||||
</Slide>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderOverlayHeaderMobile = withPropsFrom(
|
||||
BaseReaderOverlayHeaderMobile,
|
||||
[useReaderStateMangaContext, useReaderStateChaptersContext, useReaderScrollbarContext],
|
||||
['manga', 'currentChapter', 'scrollbarYSize'],
|
||||
);
|
||||
|
||||
@@ -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<NavbarContextType, 'setReaderNavBarWidth'> &
|
||||
Pick<TReaderStateMangaContext, 'manga'> &
|
||||
Pick<ReaderStateChapters, 'currentChapter' | 'previousChapter' | 'nextChapter' | 'chapters'> &
|
||||
Pick<
|
||||
ReaderStatePages,
|
||||
'pages' | 'currentPageIndex' | 'pageLoadStates' | 'setPageLoadStates' | 'setRetryFailedPagesKeyPrefix'
|
||||
> & {
|
||||
exit: ReturnType<typeof ReaderService.useExit>;
|
||||
}) => {
|
||||
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
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderNavBarDesktop = withPropsFrom(
|
||||
BaseReaderNavBarDesktop,
|
||||
[
|
||||
useNavBarContext,
|
||||
useReaderStateMangaContext,
|
||||
useReaderStateChaptersContext,
|
||||
userReaderStatePagesContext,
|
||||
() => ({ exit: ReaderService.useExit() }),
|
||||
],
|
||||
[
|
||||
'setReaderNavBarWidth',
|
||||
'manga',
|
||||
'chapters',
|
||||
'currentChapter',
|
||||
'previousChapter',
|
||||
'nextChapter',
|
||||
'pages',
|
||||
'currentPageIndex',
|
||||
'pageLoadStates',
|
||||
'setPageLoadStates',
|
||||
'setRetryFailedPagesKeyPrefix',
|
||||
'exit',
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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<ReaderStateChapters, 'chapters' | 'currentChapter' | 'previousChapter' | 'nextChapter'>) => {
|
||||
readerThemeDirection,
|
||||
openChapter,
|
||||
}: Pick<ReaderStateChapters, 'chapters' | 'currentChapter' | 'previousChapter' | 'nextChapter'> & {
|
||||
readerThemeDirection: ReturnType<typeof ReaderService.useGetThemeDirection>;
|
||||
openChapter: ReturnType<typeof ReaderControls.useOpenChapter>;
|
||||
}) => {
|
||||
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 = ({
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderNavBarDesktopChapterNavigation = withPropsFrom(
|
||||
BaseReaderNavBarDesktopChapterNavigation,
|
||||
[
|
||||
() => ({
|
||||
readerThemeDirection: ReaderService.useGetThemeDirection(),
|
||||
}),
|
||||
() => ({
|
||||
openChapter: ReaderControls.useOpenChapter(),
|
||||
}),
|
||||
],
|
||||
['readerThemeDirection', 'openChapter'],
|
||||
);
|
||||
|
||||
@@ -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<ReaderStatePages, 'currentPageIndex' | 'pages'>) => {
|
||||
readingDirection,
|
||||
openPage,
|
||||
}: Pick<ReaderStatePages, 'currentPageIndex' | 'pages'> &
|
||||
Pick<IReaderSettings, 'readingDirection'> & {
|
||||
openPage: ReturnType<typeof ReaderControls.useOpenPage>;
|
||||
}) => {
|
||||
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 (
|
||||
<Stack sx={{ flexDirection: 'row', gap: 1 }} dir="ltr">
|
||||
@@ -73,3 +78,9 @@ export const ReaderNavBarDesktopPageNavigation = ({
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderNavBarDesktopPageNavigation = withPropsFrom(
|
||||
BaseReaderNavBarDesktopPageNavigation,
|
||||
[() => ({ openPage: ReaderControls.useOpenPage() }), ReaderService.useSettingsWithoutDefaultFlag],
|
||||
['readingDirection', 'openPage'],
|
||||
);
|
||||
|
||||
@@ -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<ReaderStateChapters, 'currentChapter' | 'chapters'> &
|
||||
Pick<TReaderScrollbarContext, 'scrollbarXSize' | 'scrollbarYSize'>) => {
|
||||
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'],
|
||||
);
|
||||
|
||||
@@ -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<TReaderStateMangaContext, 'manga'> &
|
||||
Pick<IReaderSettingsWithDefaultFlag, 'readingMode' | 'readingDirection'>) => {
|
||||
const deleteSetting = ReaderService.useCreateDeleteSetting(manga ?? DEFAULT_MANGA);
|
||||
|
||||
if (!manga) {
|
||||
@@ -42,3 +47,9 @@ export const ReaderBottomBarMobileQuickSettings = () => {
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderBottomBarMobileQuickSettings = withPropsFrom(
|
||||
BaseReaderBottomBarMobileQuickSettings,
|
||||
[useReaderStateMangaContext, ReaderService.useSettings],
|
||||
['manga', 'readingMode', 'readingDirection'],
|
||||
);
|
||||
|
||||
@@ -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<TReaderProgressBarContext, 'isDragging' | 'setIsDragging'> & {
|
||||
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<typeof ReaderControls.useOpenPage>;
|
||||
direction: ReturnType<typeof ReaderService.useGetThemeDirection>;
|
||||
}) => {
|
||||
const progressBarRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const isHorizontalPosition = getProgressBarPositionInfo(progressBarPosition).isHorizontal;
|
||||
@@ -212,3 +216,15 @@ export const ReaderProgressBar = ({
|
||||
</ReaderProgressBarContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderProgressBar = withPropsFrom(
|
||||
BaseReaderProgressBar,
|
||||
[
|
||||
useReaderProgressBarContext,
|
||||
() => ({ openPage: ReaderControls.useOpenPage() }),
|
||||
() => ({
|
||||
direction: ReaderService.useGetThemeDirection(),
|
||||
}),
|
||||
],
|
||||
['isDragging', 'setIsDragging', 'openPage', 'direction'],
|
||||
);
|
||||
|
||||
@@ -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<typeof ReaderService.useGetThemeDirection>;
|
||||
}) => {
|
||||
const [appTheme] = useLocalStorage<AppThemes>('appTheme', 'default');
|
||||
const [themeMode] = useLocalStorage<ThemeMode>('themeMode', ThemeMode.SYSTEM);
|
||||
const [pureBlackMode] = useLocalStorage<boolean>('pureBlackMode', false);
|
||||
@@ -40,3 +46,9 @@ export const ReaderProgressBarDirectionWrapper = ({ children }: { children: Reac
|
||||
</CacheProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderProgressBarDirectionWrapper = withPropsFrom(
|
||||
BaseReaderProgressBarDirectionWrapper,
|
||||
[() => ({ direction: ReaderService.useGetThemeDirection() })],
|
||||
['direction'],
|
||||
);
|
||||
|
||||
@@ -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<ReaderStateChapters, 'previousChapter' | 'nextChapter'> &
|
||||
Pick<TReaderOverlayContext, 'isVisible'> &
|
||||
Pick<TReaderProgressBarContext, 'setIsMaximized'>) => {
|
||||
const pagesState = userReaderStatePagesContext();
|
||||
const { currentPageIndex, pages } = pagesState;
|
||||
|
||||
@@ -177,3 +184,9 @@ export const MobileReaderProgressBar = () => {
|
||||
</ReaderProgressBarDirectionWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const MobileReaderProgressBar = withPropsFrom(
|
||||
BaseMobileReaderProgressBar,
|
||||
[useReaderStateChaptersContext, useReaderOverlayContext, useReaderProgressBarContext],
|
||||
['previousChapter', 'nextChapter', 'isVisible', 'setIsMaximized'],
|
||||
);
|
||||
|
||||
@@ -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<NavbarContextType, 'readerNavBarWidth'> &
|
||||
Pick<TReaderProgressBarContext, 'isMaximized' | 'setIsMaximized' | 'isDragging'> &
|
||||
Pick<IReaderSettings, 'progressBarType' | 'progressBarSize' | 'progressBarPosition'> &
|
||||
Pick<TReaderScrollbarContext, 'scrollbarXSize' | 'scrollbarYSize'> & {
|
||||
readerDirection: ReturnType<typeof ReaderService.useGetThemeDirection>;
|
||||
}) => {
|
||||
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 = () => {
|
||||
</ReaderProgressBarDirectionWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const StandardReaderProgressBar = withPropsFrom(
|
||||
BaseStandardReaderProgressBar,
|
||||
[
|
||||
useNavBarContext,
|
||||
useReaderProgressBarContext,
|
||||
ReaderService.useSettingsWithoutDefaultFlag,
|
||||
() => ({ readerDirection: ReaderService.useGetThemeDirection() }),
|
||||
useReaderScrollbarContext,
|
||||
],
|
||||
[
|
||||
'readerNavBarWidth',
|
||||
'isMaximized',
|
||||
'setIsMaximized',
|
||||
'isDragging',
|
||||
'progressBarType',
|
||||
'progressBarSize',
|
||||
'progressBarPosition',
|
||||
'readerDirection',
|
||||
'scrollbarXSize',
|
||||
'scrollbarYSize',
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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<Parameters<typeof ReaderService.updateSetting>>) => void;
|
||||
areDefaultSettings?: boolean;
|
||||
deleteSetting: (setting: keyof IReaderSettings) => void;
|
||||
}) => {
|
||||
setShowPreview,
|
||||
mode: overlayMode,
|
||||
}: Pick<TReaderTapZoneContext, 'setShowPreview'> &
|
||||
Pick<ReturnType<typeof ReaderService.useOverlayMode>, 'mode'> & {
|
||||
activeTab: number;
|
||||
setActiveTab: (tab: number) => void;
|
||||
settings: IReaderSettingsWithDefaultFlag;
|
||||
updateSetting: (...args: OmitFirst<Parameters<typeof ReaderService.updateSetting>>) => void;
|
||||
areDefaultSettings?: boolean;
|
||||
deleteSetting: (setting: keyof IReaderSettings) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { setShowPreview } = useReaderTapZoneContext();
|
||||
const isTouchDevice = MediaQuery.useIsTouchDevice();
|
||||
|
||||
const { mode: overlayMode } = ReaderService.useOverlayMode();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TabsMenu
|
||||
@@ -177,3 +179,9 @@ export const ReaderSettingsTabs = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderSettingsTabs = withPropsFrom(
|
||||
BaseReaderSettingsTabs,
|
||||
[useReaderTapZoneContext, ReaderService.useOverlayMode],
|
||||
['setShowPreview', 'mode'],
|
||||
);
|
||||
|
||||
@@ -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<ComponentProps<typeof SpinnerImage>, 'ref' | 'spinnerStyle' | 'imgStyle'> & {
|
||||
display: boolean;
|
||||
doublePage?: boolean;
|
||||
position?: 'left' | 'right';
|
||||
marginTop?: number;
|
||||
},
|
||||
}: Omit<ComponentProps<typeof SpinnerImage>, 'ref' | 'spinnerStyle' | 'imgStyle'> &
|
||||
Pick<
|
||||
IReaderSettings,
|
||||
'readingMode' | 'customFilter' | 'pageScaleMode' | 'shouldStretchPage' | 'readerWidth'
|
||||
> &
|
||||
Pick<TReaderScrollbarContext, 'scrollbarXSize' | 'scrollbarYSize'> & {
|
||||
display: boolean;
|
||||
doublePage?: boolean;
|
||||
position?: 'left' | 'right';
|
||||
marginTop?: number;
|
||||
},
|
||||
ref: ForwardedRef<HTMLImageElement | null>,
|
||||
) => {
|
||||
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',
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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<IReaderSettings, 'readingMode' | 'pageScaleMode'> & {
|
||||
type: Exclude<ReaderTransitionPageMode, ReaderTransitionPageMode.NONE | ReaderTransitionPageMode.BOTH>;
|
||||
mode: ReaderTransitionPageMode;
|
||||
}) => {
|
||||
manga,
|
||||
currentChapter,
|
||||
previousChapter,
|
||||
nextChapter,
|
||||
scrollbarXSize,
|
||||
scrollbarYSize,
|
||||
readerNavBarWidth,
|
||||
}: Pick<IReaderSettings, 'readingMode' | 'pageScaleMode'> &
|
||||
Pick<TReaderStateMangaContext, 'manga'> &
|
||||
Pick<ReaderStateChapters, 'currentChapter' | 'previousChapter' | 'nextChapter'> &
|
||||
Pick<TReaderScrollbarContext, 'scrollbarXSize' | 'scrollbarYSize'> &
|
||||
Pick<NavbarContextType, 'readerNavBarWidth'> & {
|
||||
type: Exclude<ReaderTransitionPageMode, ReaderTransitionPageMode.NONE | ReaderTransitionPageMode.BOTH>;
|
||||
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 = ({
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderTransitionPage = withPropsFrom(
|
||||
BaseReaderTransitionPage,
|
||||
[useReaderStateChaptersContext, useReaderScrollbarContext, useNavBarContext],
|
||||
[
|
||||
'manga',
|
||||
'currentChapter',
|
||||
'previousChapter',
|
||||
'nextChapter',
|
||||
'scrollbarXSize',
|
||||
'scrollbarYSize',
|
||||
'readerNavBarWidth',
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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, PageInViewportType> = {
|
||||
[ReadingMode.SINGLE_PAGE]: PageInViewportType.X,
|
||||
@@ -52,251 +61,308 @@ const READING_MODE_TO_IN_VIEWPORT_TYPE: Record<ReadingMode, PageInViewportType>
|
||||
[ReadingMode.WEBTOON]: PageInViewportType.Y,
|
||||
};
|
||||
|
||||
export const ReaderViewer = forwardRef((_, ref: ForwardedRef<HTMLDivElement | null>) => {
|
||||
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<IReaderSettings, 'readingMode' | 'shouldOffsetDoubleSpreads' | 'readingDirection'> &
|
||||
Pick<TReaderScrollbarContext, 'setScrollbarXSize' | 'setScrollbarYSize'> &
|
||||
Pick<TReaderOverlayContext, 'isVisible' | 'setIsVisible'> & {
|
||||
updateCurrentPageIndex: ReturnType<typeof ReaderControls.useUpdateCurrentPageIndex>;
|
||||
},
|
||||
|
||||
const scrollElementRef = useRef<HTMLDivElement | null>(null);
|
||||
useImperativeHandle(ref, () => scrollElementRef.current!);
|
||||
ref: ForwardedRef<HTMLDivElement | null>,
|
||||
) => {
|
||||
const { direction: themeDirection } = useTheme();
|
||||
|
||||
const isContinuousReadingModeActive = isContinuousReadingMode(readingMode.value);
|
||||
const isDragging = useMouseDragScroll(isContinuousReadingModeActive ? scrollElementRef : undefined);
|
||||
const scrollElementRef = useRef<HTMLDivElement | null>(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<NodeJS.Timeout>();
|
||||
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<NodeJS.Timeout>();
|
||||
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 (
|
||||
<Stack
|
||||
ref={scrollElementRef}
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
...applyStyles(isContinuousVerticalReadingMode(readingMode.value), { alignItems: 'center' }),
|
||||
}}
|
||||
onClick={(e) => !isDragging && handleClick(e)}
|
||||
onScroll={() =>
|
||||
ReaderControls.updateCurrentPageOnScroll(
|
||||
imageRefs,
|
||||
totalPages - 1,
|
||||
updateCurrentPageIndex,
|
||||
inViewportType,
|
||||
readingDirection.value,
|
||||
)
|
||||
if (!scrollElementRef.current) {
|
||||
return () => {};
|
||||
}
|
||||
>
|
||||
<Pager
|
||||
totalPages={totalPages}
|
||||
currentPageIndex={currentPageIndex}
|
||||
pages={actualPages}
|
||||
transitionPageMode={transitionPageMode}
|
||||
pageLoadStates={pageLoadStates}
|
||||
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
|
||||
imageRefs={imageRefs}
|
||||
onLoad={(pagesIndex, isPrimary = true) => {
|
||||
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 (
|
||||
<Stack
|
||||
ref={scrollElementRef}
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
...applyStyles(isContinuousVerticalReadingMode(readingMode), { alignItems: 'center' }),
|
||||
}}
|
||||
onError={(pageIndex) => {
|
||||
setPageLoadStates((statePageLoadStates) =>
|
||||
statePageLoadStates.toSpliced(pageIndex, 1, { loaded: false, error: true }),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
onClick={(e) => !isDragging && handleClick(e)}
|
||||
onScroll={() =>
|
||||
ReaderControls.updateCurrentPageOnScroll(
|
||||
imageRefs,
|
||||
totalPages - 1,
|
||||
updateCurrentPageIndex,
|
||||
inViewportType,
|
||||
readingDirection,
|
||||
)
|
||||
}
|
||||
>
|
||||
<Pager
|
||||
totalPages={totalPages}
|
||||
currentPageIndex={currentPageIndex}
|
||||
pages={actualPages}
|
||||
transitionPageMode={transitionPageMode}
|
||||
pageLoadStates={pageLoadStates}
|
||||
retryFailedPagesKeyPrefix={retryFailedPagesKeyPrefix}
|
||||
imageRefs={imageRefs}
|
||||
onLoad={(pagesIndex, isPrimary = true) => {
|
||||
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 }),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
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',
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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<ReaderPagerProps, 'pageLoadStates' | 'retryFailedPagesKeyPrefix'> & {
|
||||
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<ReaderPagerProps, 'pageLoadStates' | 'retryFailedPagesKeyPrefix'> &
|
||||
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),
|
||||
]}
|
||||
>
|
||||
<ReaderTransitionPage
|
||||
type={ReaderTransitionPageMode.PREVIOUS}
|
||||
mode={transitionPageMode}
|
||||
readingMode={readingMode.value}
|
||||
pageScaleMode={pageScaleMode.value}
|
||||
readingMode={readingMode}
|
||||
pageScaleMode={pageScaleMode}
|
||||
/>
|
||||
{pages.map((page, pagesIndex) =>
|
||||
createPage(
|
||||
@@ -78,9 +79,15 @@ export const BasePager = ({
|
||||
<ReaderTransitionPage
|
||||
type={ReaderTransitionPageMode.NEXT}
|
||||
mode={transitionPageMode}
|
||||
readingMode={readingMode.value}
|
||||
pageScaleMode={pageScaleMode.value}
|
||||
readingMode={readingMode}
|
||||
pageScaleMode={pageScaleMode}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const BasePager = withPropsFrom(
|
||||
BaseBasePager,
|
||||
[ReaderService.useSettingsWithoutDefaultFlag],
|
||||
['readingMode', 'pageScaleMode', 'shouldStretchPage', 'readerWidth', 'imagePreLoadAmount'],
|
||||
);
|
||||
|
||||
@@ -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<IReaderSettings, 'readingDirection'>) => {
|
||||
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 (
|
||||
<BasePager
|
||||
@@ -75,9 +76,7 @@ export const ReaderDoublePagedPager = ({
|
||||
currentPage.primary.index,
|
||||
totalPages,
|
||||
pageLoadStates[primary.index].error ? retryFailedPagesKeyPrefix : undefined,
|
||||
hasSecondaryPage
|
||||
? getPagePosition('first', themeDirection, readingDirection.value)
|
||||
: undefined,
|
||||
hasSecondaryPage ? getPagePosition('first', themeDirection, readingDirection) : undefined,
|
||||
hasSecondaryPage,
|
||||
)}
|
||||
{hasSecondaryPage &&
|
||||
@@ -90,7 +89,7 @@ export const ReaderDoublePagedPager = ({
|
||||
currentSecondaryPageIndex,
|
||||
totalPages,
|
||||
pageLoadStates[secondary.index].error ? retryFailedPagesKeyPrefix : undefined,
|
||||
getPagePosition('second', themeDirection, readingDirection.value),
|
||||
getPagePosition('second', themeDirection, readingDirection),
|
||||
true,
|
||||
)}
|
||||
</Fragment>
|
||||
@@ -115,3 +114,9 @@ export const ReaderDoublePagedPager = ({
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderDoublePagedPager = withPropsFrom(
|
||||
BaseReaderDoublePagedPager,
|
||||
[ReaderService.useSettingsWithoutDefaultFlag],
|
||||
['readingDirection'],
|
||||
);
|
||||
|
||||
@@ -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<IReaderSettings, 'pageGap' | 'readingDirection'>) => {
|
||||
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 (
|
||||
<BasePager
|
||||
@@ -53,7 +55,7 @@ export const ReaderHorizontalPager = ({
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
alignItems: 'center',
|
||||
gap: `${pageGap.value}px`,
|
||||
gap: `${pageGap}px`,
|
||||
...applyStyles(themeDirection === 'ltr', {
|
||||
flexDirection: isLtrReadingDirection ? 'row' : 'row-reverse',
|
||||
justifyContent: isLtrReadingDirection ? 'flex-start' : 'flex-end',
|
||||
@@ -68,3 +70,9 @@ export const ReaderHorizontalPager = ({
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderHorizontalPager = withPropsFrom(
|
||||
BaseReaderHorizontalPager,
|
||||
[ReaderService.useSettingsWithoutDefaultFlag],
|
||||
['pageGap', 'readingDirection'],
|
||||
);
|
||||
|
||||
@@ -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<IReaderSettings, 'pageGap' | 'readingMode'>) => {
|
||||
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 (
|
||||
<BasePager
|
||||
@@ -46,3 +48,9 @@ export const ReaderVerticalPager = ({
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ReaderVerticalPager = withPropsFrom(
|
||||
BaseReaderVerticalPager,
|
||||
[ReaderService.useSettingsWithoutDefaultFlag],
|
||||
['pageGap', 'readingMode'],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user