Add "Webtoon" reading mode
This commit is contained in:
24
src/assets/icons/svg/WebtoonPageIcon.tsx
Normal file
24
src/assets/icons/svg/WebtoonPageIcon.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 SvgIcon from '@mui/material/SvgIcon';
|
||||||
|
|
||||||
|
export const WebtoonPageIcon = () => (
|
||||||
|
<SvgIcon>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M20 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V2m0 20v-8a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
@@ -62,6 +62,7 @@ const CONTINUOUS_READING_MODE_TO_SCROLL_DIRECTION: Record<ReadingMode, ScrollDir
|
|||||||
[ReadingMode.DOUBLE_PAGE]: ScrollDirection.Y,
|
[ReadingMode.DOUBLE_PAGE]: ScrollDirection.Y,
|
||||||
[ReadingMode.CONTINUOUS_VERTICAL]: ScrollDirection.Y,
|
[ReadingMode.CONTINUOUS_VERTICAL]: ScrollDirection.Y,
|
||||||
[ReadingMode.CONTINUOUS_HORIZONTAL]: ScrollDirection.X,
|
[ReadingMode.CONTINUOUS_HORIZONTAL]: ScrollDirection.X,
|
||||||
|
[ReadingMode.WEBTOON]: ScrollDirection.Y,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReaderHotkeys = ({
|
export const ReaderHotkeys = ({
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IReaderSettingsWithDefaultFlag } from '@/modules/reader/types/Reader.types.ts';
|
import { IReaderSettingsWithDefaultFlag, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
|
||||||
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx';
|
||||||
import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
||||||
@@ -21,7 +21,7 @@ export const ReaderSettingPageGap = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const isChangeable = isContinuousReadingMode(readingMode.value);
|
const isChangeable = readingMode.value !== ReadingMode.WEBTOON && isContinuousReadingMode(readingMode.value);
|
||||||
if (!isChangeable) {
|
if (!isChangeable) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ import { isTransitionPageVisible } from '@/modules/reader/utils/ReaderPager.util
|
|||||||
import { useBackButton } from '@/modules/core/hooks/useBackButton.ts';
|
import { useBackButton } from '@/modules/core/hooks/useBackButton.ts';
|
||||||
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx';
|
||||||
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
||||||
import { isContinuousReadingMode } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
import {
|
||||||
|
isContinuousReadingMode,
|
||||||
|
isContinuousVerticalReadingMode,
|
||||||
|
} from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
||||||
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx';
|
||||||
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
|
import { AppRoutes } from '@/modules/core/AppRoute.constants.ts';
|
||||||
|
|
||||||
@@ -94,7 +97,7 @@ export const ReaderTransitionPage = ({
|
|||||||
...applyStyles(isContinuousReadingMode(readingMode), {
|
...applyStyles(isContinuousReadingMode(readingMode), {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
transform: 'scale(1)',
|
transform: 'scale(1)',
|
||||||
...applyStyles(readingMode === ReadingMode.CONTINUOUS_VERTICAL, {
|
...applyStyles(isContinuousVerticalReadingMode(readingMode), {
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
...applyStyles(!isFitWidthPageScaleMode, { alignItems: 'baseline' }),
|
...applyStyles(!isFitWidthPageScaleMode, { alignItems: 'baseline' }),
|
||||||
@@ -120,7 +123,7 @@ export const ReaderTransitionPage = ({
|
|||||||
// on small screens with "fit to with" enabled, "left 50%" does not center the element in the
|
// on small screens with "fit to with" enabled, "left 50%" does not center the element in the
|
||||||
// viewport which then causes "translate" to move the element mostly outside the viewport with
|
// viewport which then causes "translate" to move the element mostly outside the viewport with
|
||||||
// only a small part of it being visible
|
// only a small part of it being visible
|
||||||
!isFitWidthPageScaleMode && readingMode === ReadingMode.CONTINUOUS_VERTICAL,
|
!isFitWidthPageScaleMode && isContinuousVerticalReadingMode(readingMode),
|
||||||
{
|
{
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ const READING_MODE_TO_IN_VIEWPORT_TYPE: Record<ReadingMode, PageInViewportType>
|
|||||||
[ReadingMode.DOUBLE_PAGE]: PageInViewportType.X,
|
[ReadingMode.DOUBLE_PAGE]: PageInViewportType.X,
|
||||||
[ReadingMode.CONTINUOUS_VERTICAL]: PageInViewportType.Y,
|
[ReadingMode.CONTINUOUS_VERTICAL]: PageInViewportType.Y,
|
||||||
[ReadingMode.CONTINUOUS_HORIZONTAL]: PageInViewportType.X,
|
[ReadingMode.CONTINUOUS_HORIZONTAL]: PageInViewportType.X,
|
||||||
|
[ReadingMode.WEBTOON]: PageInViewportType.Y,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReaderViewer = forwardRef((_, ref: ForwardedRef<HTMLDivElement | null>) => {
|
export const ReaderViewer = forwardRef((_, ref: ForwardedRef<HTMLDivElement | null>) => {
|
||||||
@@ -111,6 +112,7 @@ export const ReaderViewer = forwardRef((_, ref: ForwardedRef<HTMLDivElement | nu
|
|||||||
case ReadingMode.DOUBLE_PAGE:
|
case ReadingMode.DOUBLE_PAGE:
|
||||||
return ReaderDoublePagedPager;
|
return ReaderDoublePagedPager;
|
||||||
case ReadingMode.CONTINUOUS_VERTICAL:
|
case ReadingMode.CONTINUOUS_VERTICAL:
|
||||||
|
case ReadingMode.WEBTOON:
|
||||||
return ReaderVerticalPager;
|
return ReaderVerticalPager;
|
||||||
case ReadingMode.CONTINUOUS_HORIZONTAL:
|
case ReadingMode.CONTINUOUS_HORIZONTAL:
|
||||||
return ReaderHorizontalPager;
|
return ReaderHorizontalPager;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
import { ReaderService } from '@/modules/reader/services/ReaderService.ts';
|
||||||
import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx';
|
import { BasePager } from '@/modules/reader/components/viewer/pager/BasePager.tsx';
|
||||||
import { ReaderPagerProps } from '@/modules/reader/types/Reader.types.ts';
|
import { ReaderPagerProps, ReadingMode } from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx';
|
import { createReaderPage } from '@/modules/reader/utils/ReaderPager.utils.tsx';
|
||||||
|
|
||||||
export const ReaderVerticalPager = ({
|
export const ReaderVerticalPager = ({
|
||||||
@@ -20,7 +20,9 @@ export const ReaderVerticalPager = ({
|
|||||||
}: ReaderPagerProps) => {
|
}: ReaderPagerProps) => {
|
||||||
const { currentPageIndex, totalPages } = props;
|
const { currentPageIndex, totalPages } = props;
|
||||||
|
|
||||||
const { pageGap } = ReaderService.useSettings();
|
const { readingMode, pageGap } = ReaderService.useSettings();
|
||||||
|
const isWebtoonMode = readingMode.value === ReadingMode.WEBTOON;
|
||||||
|
const actualPageGap = isWebtoonMode ? 0 : pageGap;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasePager
|
<BasePager
|
||||||
@@ -46,7 +48,7 @@ export const ReaderVerticalPager = ({
|
|||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
flexWrap: 'nowrap',
|
flexWrap: 'nowrap',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: `${pageGap.value}px`,
|
gap: `${actualPageGap}px`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { ContinuousVerticalPageIcon } from '@/assets/icons/svg/ContinuousVertica
|
|||||||
import { ContinuousHorizontalPageIcon } from '@/assets/icons/svg/ContinuousHorizontalPageIcon.tsx';
|
import { ContinuousHorizontalPageIcon } from '@/assets/icons/svg/ContinuousHorizontalPageIcon.tsx';
|
||||||
import { TranslationKey } from '@/Base.types.ts';
|
import { TranslationKey } from '@/Base.types.ts';
|
||||||
import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
|
import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts';
|
||||||
|
import { WebtoonPageIcon } from '@/assets/icons/svg/WebtoonPageIcon.tsx';
|
||||||
|
|
||||||
export const DEFAULT_READER_PROFILE = ReadingMode.SINGLE_PAGE;
|
export const DEFAULT_READER_PROFILE = ReadingMode.SINGLE_PAGE;
|
||||||
|
|
||||||
@@ -188,6 +189,10 @@ export const READING_MODE_VALUE_TO_DISPLAY_DATA = {
|
|||||||
title: 'reader.settings.reader_type.label.continuous_horizontal',
|
title: 'reader.settings.reader_type.label.continuous_horizontal',
|
||||||
icon: <ContinuousHorizontalPageIcon />,
|
icon: <ContinuousHorizontalPageIcon />,
|
||||||
},
|
},
|
||||||
|
[ReadingMode.WEBTOON]: {
|
||||||
|
title: 'reader.settings.reader_type.label.webtoon',
|
||||||
|
icon: <WebtoonPageIcon />,
|
||||||
|
},
|
||||||
} satisfies ValueToDisplayData<ReadingMode>;
|
} satisfies ValueToDisplayData<ReadingMode>;
|
||||||
|
|
||||||
export const READING_MODE_VALUES = Object.values(ReadingMode).filter((value) => typeof value === 'number');
|
export const READING_MODE_VALUES = Object.values(ReadingMode).filter((value) => typeof value === 'number');
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export enum ReadingMode {
|
|||||||
DOUBLE_PAGE,
|
DOUBLE_PAGE,
|
||||||
CONTINUOUS_VERTICAL,
|
CONTINUOUS_VERTICAL,
|
||||||
CONTINUOUS_HORIZONTAL,
|
CONTINUOUS_HORIZONTAL,
|
||||||
|
WEBTOON,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ReaderPageScaleMode {
|
export enum ReaderPageScaleMode {
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ import {
|
|||||||
ReadingMode,
|
ReadingMode,
|
||||||
} from '@/modules/reader/types/Reader.types.ts';
|
} from '@/modules/reader/types/Reader.types.ts';
|
||||||
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts';
|
||||||
import { isContinuousReadingMode, isReaderWidthEditable } from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
import {
|
||||||
|
isContinuousReadingMode,
|
||||||
|
isContinuousVerticalReadingMode,
|
||||||
|
isReaderWidthEditable,
|
||||||
|
} from '@/modules/reader/utils/ReaderSettings.utils.tsx';
|
||||||
import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types.ts';
|
import { ReaderStatePages } from '@/modules/reader/types/ReaderProgressBar.types.ts';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
import { ReaderPage } from '@/modules/reader/components/viewer/ReaderPage.tsx';
|
import { ReaderPage } from '@/modules/reader/components/viewer/ReaderPage.tsx';
|
||||||
@@ -71,6 +75,10 @@ export const getImagePlaceholderStyling = (
|
|||||||
minWidth: DEFAULT_SINGLE_PAGE_WIDTH,
|
minWidth: DEFAULT_SINGLE_PAGE_WIDTH,
|
||||||
minHeight: `calc(${DEFAULT_SINGLE_PAGE_HEIGHT} - ${scrollbarXSize}px)`,
|
minHeight: `calc(${DEFAULT_SINGLE_PAGE_HEIGHT} - ${scrollbarXSize}px)`,
|
||||||
},
|
},
|
||||||
|
[ReadingMode.WEBTOON]: {
|
||||||
|
minWidth: `calc(${DEFAULT_SINGLE_PAGE_WIDTH} - ${scrollbarYSize}px)`,
|
||||||
|
minHeight: '100vh',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const defaultStyling = READING_MODE_TO_PLACEHOLDER_SIZE[readingMode];
|
const defaultStyling = READING_MODE_TO_PLACEHOLDER_SIZE[readingMode];
|
||||||
const minWidthForStretch = isDoublePage ? '50%' : '100%';
|
const minWidthForStretch = isDoublePage ? '50%' : '100%';
|
||||||
@@ -91,7 +99,7 @@ export const getImagePlaceholderStyling = (
|
|||||||
...defaultStyling,
|
...defaultStyling,
|
||||||
...applyStyles(shouldStretchPage, {
|
...applyStyles(shouldStretchPage, {
|
||||||
minHeight: `calc(100vh - ${scrollbarXSize}px)`,
|
minHeight: `calc(100vh - ${scrollbarXSize}px)`,
|
||||||
...applyStyles(readingMode === ReadingMode.CONTINUOUS_VERTICAL, {
|
...applyStyles(isContinuousVerticalReadingMode(readingMode), {
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -105,7 +113,7 @@ export const getImagePlaceholderStyling = (
|
|||||||
...applyStyles(shouldStretchPage, {
|
...applyStyles(shouldStretchPage, {
|
||||||
minWidth: minWidthForStretch,
|
minWidth: minWidthForStretch,
|
||||||
minHeight: `calc(100vh - ${scrollbarXSize}px)`,
|
minHeight: `calc(100vh - ${scrollbarXSize}px)`,
|
||||||
...applyStyles(readingMode === ReadingMode.CONTINUOUS_VERTICAL, {
|
...applyStyles(isContinuousVerticalReadingMode(readingMode), {
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -160,7 +168,7 @@ export const getImageWidthStyling = (
|
|||||||
minWidth: 'unset',
|
minWidth: 'unset',
|
||||||
minHeight: '100%',
|
minHeight: '100%',
|
||||||
}),
|
}),
|
||||||
...applyStyles(readingMode === ReadingMode.CONTINUOUS_VERTICAL, {
|
...applyStyles(isContinuousVerticalReadingMode(readingMode), {
|
||||||
minWidth: width,
|
minWidth: width,
|
||||||
minHeight: 'unset',
|
minHeight: 'unset',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -15,4 +15,7 @@ export const isReaderWidthEditable = (pageScaleMode: IReaderSettings['pageScaleM
|
|||||||
[ReaderPageScaleMode.WIDTH, ReaderPageScaleMode.SCREEN].includes(pageScaleMode);
|
[ReaderPageScaleMode.WIDTH, ReaderPageScaleMode.SCREEN].includes(pageScaleMode);
|
||||||
|
|
||||||
export const isContinuousReadingMode = (readingMode: IReaderSettings['readingMode']): boolean =>
|
export const isContinuousReadingMode = (readingMode: IReaderSettings['readingMode']): boolean =>
|
||||||
[ReadingMode.CONTINUOUS_VERTICAL, ReadingMode.CONTINUOUS_HORIZONTAL].includes(readingMode);
|
[ReadingMode.CONTINUOUS_VERTICAL, ReadingMode.CONTINUOUS_HORIZONTAL, ReadingMode.WEBTOON].includes(readingMode);
|
||||||
|
|
||||||
|
export const isContinuousVerticalReadingMode = (readingMode: IReaderSettings['readingMode']): boolean =>
|
||||||
|
[ReadingMode.CONTINUOUS_VERTICAL, ReadingMode.WEBTOON].includes(readingMode);
|
||||||
|
|||||||
Reference in New Issue
Block a user