From 8e59763bba508ba82091234ac8a7f3d3e4568f31 Mon Sep 17 00:00:00 2001
From: schroda <50052685+schroda@users.noreply.github.com>
Date: Sat, 26 Oct 2024 16:07:14 +0200
Subject: [PATCH] Delete deprecated reader files
---
src/App.tsx | 9 +-
src/modules/manga/Manga.types.ts | 2 +-
src/modules/metadata/Metadata.constants.ts | 1 -
src/modules/reader/Reader.constants.ts | 1 -
src/modules/reader/Reader.types.ts | 33 -
.../reader/components/ReaderNavBar.tsx | 398 ------------
.../components/ReaderSettingsOptions.tsx | 147 -----
.../reader/components/page/DoublePage.tsx | 87 ---
src/modules/reader/components/page/Page.tsx | 113 ----
.../reader/components/page/PageNumber.tsx | 39 --
.../components/pager/DoublePagedPager.tsx | 219 -------
.../components/pager/HorizontalPager.tsx | 208 -------
.../reader/components/pager/PagedPager.tsx | 123 ----
.../reader/components/pager/VerticalPager.tsx | 221 -------
.../reader/screens/DefaultReaderSettings.tsx | 77 ---
src/modules/reader/screens/Reader.tsx | 576 ------------------
16 files changed, 3 insertions(+), 2251 deletions(-)
delete mode 100644 src/modules/reader/components/ReaderNavBar.tsx
delete mode 100644 src/modules/reader/components/ReaderSettingsOptions.tsx
delete mode 100644 src/modules/reader/components/page/DoublePage.tsx
delete mode 100644 src/modules/reader/components/page/Page.tsx
delete mode 100644 src/modules/reader/components/page/PageNumber.tsx
delete mode 100644 src/modules/reader/components/pager/DoublePagedPager.tsx
delete mode 100644 src/modules/reader/components/pager/HorizontalPager.tsx
delete mode 100644 src/modules/reader/components/pager/PagedPager.tsx
delete mode 100644 src/modules/reader/components/pager/VerticalPager.tsx
delete mode 100644 src/modules/reader/screens/DefaultReaderSettings.tsx
delete mode 100644 src/modules/reader/screens/Reader.tsx
diff --git a/src/App.tsx b/src/App.tsx
index 7621c3b8..1650d2f9 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -26,7 +26,6 @@ const { Browse } = loadable(() => import('@/modules/browse/screens/Browse.tsx'),
const { DownloadQueue } = loadable(() => import('@/modules/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
const { Library } = loadable(() => import('@/modules/library/screens/Library.tsx'), lazyLoadFallback);
const { Manga } = loadable(() => import('@/modules/manga/screens/Manga.tsx'), lazyLoadFallback);
-const { Reader } = loadable(() => import('@/modules/reader/screens/Reader.tsx'), lazyLoadFallback);
const { SearchAll } = loadable(() => import('@/modules/global-search/screens/SearchAll.tsx'), lazyLoadFallback);
const { Settings } = loadable(() => import('@/modules/settings/screens/Settings.tsx'), lazyLoadFallback);
const { About } = loadable(() => import('@/modules/settings/screens/About.tsx'), lazyLoadFallback);
@@ -35,10 +34,6 @@ const { CategorySettings } = loadable(
() => import('@/modules/category/screens/CategorySettings.tsx'),
lazyLoadFallback,
);
-const { DefaultReaderSettings } = loadable(
- () => import('@/modules/reader/screens/DefaultReaderSettings.tsx'),
- lazyLoadFallback,
-);
const { SourceConfigure } = loadable(() => import('@/modules/source/screens/SourceConfigure.tsx'), lazyLoadFallback);
const { SourceMangas } = loadable(() => import('@/modules/source/screens/SourceMangas.tsx'), lazyLoadFallback);
const { Updates } = loadable(() => import('@/modules/updates/screens/Updates.tsx'), lazyLoadFallback);
@@ -119,7 +114,7 @@ const MainApp = () => {
} />
} />
} />
- } />
+
} />
} />
@@ -164,7 +159,7 @@ const MainApp = () => {
const ReaderApp = () => (
- } />
+
diff --git a/src/modules/manga/Manga.types.ts b/src/modules/manga/Manga.types.ts
index fd1f3e1a..5f4414d7 100644
--- a/src/modules/manga/Manga.types.ts
+++ b/src/modules/manga/Manga.types.ts
@@ -12,7 +12,7 @@ import { SelectableCollectionReturnType } from '@/modules/collection/hooks/useSe
import { useManageMangaLibraryState } from '@/modules/manga/hooks/useManageMangaLibraryState.tsx';
import { ChapterType, MangaReaderFieldsFragment, MangaType, TrackRecordType } from '@/lib/graphql/generated/graphql.ts';
import { SingleModeProps } from '@/modules/manga/components/MangaActionMenuItems.tsx';
-import { IReaderSettings } from '@/modules/reader/Reader.types';
+import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
import { GridLayout } from '@/modules/core/Core.types.ts';
export type MangaCardMode = 'default' | 'source' | 'migrate.search' | 'migrate.select' | 'duplicate';
diff --git a/src/modules/metadata/Metadata.constants.ts b/src/modules/metadata/Metadata.constants.ts
index 94b49499..c56c0da0 100644
--- a/src/modules/metadata/Metadata.constants.ts
+++ b/src/modules/metadata/Metadata.constants.ts
@@ -17,7 +17,6 @@ const APP_METADATA_OBJECT: Record = {
skipDupChapters: undefined,
fitPageToWindow: undefined,
scalePage: undefined,
- readerType: undefined,
offsetFirstPage: undefined,
migration: undefined,
deleteChaptersManuallyMarkedRead: undefined,
diff --git a/src/modules/reader/Reader.constants.ts b/src/modules/reader/Reader.constants.ts
index 3f3b400b..77b197cf 100644
--- a/src/modules/reader/Reader.constants.ts
+++ b/src/modules/reader/Reader.constants.ts
@@ -15,7 +15,6 @@ export const DEFAULT_READER_SETTINGS: IReaderSettings = {
skipDupChapters: true,
fitPageToWindow: true,
scalePage: false,
- readerType: 'ContinuesVertical',
offsetFirstPage: false,
readerWidth: 50,
};
diff --git a/src/modules/reader/Reader.types.ts b/src/modules/reader/Reader.types.ts
index 2cb99876..14967bac 100644
--- a/src/modules/reader/Reader.types.ts
+++ b/src/modules/reader/Reader.types.ts
@@ -6,20 +6,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-import { GetChaptersReaderQuery, MangaReaderFieldsFragment } from '@/lib/graphql/generated/graphql.ts';
-
-export type ReaderType =
- | 'ContinuesVertical'
- | 'Webtoon'
- | 'SingleVertical'
- | 'SingleRTL'
- | 'SingleLTR'
- | 'DoubleVertical'
- | 'DoubleRTL'
- | 'DoubleLTR'
- | 'ContinuesHorizontalLTR'
- | 'ContinuesHorizontalRTL';
-
export interface IReaderSettings {
staticNav: boolean;
showPageNumber: boolean;
@@ -27,25 +13,6 @@ export interface IReaderSettings {
skipDupChapters: boolean;
fitPageToWindow: boolean;
scalePage: boolean;
- readerType: ReaderType;
offsetFirstPage: boolean;
readerWidth: number;
}
-
-export interface IReaderPage {
- index: number;
- src: string;
-}
-
-export interface IReaderProps {
- pages: Array;
- pageCount: number;
- setCurPage: React.Dispatch>;
- curPage: number;
- initialPage: number;
- settings: IReaderSettings;
- manga: MangaReaderFieldsFragment;
- chapter: GetChaptersReaderQuery['chapters']['nodes'][number];
- nextChapter: () => void;
- prevChapter: () => void;
-}
diff --git a/src/modules/reader/components/ReaderNavBar.tsx b/src/modules/reader/components/ReaderNavBar.tsx
deleted file mode 100644
index 66e18fde..00000000
--- a/src/modules/reader/components/ReaderNavBar.tsx
+++ /dev/null
@@ -1,398 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import IconButton from '@mui/material/IconButton';
-import CloseIcon from '@mui/icons-material/Close';
-import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
-import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
-import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
-import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
-import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
-import Typography from '@mui/material/Typography';
-import { useLocation, useNavigate } from 'react-router-dom';
-import Slide from '@mui/material/Slide';
-import Fade from '@mui/material/Fade';
-import Zoom from '@mui/material/Zoom';
-import Divider from '@mui/material/Divider';
-import FormControl from '@mui/material/FormControl';
-import MenuItem from '@mui/material/MenuItem';
-import Tooltip from '@mui/material/Tooltip';
-import { styled, useTheme } from '@mui/material/styles';
-import ListItem from '@mui/material/ListItem';
-import ListItemText from '@mui/material/ListItemText';
-import Collapse from '@mui/material/Collapse';
-import { useTranslation } from 'react-i18next';
-import { ReaderSettingsOptions } from '@/modules/reader/components/ReaderSettingsOptions.tsx';
-import { useBackButton } from '@/modules/core/hooks/useBackButton.ts';
-import { Select } from '@/modules/core/components/inputs/Select.tsx';
-import { useGetOptionForDirection } from '@/theme.tsx';
-import { ChapterType } from '@/lib/graphql/generated/graphql.ts';
-import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
-import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
-import { CustomIconButton } from '@/modules/core/components/buttons/CustomIconButton.tsx';
-import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
-import { DirectionOffset } from '@/Base.types.ts';
-import { AllowedMetadataValueTypes } from '@/modules/metadata/Metadata.types.ts';
-import { MangaChapterCountInfo, MangaIdInfo } from '@/modules/manga/Manga.types.ts';
-
-const Root = styled('div')({
- zIndex: 10,
-});
-
-const NavContainer = styled('div')(({ theme }) => ({
- position: 'fixed',
- top: 0,
- left: 0,
- minWidth: '240px',
- maxWidth: '400px',
- height: '100vh',
- overflowY: 'auto',
- backgroundColor: theme.palette.background.default,
-
- '& header': {
- backgroundColor: theme.palette.action.hover,
- display: 'flex',
- alignItems: 'center',
- padding: `${theme.spacing(1)} ${theme.spacing(3)}`,
-
- transition: 'left 2s ease',
- },
-}));
-
-const Navigation = styled('div')(({ theme }) => ({
- margin: `0 ${theme.spacing(2)}`,
-}));
-
-const PageNavigation = styled('div')({
- display: 'flex',
- flexWrap: 'wrap',
- alignContent: 'center',
- alignItems: 'center',
- justifyContent: 'center',
-});
-
-const ChapterNavigation = styled('div')(({ theme }) => ({
- display: 'grid',
- gridTemplateRows: 'auto auto auto',
- gridTemplateColumns: '0fr 1fr 0fr',
- gridTemplateAreas: '"pre current next"',
- gridColumnGap: theme.spacing(0.5),
- margin: `${theme.spacing(1)} 0`,
-
- '& a': {
- textDecoration: 'none',
- color: 'inherit',
- display: 'flex',
- flexWrap: 'wrap',
- alignContent: 'center',
- },
-}));
-
-interface IProps {
- settings: IReaderSettings;
- setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist?: boolean) => void;
- manga: MangaIdInfo & MangaChapterCountInfo;
- chapter: Pick;
- chapters: Pick[];
- curPage: number;
- scrollToPage: (page: number) => void;
- openNextChapter: (offset: DirectionOffset) => void;
- retrievingNextChapter: boolean;
-}
-
-export function ReaderNavBar(props: IProps) {
- const { t } = useTranslation();
- const { setReaderNavBarWidth } = useNavBarContext();
- const theme = useTheme();
- const getOptionForDirection = useGetOptionForDirection();
-
- const navigate = useNavigate();
- const location = useLocation<{
- prevDrawerOpen?: boolean;
- prevSettingsCollapseOpen?: boolean;
- }>();
- const { prevDrawerOpen, prevSettingsCollapseOpen } = location.state ?? {};
-
- const navBarRef = useRef(null);
- useResizeObserver(
- navBarRef,
- useCallback(() => {
- if (navBarRef.current?.offsetWidth === undefined) {
- return;
- }
-
- setReaderNavBarWidth(navBarRef.current.offsetWidth);
- }, [navBarRef.current]),
- );
- useLayoutEffect(() => () => setReaderNavBarWidth(0), [navBarRef]);
-
- const {
- settings,
- setSettingValue,
- manga,
- chapter,
- chapters,
- curPage,
- scrollToPage,
- openNextChapter,
- retrievingNextChapter,
- } = props;
-
- const handleBack = useBackButton();
-
- const hasMultipleScanlators = useMemo(
- () => new Set(chapters.map(({ scanlator }) => scanlator)).size > 1,
- [chapters],
- );
-
- const [drawerOpen, setDrawerOpen] = useState(settings.staticNav || prevDrawerOpen);
- const [updateDrawerOnRender, setUpdateDrawerOnRender] = useState(true);
- const [hideOpenButton, setHideOpenButton] = useState(settings.staticNav || prevDrawerOpen);
- const [prevScrollPos, setPrevScrollPos] = useState(0);
- const [settingsCollapseOpen, setSettingsCollapseOpen] = useState(prevSettingsCollapseOpen ?? true);
-
- const disableChapterNavButtons = retrievingNextChapter;
-
- const updateSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist?: boolean) => {
- // prevent closing the navBar when updating the "staticNav" setting
- setUpdateDrawerOnRender(key !== 'staticNav');
- setSettingValue(key, value, persist);
- };
-
- const updateDrawer = (open: boolean) => {
- setDrawerOpen(open);
- setHideOpenButton(open);
- };
-
- const handleScroll = () => {
- const currentScrollPos = window.pageYOffset;
-
- if (Math.abs(currentScrollPos - prevScrollPos) > 20) {
- setHideOpenButton(currentScrollPos > prevScrollPos);
- setPrevScrollPos(currentScrollPos);
- }
- };
-
- useEffect(() => {
- if (updateDrawerOnRender) {
- updateDrawer(settings.staticNav);
- }
- }, [settings.staticNav]);
-
- useEffect(() => {
- window.addEventListener('scroll', handleScroll);
-
- const rootEl: HTMLDivElement = document.querySelector('#root')!;
- const mainContainer: HTMLDivElement = document.querySelector('#appMainContainer')!;
-
- // main container and root div need to change styles...
- rootEl.style.display = 'flex';
- rootEl.style.flexDirection = 'column';
- mainContainer.style.display = 'none';
-
- return () => {
- rootEl.style.display = 'block';
- mainContainer.style.display = 'block';
- window.removeEventListener('scroll', handleScroll);
- };
- }, [handleScroll]); // handleScroll changes on every render
-
- return (
-
-
-
-
- {!settings.staticNav && (
-
- updateDrawer(false)}
- size="large"
- >
- {getOptionForDirection(, )}
-
-
- )}
-
- {chapter.name}
-
-
-
-
-
-
-
-
-
- setSettingsCollapseOpen(!settingsCollapseOpen)}
- size="large"
- >
- {settingsCollapseOpen && }
- {!settingsCollapseOpen && }
-
-
-
-
-
-
-
-
- {t('reader.page_info.label.currently_on_page')}
-
-
-
- {t('reader.page_info.label.of_max_pages', { maxPages: chapter.pageCount })}
-
-
-
- openNextChapter(DirectionOffset.PREVIOUS)}
- >
- {getOptionForDirection(, )}
-
-
-
-
-
-
- = manga.chapters.totalCount
- }
- onClick={() => openNextChapter(DirectionOffset.NEXT)}
- >
- {getOptionForDirection(, )}
-
-
-
-
-
-
-
-
-
- updateDrawer(true)}
- >
- {getOptionForDirection(, )}
-
-
-
-
-
- );
-}
diff --git a/src/modules/reader/components/ReaderSettingsOptions.tsx b/src/modules/reader/components/ReaderSettingsOptions.tsx
deleted file mode 100644
index 9f81e77b..00000000
--- a/src/modules/reader/components/ReaderSettingsOptions.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import List from '@mui/material/List';
-import ListItem from '@mui/material/ListItem';
-import ListItemText from '@mui/material/ListItemText';
-import Switch from '@mui/material/Switch';
-import MenuItem from '@mui/material/MenuItem';
-import { useTranslation } from 'react-i18next';
-import { NumberSetting } from '@/modules/core/components/settings/NumberSetting.tsx';
-import { isHorizontalReaderType } from '@/modules/reader/components/page/Page.tsx';
-import { Select } from '@/modules/core/components/inputs/Select.tsx';
-import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
-import { AllowedMetadataValueTypes } from '@/modules/metadata/Metadata.types.ts';
-
-interface IProps extends IReaderSettings {
- setSettingValue: (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist?: boolean) => void;
-}
-
-export function ReaderSettingsOptions({
- staticNav,
- loadNextOnEnding,
- readerType,
- showPageNumber,
- skipDupChapters,
- setSettingValue,
- fitPageToWindow,
- scalePage,
- offsetFirstPage,
- readerWidth,
-}: IProps) {
- const { t } = useTranslation();
- const fitPageToWindowEligible = !isHorizontalReaderType(readerType);
- return (
-
-
-
- setSettingValue('staticNav', e.target.checked)}
- />
-
-
-
- setSettingValue('showPageNumber', e.target.checked)}
- />
-
-
-
- setSettingValue('loadNextOnEnding', e.target.checked)}
- />
-
-
-
- setSettingValue('skipDupChapters', e.target.checked)}
- />
-
- {fitPageToWindowEligible && (
-
-
- setSettingValue('fitPageToWindow', e.target.checked)}
- />
-
- )}
- {fitPageToWindowEligible && fitPageToWindow && (
-
-
- setSettingValue('scalePage', e.target.checked)}
- />
-
- )}
- {(readerType === 'DoubleLTR' || readerType === 'DoubleRTL') && (
-
-
- setSettingValue('offsetFirstPage', e.target.checked)}
- />
-
- )}
- {fitPageToWindowEligible && !fitPageToWindow && (
- setSettingValue('readerWidth', width)}
- handleLiveUpdate={(width: number) => setSettingValue('readerWidth', width, false)}
- listItemTextSx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
- />
- )}
-
-
-
-
-
- );
-}
diff --git a/src/modules/reader/components/page/DoublePage.tsx b/src/modules/reader/components/page/DoublePage.tsx
deleted file mode 100644
index f0181ff5..00000000
--- a/src/modules/reader/components/page/DoublePage.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import { CSSProperties, forwardRef } from 'react';
-import Box from '@mui/material/Box';
-import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
-import { imageStyle } from '@/modules/reader/components/page/Page.tsx';
-import { getOptionForDirection } from '@/theme.tsx';
-import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
-
-interface IProps {
- index: number;
- image1src: string;
- image2src: string;
- onImageLoad?: () => void;
- settings: IReaderSettings;
-}
-
-export const DoublePage = forwardRef((props: IProps, ref: any) => {
- const { image1src, image2src, index, onImageLoad, settings } = props;
-
- const baseImgStyle = imageStyle(settings);
- const imgStyle = {
- ...baseImgStyle,
- width: settings.fitPageToWindow ? baseImgStyle.width : `calc(${baseImgStyle.width} * 0.5)`,
- minWidth:
- settings.fitPageToWindow && settings.scalePage
- ? `calc(${baseImgStyle.minWidth} * 0.5)`
- : baseImgStyle.minWidth,
- maxWidth: settings.fitPageToWindow ? `calc(${baseImgStyle.maxWidth} * 0.5)` : baseImgStyle.maxWidth,
- };
-
- const spinnerStyle: CSSProperties = {
- ...imgStyle,
- height: '100vh',
- width: '50%',
- backgroundColor: '#525252',
- };
-
- return (
-
-
-
-
- );
-});
diff --git a/src/modules/reader/components/page/Page.tsx b/src/modules/reader/components/page/Page.tsx
deleted file mode 100644
index ecc2ccf9..00000000
--- a/src/modules/reader/components/page/Page.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import { CSSProperties, forwardRef } from 'react';
-import Box from '@mui/material/Box';
-import { useTheme } from '@mui/material/styles';
-import useMediaQuery from '@mui/material/useMediaQuery';
-import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx';
-import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
-import { IReaderSettings, ReaderType } from '@/modules/reader/Reader.types.ts';
-import { Priority } from '@/lib/Queue.ts';
-
-export const isHorizontalReaderType = (readerType: ReaderType): boolean =>
- ['ContinuesHorizontalLTR', 'ContinuesHorizontalRTL'].includes(readerType);
-
-export function imageStyle(settings: IReaderSettings): CSSProperties {
- const isDoublePageReader = ['DoubleRTL', 'DoubleLTR'].includes(settings.readerType);
- const isVertical = settings.readerType === 'ContinuesVertical';
- const isHorizontal = isHorizontalReaderType(settings.readerType);
-
- const scrollbarHeight = MediaQuery.useGetScrollbarSize('height');
-
- const baseStyling: CSSProperties = {
- margin: 0,
- width: `${settings.readerWidth}%`,
- objectFit: 'contain',
- };
-
- const doublePageStyling: CSSProperties = {};
-
- const continuesVerticalStyling: CSSProperties = {
- marginBottom: '15px',
- };
-
- const continuesHorizontalStyling: CSSProperties = {
- width: undefined,
- minHeight: `calc(100vh - ${scrollbarHeight}px)`,
- maxHeight: `calc(100vh - ${scrollbarHeight}px)`,
- marginLeft: '7px',
- marginRight: '7px',
- };
-
- const fitToPageStyling: CSSProperties = {
- width: undefined,
- height: undefined,
- minWidth: settings.scalePage ? 'calc(100vw - (100vw - 100%))' : undefined,
- maxWidth: 'calc(100vw - (100vw - 100%))',
- minHeight: settings.scalePage ? `calc(100vh - ${scrollbarHeight}px)` : undefined,
- maxHeight: `calc(100vh - ${scrollbarHeight}px)`,
- };
-
- return {
- ...baseStyling,
- ...(isDoublePageReader ? doublePageStyling : undefined),
- ...(isHorizontal ? continuesHorizontalStyling : undefined),
- ...(isVertical ? continuesVerticalStyling : undefined),
- ...(settings.fitPageToWindow && !isHorizontal ? fitToPageStyling : undefined),
- };
-}
-
-interface IProps {
- src: string;
- index: number;
- onImageLoad: () => void;
- settings: IReaderSettings;
- display?: boolean;
- priority?: Priority;
-}
-
-export const Page = forwardRef((props: IProps, ref: any) => {
- const { src, index, onImageLoad, settings, display = true, priority } = props;
-
- const theme = useTheme();
- const isMobileWidth = useMediaQuery(theme.breakpoints.down('md'));
-
- const imgStyle = imageStyle(settings);
-
- return (
-
-
-
- );
-});
diff --git a/src/modules/reader/components/page/PageNumber.tsx b/src/modules/reader/components/page/PageNumber.tsx
deleted file mode 100644
index 9cfaa686..00000000
--- a/src/modules/reader/components/page/PageNumber.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import Box from '@mui/material/Box';
-
-import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
-
-interface IProps {
- settings: IReaderSettings;
- curPage: number;
- pageCount: number;
-}
-
-export function PageNumber(props: IProps) {
- const { settings, curPage, pageCount } = props;
-
- return (
-
- {`${curPage + 1} / ${pageCount}`}
-
- );
-}
diff --git a/src/modules/reader/components/pager/DoublePagedPager.tsx b/src/modules/reader/components/pager/DoublePagedPager.tsx
deleted file mode 100644
index 5da1accd..00000000
--- a/src/modules/reader/components/pager/DoublePagedPager.tsx
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import { MouseEvent, useEffect, useMemo, useRef, useState } from 'react';
-import Box from '@mui/material/Box';
-import { Page } from '@/modules/reader/components/page/Page.tsx';
-import { IReaderProps } from '@/modules/reader/Reader.types.ts';
-import { Priority } from '@/lib/Queue.ts';
-
-const isSpreadPage = (image: HTMLImageElement): boolean => {
- const aspectRatio = image.height / image.width;
- return aspectRatio < 1;
-};
-
-export function DoublePagedPager(props: IReaderProps) {
- const { pages, settings, setCurPage, initialPage, curPage, nextChapter, prevChapter } = props;
-
- const selfRef = useRef(null);
-
- const pagesToDisplayStateRef = useRef([]);
-
- const [pagesToSpreadState, setPagesToSpreadState] = useState(Array(pages.length).fill(false));
- const [pagesLoadState, setPagesLoadState] = useState(Array(pages.length).fill(false));
-
- // each spread page has to be counted as 2 pages and all trailing page numbers have to be increased by the count
- // of leading spread pages
- const pageToActualPageIndex = useMemo(
- () => pagesToSpreadState.map((_, page) => page + pagesToSpreadState.slice(0, page).filter(Boolean).length),
- [pagesToSpreadState],
- );
-
- function nextPage() {
- const setNextPage = (page: number) => setCurPage(page === -1 ? pages.length - 1 : page);
-
- const isLastPageDisplayed = pagesToDisplayStateRef.current[pages.length - 1];
- if (isLastPageDisplayed && settings.loadNextOnEnding) {
- // make sure to set last page as current page so that the chapter gets marked as read before opening the next chapter
- const isLastPage = curPage === pages.length - 1;
- if (!isLastPage) {
- setNextPage(pages.length - 1);
- }
-
- nextChapter();
- return;
- }
-
- const page = pagesToDisplayStateRef.current.findIndex((displayed, index) => !displayed && index > curPage);
- setNextPage(page);
- }
-
- function prevPage() {
- const setPrevPage = (page: number) => setCurPage(Math.max(page, 0));
-
- const isFirstPageDisplayed = pagesToDisplayStateRef.current[0];
- if (isFirstPageDisplayed) {
- // not important, but for consistency make sure that first page gets set as current page in case it was displayed
- const isFirstPage = !curPage;
- if (!isFirstPage) {
- setPrevPage(0);
- }
-
- prevChapter();
- return;
- }
-
- const page = [...pagesToDisplayStateRef.current].slice(0, curPage).findLastIndex((displayed) => !displayed);
- setPrevPage(page);
- }
-
- function goLeft() {
- if (settings.readerType === 'DoubleLTR') {
- prevPage();
- } else {
- nextPage();
- }
- }
-
- function goRight() {
- if (settings.readerType === 'DoubleLTR') {
- nextPage();
- } else {
- prevPage();
- }
- }
-
- function keyboardControl(e: KeyboardEvent) {
- switch (e.key) {
- case 'Space':
- e.preventDefault();
- nextPage();
- break;
- case 'ArrowRight':
- goRight();
- break;
- case 'ArrowLeft':
- goLeft();
- break;
- default:
- break;
- }
- }
-
- function clickControl(e: MouseEvent) {
- if (e.clientX > window.innerWidth / 2) {
- goRight();
- } else {
- goLeft();
- }
- }
-
- useEffect(() => {
- document.addEventListener('keydown', keyboardControl);
-
- return () => {
- document.removeEventListener('keydown', keyboardControl);
- };
- }, [selfRef, curPage, settings.readerType, prevChapter, nextChapter, pagesLoadState, pagesToSpreadState]);
-
- useEffect(() => {
- setCurPage(initialPage);
- }, [initialPage]);
-
- return (
-
-
- {pages.map(({ index, src }) => {
- /*
-
- | = page separator
- + = double page
- _ = double spread
-
- without double spreads:
- without double spread offset: | 0 + 1 | 2 + 3 | 4 + 5 | 6 + 7 | 8 |
- with double spread offset : | 0 | 1 + 2 | 3 + 4 | 5 + 6 | 7 + 8 |
-
- with double spreads
- to handle double spreads:
- each double spread has to count as 2 pages, thus, each page number after a double spread has to increase
- by the number of leading double spreads
-
- without double spread offset: | 0 + 1 | 2 | _3/4_ | 5 + 6 | 7 + 8 | _9/10_ | 11 + 12 | 13 + 14 |
- with double spread offset : | 0 | 1 + 2 | _3/4_ | 5 + 6 | 7 + 8 | _9/10_ | 11 + 12 | 13 + 14 |
-
- thus, to get the second page:
- without offset: second page = current page number even ? +1 : -1
- with offset : second page = current page number even ? +1 : -1
-
- the second page has to be ignored in case:
- - double spreads are offset, and the current page is the first page
- - either the current or second page is a double spread
- */
-
- const normalizedCurPage = pageToActualPageIndex[curPage];
- const isCurPageEven = !(normalizedCurPage % 2);
-
- const secondPageOffset = (() => {
- const invert = settings.offsetFirstPage ? -1 : 1;
- const offset = isCurPageEven ? 1 : -1;
-
- return offset * invert;
- })();
-
- const secondPage = curPage + secondPageOffset;
-
- const isFirstPage = curPage === 0;
- const isCurPage = index === curPage;
- const isSecondPage = index === secondPage;
-
- const isCurrentPageSpreadPage = pagesToSpreadState[curPage];
- const isSecondPageSpreadPage = pagesToSpreadState[secondPage];
- const hasSpreadPage = isCurrentPageSpreadPage || isSecondPageSpreadPage;
-
- const ignoreSecondPageDueToOffset = isFirstPage && settings.offsetFirstPage;
-
- const displaySecondPage = isSecondPage && !hasSpreadPage && !ignoreSecondPageDueToOffset;
- const displayPage = isCurPage || displaySecondPage;
-
- pagesToDisplayStateRef.current[index] = displayPage;
-
- return (
- {
- const img = new Image();
- img.onload = () => {
- setPagesLoadState((prevState) => prevState.toSpliced(index, 1, true));
- setPagesToSpreadState((prevState) =>
- prevState.toSpliced(index, 1, isSpreadPage(img)),
- );
- };
- img.src = src;
- }}
- settings={settings}
- display={displayPage}
- priority={displayPage ? Priority.HIGH : undefined}
- />
- );
- })}
-
-
- );
-}
diff --git a/src/modules/reader/components/pager/HorizontalPager.tsx b/src/modules/reader/components/pager/HorizontalPager.tsx
deleted file mode 100644
index 0b82829f..00000000
--- a/src/modules/reader/components/pager/HorizontalPager.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import { MouseEvent as ReactMouseEvent, WheelEvent as ReactWheelEvent, useCallback, useEffect, useRef } from 'react';
-import Box from '@mui/material/Box';
-import { Page } from '@/modules/reader/components/page/Page.tsx';
-import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
-import { IReaderProps } from '@/modules/reader/Reader.types.ts';
-import { Priority } from '@/lib/Queue.ts';
-
-const findCurrentPageIndex = (wrapper: HTMLDivElement): number => {
- for (let i = 0; i < wrapper.children.length; i++) {
- const child = wrapper.children.item(i);
- if (child) {
- const { left, right } = child.getBoundingClientRect();
- if (left <= window.innerWidth / 2 && right > window.innerWidth / 2) return i;
- }
- }
- return -1;
-};
-
-const SCROLL_SAFE_ZONE = 5; // px
-const isAtEnd = () => {
- const visibleEnd = window.innerWidth + window.scrollX;
- // SCROLL_SAFE_ZONE is here for special cases when window might be .5px shorter
- // and math just dont add up correctly
- return visibleEnd >= document.body.scrollWidth - SCROLL_SAFE_ZONE;
-};
-const isAtStart = () => window.scrollX <= 0;
-
-export function HorizontalPager(props: IReaderProps) {
- const { pages, curPage, initialPage, settings, setCurPage, prevChapter, nextChapter } = props;
-
- const currentPageRef = useRef(initialPage);
- const selfRef = useRef(null);
- const pagesRef = useRef([]);
-
- function nextPage() {
- if (curPage < pages.length - 1) {
- pagesRef.current[curPage + 1]?.scrollIntoView({ inline: 'center' });
- setCurPage((page) => page + 1);
- } else if (settings.loadNextOnEnding) {
- nextChapter();
- }
- }
-
- function prevPage() {
- if (curPage > 0) {
- pagesRef.current[curPage - 1]?.scrollIntoView({ inline: 'center' });
- setCurPage(curPage - 1);
- } else if (curPage === 0) {
- prevChapter();
- }
- }
-
- function goLeft() {
- if (settings.readerType === 'ContinuesHorizontalLTR') {
- prevPage();
- } else {
- nextPage();
- }
- }
-
- function goRight() {
- if (settings.readerType === 'ContinuesHorizontalLTR') {
- nextPage();
- } else {
- prevPage();
- }
- }
-
- const mouseXPos = useRef(0);
-
- function dragScreen(e: MouseEvent) {
- window.scrollBy(mouseXPos.current - e.pageX, 0);
- }
-
- function dragControl(e: MouseEvent) {
- mouseXPos.current = e.pageX;
- selfRef.current?.addEventListener('mousemove', dragScreen);
- }
-
- function removeDragControl() {
- selfRef.current?.removeEventListener('mousemove', dragScreen);
- }
-
- function clickControl(e: ReactMouseEvent) {
- if (e.clientX >= window.innerWidth * 0.85) {
- goRight();
- } else if (e.clientX <= window.innerWidth * 0.15) {
- goLeft();
- }
- }
-
- function horizontalScroll(e: ReactWheelEvent) {
- window.scrollBy({
- left: settings.readerType === 'ContinuesHorizontalLTR' ? e.deltaY : e.deltaY * -1,
- });
- }
-
- const handleLoadNextonEnding = () => {
- if (settings.readerType === 'ContinuesHorizontalLTR') {
- if (window.scrollX + window.innerWidth >= document.body.scrollWidth) {
- nextChapter();
- }
- } else if (settings.readerType === 'ContinuesHorizontalRTL') {
- if (window.scrollX <= window.innerWidth) {
- nextChapter();
- }
- }
- };
-
- useResizeObserver(
- pagesRef.current[initialPage],
- useCallback(
- (_, resizeObserver) => {
- const initialPageElement = pagesRef.current[initialPage];
- if (!initialPageElement?.offsetHeight) {
- return;
- }
-
- initialPageElement.scrollIntoView({ inline: 'center' });
- resizeObserver.disconnect();
- },
- [pagesRef.current[initialPage], initialPage],
- ),
- );
-
- useEffect(() => {
- selfRef.current?.addEventListener('mousedown', dragControl);
- selfRef.current?.addEventListener('mouseup', removeDragControl);
-
- return () => {
- selfRef.current?.removeEventListener('mousedown', dragControl);
- selfRef.current?.removeEventListener('mouseup', removeDragControl);
- };
- }, [selfRef]);
-
- useEffect(() => {
- if (settings.loadNextOnEnding) {
- document.addEventListener('scroll', handleLoadNextonEnding);
- }
-
- return () => {
- document.removeEventListener('scroll', handleLoadNextonEnding);
- };
- }, [selfRef, curPage, prevChapter, nextChapter]);
-
- useEffect(() => {
- const handleScroll = () => {
- if (!selfRef.current) return;
-
- // Update current page in parent
- const currentPage = findCurrentPageIndex(selfRef.current);
- if (currentPage !== currentPageRef.current) {
- currentPageRef.current = currentPage;
- setCurPage(currentPage);
- }
-
- // Special case if scroll is moved all the way to the edge
- // This handles cases when last page is show, but is smaller then
- // window, in which case it would never get marked as read.
- // See https://github.com/Suwayomi/Suwayomi-WebUI/issues/14 for more info
- if (settings.readerType === 'ContinuesHorizontalLTR' ? isAtEnd() : isAtStart()) {
- currentPageRef.current = pages.length - 1;
- setCurPage(currentPageRef.current);
- }
- };
- window.addEventListener('scroll', handleScroll);
- return () => window.removeEventListener('scroll', handleScroll);
- }, [settings.readerType]);
-
- return (
-
- {pages.map((page) => (
- {}}
- settings={settings}
- ref={(e: HTMLDivElement) => {
- pagesRef.current[page.index] = e;
- }}
- priority={page.index === curPage ? Priority.HIGH : undefined}
- />
- ))}
-
- );
-}
diff --git a/src/modules/reader/components/pager/PagedPager.tsx b/src/modules/reader/components/pager/PagedPager.tsx
deleted file mode 100644
index a2418cc0..00000000
--- a/src/modules/reader/components/pager/PagedPager.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import { MouseEvent, useEffect, useRef } from 'react';
-import Box from '@mui/material/Box';
-import { Page } from '@/modules/reader/components/page/Page.tsx';
-import { IReaderProps } from '@/modules/reader/Reader.types.ts';
-import { Priority } from '@/lib/Queue.ts';
-
-export function PagedPager(props: IReaderProps) {
- const { pages, settings, setCurPage, initialPage, curPage, nextChapter, prevChapter } = props;
-
- const selfRef = useRef(null);
-
- const changePage = (newPage: number) => {
- setCurPage(newPage);
- window.scroll({ top: 0 });
- };
-
- function nextPage() {
- if (curPage < pages.length - 1) {
- changePage(curPage + 1);
- } else if (settings.loadNextOnEnding) {
- nextChapter();
- }
- }
-
- function prevPage() {
- if (curPage > 0) {
- changePage(curPage - 1);
- } else {
- prevChapter();
- }
- }
-
- function goLeft() {
- if (settings.readerType === 'SingleLTR') {
- prevPage();
- } else if (settings.readerType === 'SingleRTL') {
- nextPage();
- }
- }
-
- function goRight() {
- if (settings.readerType === 'SingleLTR') {
- nextPage();
- } else if (settings.readerType === 'SingleRTL') {
- prevPage();
- }
- }
-
- function keyboardControl(e: KeyboardEvent) {
- switch (e.key) {
- case 'Space':
- e.preventDefault();
- nextPage();
- break;
- case 'ArrowRight':
- goRight();
- break;
- case 'ArrowLeft':
- goLeft();
- break;
- default:
- break;
- }
- }
-
- function clickControl(e: MouseEvent) {
- if (e.clientX > window.innerWidth / 2) {
- goRight();
- } else {
- goLeft();
- }
- }
-
- useEffect(() => {
- document.addEventListener('keydown', keyboardControl);
-
- return () => {
- document.removeEventListener('keydown', keyboardControl);
- };
- }, [selfRef, curPage, settings.readerType, prevChapter, nextChapter]);
-
- useEffect(() => {
- // Delay scrolling to next cycle
- setTimeout(() => {
- // scroll last read page into view when initialPage changes
- changePage(initialPage);
- }, 0);
- }, [initialPage]);
-
- return (
-
- {pages.map(({ index, src }) => (
- {}}
- src={src}
- settings={settings}
- display={index === curPage}
- priority={index === curPage ? Priority.HIGH : undefined}
- />
- ))}
-
- );
-}
diff --git a/src/modules/reader/components/pager/VerticalPager.tsx b/src/modules/reader/components/pager/VerticalPager.tsx
deleted file mode 100644
index f5264861..00000000
--- a/src/modules/reader/components/pager/VerticalPager.tsx
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import { useCallback, useEffect, useRef } from 'react';
-import Box from '@mui/material/Box';
-import { Page } from '@/modules/reader/components/page/Page.tsx';
-import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx';
-import { IReaderProps } from '@/modules/reader/Reader.types.ts';
-import { Priority } from '@/lib/Queue.ts';
-
-const findCurrentPageIndex = (wrapper: HTMLDivElement): number => {
- for (let i = 0; i < wrapper.children.length; i++) {
- const child = wrapper.children.item(i);
- if (child) {
- const { top, bottom } = child.getBoundingClientRect();
- if (top <= window.innerHeight && bottom > 1) return i;
- }
- }
- return -1;
-};
-
-// TODO: make configurable?
-const SCROLL_SAFE_ZONE = 5; // px
-const SCROLL_OFFSET = 0.95;
-const SCROLL_OFFSET_SLIGHT = 0.25;
-const SCROLL_BEHAVIOR: ScrollBehavior = 'smooth';
-
-const isAtBottom = () => {
- const visibleBottom = window.innerHeight + window.scrollY;
- // SCROLL_SAFE_ZONE is here for special cases when window might be .5px shorter
- // and math just dont add up correctly
- return visibleBottom >= document.body.offsetHeight - SCROLL_SAFE_ZONE;
-};
-const isAtTop = () => window.scrollY <= 0;
-
-export function VerticalPager(props: IReaderProps) {
- const { curPage, pages, settings, setCurPage, initialPage, nextChapter, prevChapter } = props;
-
- const currentPageRef = useRef(initialPage);
- const selfRef = useRef(null);
- const pagesRef = useRef([]);
-
- useEffect(() => {
- let handlingEndOfPage = false;
- const handleScroll = () => {
- if (!selfRef.current) return;
-
- if (isAtBottom()) {
- if (handlingEndOfPage) {
- return;
- }
-
- handlingEndOfPage = true;
-
- // If scroll is moved all the way to the bottom
- // This handles cases when last page is show, but is smaller then
- // window, in which case it would never get marked as read.
- // See https://github.com/Suwayomi/Suwayomi-WebUI/issues/14 for more info
- currentPageRef.current = pages.length - 1;
- setCurPage(currentPageRef.current);
-
- // Go to next chapter if configured to and at bottom
- if (settings.loadNextOnEnding) {
- nextChapter();
- }
- } else {
- handlingEndOfPage = false;
-
- // Update current page in parent
- const currentPage = findCurrentPageIndex(selfRef.current);
- if (currentPage !== currentPageRef.current) {
- currentPageRef.current = currentPage;
- setCurPage(currentPage);
- }
- }
- };
-
- window.addEventListener('scroll', handleScroll);
- return () => {
- window.removeEventListener('scroll', handleScroll);
- };
- }, [settings.loadNextOnEnding, nextChapter]);
-
- const mouseYPos = useRef(0);
- const didMouseMove = useRef(false);
-
- function dragScreen(e: MouseEvent) {
- didMouseMove.current = true;
- window.scrollBy(0, mouseYPos.current - e.pageY);
- }
-
- function dragControl(e: MouseEvent) {
- mouseYPos.current = e.pageY;
- selfRef.current?.addEventListener('mousemove', dragScreen);
- }
-
- function removeDragControl() {
- selfRef.current?.removeEventListener('mousemove', dragScreen);
- }
-
- useEffect(() => {
- selfRef.current?.addEventListener('mousedown', dragControl);
- selfRef.current?.addEventListener('mouseup', removeDragControl);
-
- return () => {
- selfRef.current?.removeEventListener('mousedown', dragControl);
- selfRef.current?.removeEventListener('mouseup', removeDragControl);
- };
- }, [selfRef]);
-
- const go = useCallback(
- (direction: 'up' | 'down', offset: number = SCROLL_OFFSET) => {
- if (direction === 'down' && isAtBottom()) {
- nextChapter();
- return;
- }
-
- if (direction === 'up' && isAtTop()) {
- prevChapter();
- return;
- }
-
- window.scroll({
- top: window.scrollY + window.innerHeight * offset * (direction === 'up' ? -1 : 1),
- behavior: SCROLL_BEHAVIOR,
- });
- },
- [nextChapter, prevChapter],
- );
-
- useEffect(() => {
- const handleKeyboard = (e: KeyboardEvent) => {
- switch (e.key) {
- case 'Space':
- e.preventDefault();
- go(e.shiftKey ? 'up' : 'down');
- break;
- case 'ArrowDown':
- e.preventDefault();
- go(e.shiftKey ? 'up' : 'down', SCROLL_OFFSET_SLIGHT);
- break;
- case 'ArrowRight':
- e.preventDefault();
- go(e.shiftKey ? 'up' : 'down');
- break;
- case 'ArrowUp':
- e.preventDefault();
- go(e.shiftKey ? 'down' : 'up', SCROLL_OFFSET_SLIGHT);
- break;
- case 'ArrowLeft':
- e.preventDefault();
- go(e.shiftKey ? 'down' : 'up');
- break;
- default:
- break;
- }
- };
-
- document.addEventListener('keydown', handleKeyboard, false);
- return () => {
- document.removeEventListener('keydown', handleKeyboard);
- };
- }, [go]);
-
- useResizeObserver(
- pagesRef.current[initialPage],
- useCallback(
- (_, resizeObserver) => {
- const initialPageElement = pagesRef.current[initialPage];
- if (!initialPageElement?.offsetHeight) {
- return;
- }
-
- initialPageElement.scrollIntoView();
- resizeObserver.disconnect();
- },
- [pagesRef.current[initialPage], initialPage],
- ),
- );
-
- return (
- {
- if (didMouseMove.current) {
- didMouseMove.current = false;
- return;
- }
-
- go(e.clientX > window.innerWidth / 2 ? 'down' : 'up');
- }}
- >
- {pages.map((page) => (
- {}}
- settings={settings}
- ref={(e: HTMLDivElement) => {
- pagesRef.current[page.index] = e;
- }}
- priority={page.index === curPage ? Priority.HIGH : undefined}
- />
- ))}
-
- );
-}
diff --git a/src/modules/reader/screens/DefaultReaderSettings.tsx b/src/modules/reader/screens/DefaultReaderSettings.tsx
deleted file mode 100644
index 3e6c925f..00000000
--- a/src/modules/reader/screens/DefaultReaderSettings.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import { useContext, useLayoutEffect } from 'react';
-import { useTranslation } from 'react-i18next';
-import { requestUpdateServerMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
-import { useDefaultReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
-import { ReaderSettingsOptions } from '@/modules/reader/components/ReaderSettingsOptions.tsx';
-import { makeToast } from '@/modules/core/utils/Toast.ts';
-import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
-import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
-import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
-import { LoadingPlaceholder } from '@/modules/core/components/placeholder/LoadingPlaceholder.tsx';
-import { IReaderSettings } from '@/modules/reader/Reader.types.ts';
-import { AllowedMetadataValueTypes } from '@/modules/metadata/Metadata.types.ts';
-
-export function DefaultReaderSettings() {
- const { t } = useTranslation();
- const { setTitle, setAction } = useContext(NavBarContext);
- useLayoutEffect(() => {
- setTitle(t('reader.settings.title.default_reader_settings'));
- setAction(null);
-
- return () => {
- setTitle('');
- setAction(null);
- };
- }, [t]);
-
- const {
- settings,
- loading,
- request: { error, refetch },
- } = useDefaultReaderSettings();
-
- const setSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist: boolean = true) => {
- if (persist) {
- requestUpdateServerMetadata([[key, value]]).catch(() =>
- makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
- );
- }
- };
-
- if (loading) {
- return ;
- }
-
- if (error) {
- return (
- refetch().catch(defaultPromiseErrorHandler('DefaultReaderSettings::refetch'))}
- />
- );
- }
-
- return (
-
- );
-}
diff --git a/src/modules/reader/screens/Reader.tsx b/src/modules/reader/screens/Reader.tsx
deleted file mode 100644
index 298141ae..00000000
--- a/src/modules/reader/screens/Reader.tsx
+++ /dev/null
@@ -1,576 +0,0 @@
-/*
- * Copyright (C) Contributors to the Suwayomi project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-import CircularProgress from '@mui/material/CircularProgress';
-import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
-import { useLocation, useNavigate, useParams } from 'react-router-dom';
-import Box from '@mui/material/Box';
-import { useTranslation } from 'react-i18next';
-import { requestManager } from '@/lib/requests/RequestManager.ts';
-import { getReaderSettingsFor, useDefaultReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts';
-import { requestUpdateMangaMetadata } from '@/modules/metadata/services/MetadataUpdater.ts';
-import { HorizontalPager } from '@/modules/reader/components/pager/HorizontalPager.tsx';
-import { PageNumber } from '@/modules/reader/components/page/PageNumber.tsx';
-import { PagedPager } from '@/modules/reader/components/pager/PagedPager.tsx';
-import { DoublePagedPager } from '@/modules/reader/components/pager/DoublePagedPager.tsx';
-import { VerticalPager } from '@/modules/reader/components/pager/VerticalPager.tsx';
-import { ReaderNavBar } from '@/modules/reader/components/ReaderNavBar.tsx';
-import { makeToast } from '@/modules/core/utils/Toast.ts';
-import { NavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
-import { useDebounce } from '@/modules/core/hooks/useDebounce.ts';
-import {
- GetChaptersReaderQuery,
- GetChaptersReaderQueryVariables,
- GetMangaReaderQuery,
- UpdateChapterPatchInput,
-} from '@/lib/graphql/generated/graphql.ts';
-import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts';
-import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
-import { ChapterIdInfo, Chapters } from '@/modules/chapter/services/Chapters.ts';
-import { EmptyViewAbsoluteCentered } from '@/modules/core/components/placeholder/EmptyViewAbsoluteCentered.tsx';
-import { GET_CHAPTERS_READER } from '@/lib/graphql/queries/ChapterQuery.ts';
-import { GET_MANGA_READER } from '@/lib/graphql/queries/MangaQuery.ts';
-import { CHAPTER_READER_FIELDS } from '@/lib/graphql/fragments/ChapterFragments.ts';
-import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
-import { IReaderSettings, ReaderType } from '@/modules/reader/Reader.types.ts';
-import { DirectionOffset } from '@/Base.types.ts';
-import { AllowedMetadataValueTypes } from '@/modules/metadata/Metadata.types.ts';
-import { TMangaReader } from '@/modules/manga/Manga.types.ts';
-
-type TChapter = GetChaptersReaderQuery['chapters']['nodes'][number];
-
-const getChapterFromCache = (id: ChapterIdInfo['id']): TChapter | null =>
- Chapters.getFromCache(id, CHAPTER_READER_FIELDS, 'CHAPTER_READER_FIELDS')!;
-
-const getReaderComponent = (readerType: ReaderType) => {
- switch (readerType) {
- case 'ContinuesVertical':
- case 'Webtoon':
- return VerticalPager;
- break;
- case 'SingleVertical':
- case 'SingleRTL':
- case 'SingleLTR':
- return PagedPager;
- break;
- case 'DoubleVertical':
- case 'DoubleRTL':
- case 'DoubleLTR':
- return DoublePagedPager;
- break;
- case 'ContinuesHorizontalLTR':
- case 'ContinuesHorizontalRTL':
- return HorizontalPager;
- default:
- return VerticalPager;
- break;
- }
-};
-
-const range = (n: number) => Array.from({ length: n }, (value, key) => key);
-const fallbackChapter = {
- pageCount: -1,
- sourceOrder: -1,
- chapterCount: 0,
- lastPageRead: 0,
- name: 'Loading...',
- manga: { id: -1 },
-} as unknown as TChapter;
-
-export function Reader() {
- const { t } = useTranslation();
- const navigate = useNavigate();
- const location = useLocation();
-
- const { chapterIndex, mangaId } = useParams<{ chapterIndex: string; mangaId: string }>();
-
- const fallbackManga = useMemo(
- () =>
- ({
- id: +mangaId,
- title: '',
- thumbnailUrl: '',
- genre: [],
- inLibraryAt: 0,
- lastReadAt: 0,
- chapters: { totalCount: 0 },
- trackRecords: { totalCount: 0 },
- }) as unknown as TMangaReader,
- [mangaId],
- );
-
- const {
- data,
- loading: isMangaLoading,
- error: mangaError,
- refetch: refetchManga,
- } = requestManager.useGetManga(GET_MANGA_READER, mangaId);
- const loadedChapter = useRef(null);
- const isChapterLoaded =
- Number(mangaId) === loadedChapter.current?.mangaId &&
- Number(chapterIndex) === loadedChapter.current?.sourceOrder &&
- loadedChapter.current?.pageCount !== -1;
- const manga = data?.manga ?? fallbackManga;
- const {
- data: chaptersData,
- loading: isChapterLoading,
- error: chapterError,
- refetch: fetchChapter,
- } = requestManager.useGetChapters(
- GET_CHAPTERS_READER,
- {
- condition: { mangaId: Number(mangaId), sourceOrder: Number(chapterIndex) },
- },
- {
- notifyOnNetworkStatusChange: true,
- },
- );
- const chapterData = chaptersData?.chapters.nodes[0];
- const arePagesUpdatedRef = useRef(false);
-
- const {
- settings: { downloadAheadLimit },
- } = useMetadataServerSettings();
- const isDownloadAheadEnabled = !!downloadAheadLimit;
-
- const getLoadedChapter = () => {
- const isAChapterLoaded = loadedChapter.current;
-
- const isSameAsLoadedChapter = isAChapterLoaded && isChapterLoaded;
- if (isSameAsLoadedChapter) {
- const didPageCountChange = chapterData && loadedChapter.current?.pageCount !== chapterData.pageCount;
- return didPageCountChange ? chapterData! : loadedChapter.current;
- }
-
- arePagesUpdatedRef.current = false;
-
- if (chapterData) {
- return chapterData;
- }
-
- return null;
- };
- loadedChapter.current = getLoadedChapter();
-
- const chapter = loadedChapter.current ?? fallbackChapter;
-
- const initialChapterRef = useRef(fallbackChapter);
- if (initialChapterRef.current === fallbackChapter) {
- initialChapterRef.current = chapter;
- }
- if (chapter.mangaId !== initialChapterRef.current?.mangaId) {
- initialChapterRef.current = fallbackChapter;
- }
-
- const [fetchPages, { loading: arePagesLoading, error: pagesError }] = requestManager.useGetChapterPagesFetch(
- chapter.id,
- );
-
- const doFetchPages = () => {
- if (isChapterLoading) {
- return;
- }
-
- fetchPages()
- .then(() => {
- arePagesUpdatedRef.current = true;
- })
- .catch(defaultPromiseErrorHandler('Reader::fetchPages'));
- };
-
- useEffect(() => {
- doFetchPages();
- }, [chapter.id]);
-
- const [wasLastPageReadSet, setWasLastPageReadSet] = useState(false);
- const [curPage, setCurPage] = useState(0);
- const isLastPage = curPage === chapter.pageCount - 1;
- const curPageDebounced = useDebounce(curPage, isLastPage ? 0 : 1000);
- const [pageToScrollTo, setPageToScrollTo] = useState(undefined);
- const { setOverride, setTitle, readerNavBarWidth: currentNavBarWidth } = useContext(NavBarContext);
- const [retrievingNextChapter, setRetrievingNextChapter] = useState(false);
- const {
- data: mangaChaptersData,
- loading: areChaptersLoading,
- error: chaptersError,
- refetch: refetchChapters,
- } = requestManager.useGetMangaChapters(
- GET_CHAPTERS_READER,
- mangaId,
- { nextFetchPolicy: 'standby' },
- );
- const mangaChapters = mangaChaptersData?.chapters.nodes;
-
- const isLoading =
- isMangaLoading ||
- isChapterLoading ||
- areChaptersLoading ||
- arePagesLoading ||
- (!arePagesUpdatedRef.current && !chapterError && !pagesError);
- const error = mangaError ?? chapterError ?? chaptersError ?? pagesError;
-
- const { settings: defaultSettings, loading: areDefaultSettingsLoading } = useDefaultReaderSettings();
- const [settings, setSettings] = useState(defaultSettings);
-
- const { settings: metadataSettings } = useMetadataServerSettings();
-
- const uniqueChapters = useMemo(
- () =>
- settings.skipDupChapters
- ? Chapters.removeDuplicates(initialChapterRef.current, mangaChapters ?? [])
- : (mangaChapters ?? []),
- [initialChapterRef.current, mangaChapters, settings.skipDupChapters],
- );
- const prevChapters = useMemo(
- () =>
- Chapters.getNextChapters(chapter, mangaChapters ?? [], {
- offset: DirectionOffset.PREVIOUS,
- skipDupe: settings.skipDupChapters,
- skipDupeChapter: initialChapterRef.current,
- }),
- [chapter, initialChapterRef.current, mangaChapters, settings.skipDupChapters],
- );
- const nextChapters = useMemo(
- () =>
- Chapters.getNextChapters(chapter, mangaChapters ?? [], {
- skipDupe: settings.skipDupChapters,
- skipDupeChapter: initialChapterRef.current,
- }),
- [chapter, initialChapterRef.current, mangaChapters, settings.skipDupChapters],
- );
- const prevChapter = useMemo(
- () =>
- Chapters.getNextChapter(chapter, mangaChapters ?? [], {
- offset: DirectionOffset.PREVIOUS,
- skipDupe: settings.skipDupChapters,
- skipDupeChapter: initialChapterRef.current,
- }),
- [chapter, initialChapterRef.current, mangaChapters, settings.skipDupChapters],
- );
- const nextChapter = useMemo(
- () =>
- Chapters.getNextChapter(chapter, mangaChapters ?? [], {
- skipDupe: settings.skipDupChapters,
- skipDupeChapter: initialChapterRef.current,
- }),
- [chapter, initialChapterRef.current, mangaChapters, settings.skipDupChapters],
- );
-
- const updateChapter = (patch: UpdateChapterPatchInput) => {
- if (chapter === fallbackChapter) {
- return;
- }
-
- const getChapterIdsToDelete = () => {
- const isAutoDeletionEnabled = !!patch.isRead && !!metadataSettings.deleteChaptersWhileReading;
- if (!isAutoDeletionEnabled || !mangaChapters) {
- return [];
- }
-
- const chapterToDelete = [chapter, ...prevChapters][metadataSettings.deleteChaptersWhileReading - 1];
-
- if (!chapterToDelete) {
- return [];
- }
-
- // chapter has to exist in the cache since the reader fetches all chapters of the manga
- const chapterToDeleteUpToDateData = getChapterFromCache(chapterToDelete.id)!;
-
- const shouldDeleteChapter =
- chapterToDeleteUpToDateData.isRead &&
- Chapters.isDeletable(chapterToDeleteUpToDateData, metadataSettings.deleteChaptersWithBookmark);
- if (!shouldDeleteChapter) {
- return [];
- }
-
- if (!settings.skipDupChapters) {
- return Chapters.getIds([chapterToDelete]);
- }
-
- return Chapters.getIds(Chapters.addDuplicates([chapterToDelete], mangaChapters));
- };
-
- const downloadAhead = () => {
- const currentChapter = getChapterFromCache(chapter.id);
-
- const inDownloadRange = (patch.lastPageRead ?? 0) / chapter.pageCount > 0.25;
- const shouldCheckDownloadAhead =
- isDownloadAheadEnabled && manga.inLibrary && !!currentChapter?.isDownloaded && inDownloadRange;
-
- if (shouldCheckDownloadAhead) {
- const nextChapterUpToDate = nextChapter ? getChapterFromCache(nextChapter.id) : null;
-
- if (!nextChapterUpToDate?.isDownloaded) {
- return;
- }
-
- const nextChaptersUpToDate = Chapters.getNonRead(nextChapters).map(
- // the chapters have to be in the cache since the reader fetches the whole chapter list of the manga
- (mangaChapter) => getChapterFromCache(mangaChapter.id)!,
- );
-
- const chapterIdsToDownload = nextChaptersUpToDate
- // "settingsData" can't be undefined since this would not get executed otherwise
- .slice(-downloadAheadLimit)
- .filter((mangaChapter) => !mangaChapter.isDownloaded)
- .map((mangaChapter) => mangaChapter.id)
- .filter((id) => !Chapters.isDownloading(id));
-
- if (!chapterIdsToDownload.length) {
- return;
- }
-
- Chapters.download(chapterIdsToDownload).catch(
- defaultPromiseErrorHandler('Reader::updateChapter: shouldDownloadAhead'),
- );
- }
- };
-
- downloadAhead();
-
- const chapterIds = settings.skipDupChapters
- ? Chapters.getIds(Chapters.addDuplicates([chapter], mangaChapters ?? [chapter]))
- : [chapter.id];
-
- requestManager
- .updateChapters(
- chapterIds,
- {
- ...patch,
- chapterIdsToDelete: getChapterIdsToDelete(),
- trackProgressMangaId:
- metadataSettings.updateProgressAfterReading && patch.isRead && manga.trackRecords.totalCount
- ? manga.id
- : undefined,
- },
- { errorPolicy: 'all' },
- )
- .response.catch(defaultPromiseErrorHandler('Reader::updateChapter'));
- };
-
- const setSettingValue = (key: keyof IReaderSettings, value: AllowedMetadataValueTypes, persist: boolean = true) => {
- setSettings({ ...settings, [key]: value });
- if (persist) {
- requestUpdateMangaMetadata(manga, [[key, value]]).catch(() =>
- makeToast(t('reader.settings.error.label.failed_to_save_settings'), 'warning'),
- );
- }
- };
-
- const openNextChapter = useCallback(
- (offset: DirectionOffset) => {
- const isOpenNextChapter = offset === DirectionOffset.NEXT;
- const chapterToOpen = isOpenNextChapter ? nextChapter : prevChapter;
-
- if (!chapterToOpen) {
- makeToast(
- t(
- isOpenNextChapter
- ? 'reader.error.label.next_chapter_does_not_exist'
- : 'reader.error.label.prev_chapter_does_not_exist',
- ),
- 'error',
- );
- return;
- }
-
- setRetrievingNextChapter(true);
- setCurPage(0);
-
- navigate(`/manga/${manga.id}/chapter/${chapterToOpen.sourceOrder}`, {
- replace: true,
- state: location.state,
- });
-
- setRetrievingNextChapter(false);
- },
- [manga.id, prevChapter?.id, nextChapter?.id],
- );
-
- useEffect(() => {
- if (isLoading || !chapter) {
- setCurPage(0);
- return;
- }
-
- setWasLastPageReadSet(true);
- if (chapter.lastPageRead === chapter.pageCount - 1) {
- // last page, also probably read = true, we will load the first page.
- setCurPage(0);
- } else setCurPage(chapter.lastPageRead);
- }, [chapter, isLoading]);
-
- useLayoutEffect(() => {
- if (!manga?.title || chapter.name === t('global.label.loading')) {
- setTitle(t('reader.title'));
- } else {
- setTitle(`${manga.title}: ${chapter.name}`);
- }
- }, [t, manga, chapter]);
-
- useEffect(() => {
- if (!areDefaultSettingsLoading && !isMangaLoading) {
- setSettings(getReaderSettingsFor(manga, defaultSettings));
- }
- }, [areDefaultSettingsLoading, isMangaLoading]);
-
- useLayoutEffect(() => {
- // set the custom navbar
- setOverride({
- status: true,
- value: (
-
- ),
- });
-
- // clean up for when we leave the reader
- return () => setOverride({ status: false, value: });
- }, [manga, chapter, settings, curPage, chapterIndex, retrievingNextChapter, openNextChapter, uniqueChapters]);
-
- useEffect(() => {
- if (!wasLastPageReadSet) {
- return;
- }
-
- if (chapter === fallbackChapter) {
- return;
- }
-
- const chapterUpToDate = getChapterFromCache(chapter.id);
- if (curPageDebounced === chapterUpToDate?.lastPageRead) {
- return;
- }
-
- // do not mutate the chapter, this will cause the page to jump around due to always scrolling to the last read page
- const updateLastPageRead = curPageDebounced !== -1;
- const updateIsRead = curPageDebounced === chapter.pageCount - 1;
- const shouldUpdateChapter = updateLastPageRead || updateIsRead;
- if (!shouldUpdateChapter) {
- return;
- }
-
- updateChapter({
- lastPageRead: updateLastPageRead ? curPageDebounced : undefined,
- isRead: updateIsRead ? true : undefined,
- });
- }, [curPageDebounced, isDownloadAheadEnabled]);
-
- const loadNextChapter = useCallback(() => {
- updateChapter({
- lastPageRead: chapter.pageCount - 1,
- isRead: true,
- });
-
- openNextChapter(DirectionOffset.NEXT);
- }, [chapter.pageCount, openNextChapter, chapter, manga]);
-
- const loadPrevChapter = useCallback(() => {
- openNextChapter(DirectionOffset.PREVIOUS);
- }, [openNextChapter]);
-
- const scrollbarHeight = MediaQuery.useGetScrollbarSize('height');
-
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- if (error) {
- return (
- {
- if (mangaError) {
- refetchManga().catch(defaultPromiseErrorHandler('Reader::refetchManga'));
- }
-
- if (chapterError) {
- fetchChapter().catch(defaultPromiseErrorHandler('Reader::refetchChapter'));
- }
-
- if (chaptersError) {
- refetchChapters().catch(defaultPromiseErrorHandler('Reader::refetchChapters'));
- }
-
- if (pagesError) {
- doFetchPages();
- }
- }}
- />
- );
- }
-
- if (!chapter.pageCount) {
- return ;
- }
-
- const pages = range(chapter.pageCount).map((index) => ({
- index,
- src: requestManager.getChapterPageUrl(mangaId, chapterIndex, index),
- }));
-
- const ReaderComponent = getReaderComponent(settings.readerType);
-
- // last page, also probably read = true, we will load the first page.
- const initialPage = pageToScrollTo ?? (chapter.lastPageRead === chapter.pageCount - 1 ? 0 : chapter.lastPageRead);
-
- const navBarWidth = settings.staticNav ? currentNavBarWidth : 0;
-
- return (
-
-
-
-
-
-
- );
-}