diff --git a/public/locales/en.json b/public/locales/en.json index d32d346a..ff4eb221 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -754,6 +754,7 @@ }, "reader": { "button": { + "chapter_list": "Chapter list", "close_menu": "Close menu", "exit": "Exit reader", "next_chapter": "Next Chapter", @@ -786,6 +787,8 @@ "offset_first_page": "Offset first page", "reader_type": "Reader type", "reader_width": "Reader width", + "reading_direction": "Reading direction", + "reading_mode": "Reading mode", "scale_page": "Scale small pages", "show_page_number": "Show page number", "skip_dup_chapters": "Skip duplicate chapters", @@ -793,16 +796,17 @@ }, "reader_type": { "label": { - "continuous_horizontal_ltr": "Horizontal (LTR) ➡️", - "continuous_horizontal_rtl": "Horizontal (RTL) ⬅️", - "continuous_vertical": "Continues vertical", - "double_page_ltr": "Double page (LTR) ➡️", - "double_page_rtl": "Double page (RTL) ⬅️", - "single_page_ltr": "Single page (LTR) ➡️", - "single_page_rtl": "Single page (RTL) ⬅️", + "continuous_horizontal": "Continuous horizontal", + "continuous_vertical": "Continuous vertical", + "double_page": "Double page", + "single_page": "Single page", "webtoon": "Webtoon" } }, + "reading_direction": { + "ltr": "Left to right", + "rtl": "Right to left" + }, "title": { "default_reader_settings": "Default Reader Settings", "reader_settings": "Reader Settings" diff --git a/src/assets/icons/svg/ContinuousHorizontalPageIcon.tsx b/src/assets/icons/svg/ContinuousHorizontalPageIcon.tsx new file mode 100644 index 00000000..673da9f4 --- /dev/null +++ b/src/assets/icons/svg/ContinuousHorizontalPageIcon.tsx @@ -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 ContinuousHorizontalPageIcon = () => ( + + + + + +); diff --git a/src/assets/icons/svg/ContinuousVerticalPageIcon.tsx b/src/assets/icons/svg/ContinuousVerticalPageIcon.tsx new file mode 100644 index 00000000..07885056 --- /dev/null +++ b/src/assets/icons/svg/ContinuousVerticalPageIcon.tsx @@ -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 ContinuousVerticalPageIcon = () => ( + + + + + +); diff --git a/src/assets/icons/svg/DoublePageIcon.tsx b/src/assets/icons/svg/DoublePageIcon.tsx new file mode 100644 index 00000000..68da4f55 --- /dev/null +++ b/src/assets/icons/svg/DoublePageIcon.tsx @@ -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 DoublePageIcon = () => ( + + + + + +); diff --git a/src/assets/icons/svg/OffsetDoubleSpreadIcon.tsx b/src/assets/icons/svg/OffsetDoubleSpreadIcon.tsx new file mode 100644 index 00000000..9ac182bf --- /dev/null +++ b/src/assets/icons/svg/OffsetDoubleSpreadIcon.tsx @@ -0,0 +1,17 @@ +/* + * 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 OffsetDoubleSpreadIcon = () => ( + + + + + +); diff --git a/src/assets/icons/svg/SinglePageIcon.tsx b/src/assets/icons/svg/SinglePageIcon.tsx new file mode 100644 index 00000000..6571c2ab --- /dev/null +++ b/src/assets/icons/svg/SinglePageIcon.tsx @@ -0,0 +1,32 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import SvgIcon from '@mui/material/SvgIcon'; + +export const SinglePageIcon = () => ( + + + + + + +); diff --git a/src/modules/chapter/components/actions/ChapterActionMenuItems.tsx b/src/modules/chapter/components/actions/ChapterActionMenuItems.tsx index e2c91a68..3e72f09d 100644 --- a/src/modules/chapter/components/actions/ChapterActionMenuItems.tsx +++ b/src/modules/chapter/components/actions/ChapterActionMenuItems.tsx @@ -40,7 +40,7 @@ import { import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts'; import { useMetadataServerSettings } from '@/modules/settings/services/ServerSettingsMetadata.ts'; -type BaseProps = { onClose: () => void }; +type BaseProps = { onClose: () => void; selectable?: boolean }; type TChapter = ChapterIdInfo & ChapterMangaInfo & @@ -71,6 +71,7 @@ export const ChapterActionMenuItems = ({ canBeDownloaded = false, selectedChapters = [], onClose, + selectable = true, }: Props) => { const { t } = useTranslation(); @@ -158,7 +159,7 @@ export const ChapterActionMenuItems = ({ return ( <> - {isSingleMode && ( + {isSingleMode && selectable && ( )} {isSingleMode && ( diff --git a/src/modules/chapter/components/cards/ChapterCard.tsx b/src/modules/chapter/components/cards/ChapterCard.tsx index 2031773f..9d84dbe4 100644 --- a/src/modules/chapter/components/cards/ChapterCard.tsx +++ b/src/modules/chapter/components/cards/ChapterCard.tsx @@ -50,12 +50,15 @@ type TChapter = ChapterIdInfo & Pick; interface IProps { + mode?: 'manga.page' | 'reader'; chapter: TChapter; allChapters: TChapter[]; downloadChapter: ChapterDownloadStatus | undefined; showChapterNumber: boolean; onSelect: (selected: boolean, isShiftKey?: boolean) => void; selected: boolean | null; + selectable?: boolean; + isActiveChapter?: boolean; // reader } export const ChapterCard: React.FC = (props: IProps) => { @@ -64,7 +67,17 @@ export const ChapterCard: React.FC = (props: IProps) => { const menuButtonRef = useRef(null); - const { chapter, allChapters, downloadChapter: dc, showChapterNumber, onSelect, selected } = props; + const { + mode = 'manga.page', + chapter, + allChapters, + downloadChapter: dc, + showChapterNumber, + onSelect, + selected, + selectable = true, + isActiveChapter = false, + } = props; const isSelecting = selected !== null; const { isDownloaded } = chapter; @@ -98,102 +111,115 @@ export const ChapterCard: React.FC = (props: IProps) => { }); return ( -
  • - - {(popupState) => ( - - - + {(popupState) => ( + + + handleClick(e)} + {...longPressBind(popupState.open)} + > + handleClick(e)} - {...longPressBind(popupState.open)} > - - - {chapter.isBookmarked && } - - {showChapterNumber - ? `${t('chapter.title_one')} ${chapter.chapterNumber}` - : chapter.name} - - - {chapter.scanlator} - - {getDateString(Number(chapter.uploadDate ?? 0), true)} - {isDownloaded && ` • ${t('chapter.status.label.downloaded')}`} - - - - {dc && } - - - {selected === null ? ( - - handleClickOpenMenu(e, popupState.open)} - onTouchStart={(e) => handleClickOpenMenu(e, popupState.open)} - aria-label="more" - size="large" - > - - - - ) : ( - - - + {chapter.isBookmarked && ( + )} + + {showChapterNumber + ? `${t('chapter.title_one')} ${chapter.chapterNumber}` + : chapter.name} + - - - - {!isSelecting && popupState.isOpen && ( - - {(onClose) => ( - onSelect(true)} - canBeDownloaded={ChaptersWithMeta.isDownloadable({ - chapter, - downloadChapter: dc, - })} - /> - )} - - )} - - )} - -
  • + {chapter.scanlator} + + {getDateString(Number(chapter.uploadDate ?? 0), true)} + {isDownloaded && ` • ${t('chapter.status.label.downloaded')}`} + + + + {dc && } + + + {selected === null ? ( + + handleClickOpenMenu(e, popupState.open)} + onTouchStart={(e) => handleClickOpenMenu(e, popupState.open)} + aria-label="more" + size="large" + > + + + + ) : ( + + + + )} + + + + + {!isSelecting && popupState.isOpen && ( + + {(onClose) => ( + onSelect(true)} + canBeDownloaded={ChaptersWithMeta.isDownloadable({ + chapter, + downloadChapter: dc, + })} + selectable={selectable} + /> + )} + + )} + + )} + ); }; diff --git a/src/modules/chapter/services/Chapters.ts b/src/modules/chapter/services/Chapters.ts index 795eb04a..d3a31252 100644 --- a/src/modules/chapter/services/Chapters.ts +++ b/src/modules/chapter/services/Chapters.ts @@ -117,7 +117,7 @@ export class Chapters { } static getReaderUrl(chapter: Chapter): string { - return `manga/${chapter.mangaId}/chapter/${chapter.sourceOrder}`; + return `/manga/${chapter.mangaId}/chapter/${chapter.sourceOrder}`; } static isDownloading(id: number): boolean { diff --git a/src/modules/core/Core.types.ts b/src/modules/core/Core.types.ts index b3d4b792..26b1bb64 100644 --- a/src/modules/core/Core.types.ts +++ b/src/modules/core/Core.types.ts @@ -6,8 +6,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { ReactNode } from 'react'; +import { TranslationKey } from '@/Base.types.ts'; + export enum GridLayout { Compact = 0, Comfortable = 1, List = 2, } + +export type ValueToDisplayData = Record< + Value, + { + title: TranslationKey; + icon: ReactNode; + } +>; diff --git a/src/modules/core/components/buttons/ButtonSelect.tsx b/src/modules/core/components/buttons/ButtonSelect.tsx new file mode 100644 index 00000000..60148c1f --- /dev/null +++ b/src/modules/core/components/buttons/ButtonSelect.tsx @@ -0,0 +1,41 @@ +/* + * 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 Stack from '@mui/material/Stack'; +import { useTranslation } from 'react-i18next'; +import Button from '@mui/material/Button'; +import { ValueToDisplayData } from '@/modules/core/Core.types.ts'; + +export const ButtonSelect = ({ + value, + values, + setValue, + valueToDisplayData, +}: { + value: Value; + values: Value[]; + setValue: (value: Value) => void; + valueToDisplayData: ValueToDisplayData; +}) => { + const { t } = useTranslation(); + + return ( + + {values.map((displayValue) => ( + + ))} + + ); +}; diff --git a/src/modules/core/components/inputs/ButtonSelectInput.tsx b/src/modules/core/components/inputs/ButtonSelectInput.tsx new file mode 100644 index 00000000..4517817b --- /dev/null +++ b/src/modules/core/components/inputs/ButtonSelectInput.tsx @@ -0,0 +1,23 @@ +/* + * 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 Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import { ComponentProps } from 'react'; + +import { ButtonSelect } from '@/modules/core/components/buttons/ButtonSelect.tsx'; + +export const ButtonSelectInput = ({ + label, + ...buttonSelectProps +}: ComponentProps> & { label: string }) => ( + + {label} + + +); diff --git a/src/modules/metadata/Metadata.constants.ts b/src/modules/metadata/Metadata.constants.ts index cd410c49..dfa937fb 100644 --- a/src/modules/metadata/Metadata.constants.ts +++ b/src/modules/metadata/Metadata.constants.ts @@ -7,6 +7,7 @@ */ import { AppMetadataKeys, IMetadataMigration } from '@/modules/metadata/Metadata.types.ts'; +import { ReadingMode } from '@/modules/reader/types/Reader.types.ts'; export const APP_METADATA_KEY_PREFIX = 'webUI_'; @@ -60,6 +61,7 @@ const APP_METADATA_OBJECT: Record = { progressBarType: undefined, progressBarSize: undefined, progressBarPosition: undefined, + readingMode: undefined, }; export const VALID_APP_METADATA_KEYS = Object.keys(APP_METADATA_OBJECT); @@ -121,6 +123,7 @@ export const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [ 'customThemes', 'mangaThumbnailBackdrop', ]; + /** * Once all changes have been done in the current branch, a new migration for all changes should be * created. @@ -184,4 +187,56 @@ export const METADATA_MIGRATIONS: IMetadataMigration[] = [ { keys: [{ oldKey: 'deleteChaptersAutoMarkedRead', newKey: 'deleteChaptersWhileReading' }], }, + { + keys: [ + { + oldKey: 'readerType', + newKey: 'readingMode', + }, + ], + values: [ + // START: readerType + { + key: 'readerType', + oldValue: 'ContinuesVertical', + newValue: `${ReadingMode.CONTINUOUS_VERTICAL}`, + }, + { + key: 'readerType', + oldValue: 'Webtoon', + newValue: `${ReadingMode.CONTINUOUS_VERTICAL}`, + }, + { + key: 'readerType', + oldValue: 'SingleRTL', + newValue: `${ReadingMode.SINGLE_PAGE}`, + }, + { + key: 'readerType', + oldValue: 'SingleLTR', + newValue: `${ReadingMode.SINGLE_PAGE}`, + }, + { + key: 'readerType', + oldValue: 'DoubleRTL', + newValue: `${ReadingMode.DOUBLE_PAGE}`, + }, + { + key: 'readerType', + oldValue: 'DoubleLTR', + newValue: `${ReadingMode.DOUBLE_PAGE}`, + }, + { + key: 'readerType', + oldValue: 'ContinuesHorizontalLTR', + newValue: `${ReadingMode.CONTINUOUS_HORIZONTAL}`, + }, + { + key: 'readerType', + oldValue: 'ContinuesHorizontalRTL', + newValue: `${ReadingMode.CONTINUOUS_HORIZONTAL}`, + }, + // END: readerType + ], + }, ]; diff --git a/src/modules/reader/components/overlay/navigation/ReaderChapterList.tsx b/src/modules/reader/components/overlay/navigation/ReaderChapterList.tsx new file mode 100644 index 00000000..405f88db --- /dev/null +++ b/src/modules/reader/components/overlay/navigation/ReaderChapterList.tsx @@ -0,0 +1,69 @@ +/* + * 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 { Virtuoso } from 'react-virtuoso'; +import { useMemo } from 'react'; +import { IChapterWithMeta } from '@/modules/chapter/components/ChapterList.tsx'; +import { ChapterCard } from '@/modules/chapter/components/cards/ChapterCard.tsx'; +import { requestManager } from '@/lib/requests/RequestManager.ts'; +import { ReaderStateChapters } from '@/modules/reader/types/Reader.types.ts'; + +export const ReaderChapterList = ({ + currentChapter, + chapters, +}: Required>) => { + const { data: downloaderData } = requestManager.useGetDownloadStatus(); + const queue = downloaderData?.downloadStatus.queue ?? []; + + const currentChapterIndex = useMemo( + () => currentChapter && chapters.findIndex((chapter) => chapter.id === currentChapter.id), + [currentChapter, chapters], + ); + + const chaptersWithMeta: IChapterWithMeta[] = useMemo( + () => + chapters.map((chapter) => { + const downloadChapter = queue?.find((cd) => cd.chapter.id === chapter.id); + + return { + chapter, + downloadChapter, + selected: null, + }; + }), + [queue, chapters], + ); + + return ( + chaptersWithMeta[index].chapter.id} + itemContent={(index) => ( + undefined} + selectable={false} + isActiveChapter={index === currentChapterIndex} + /> + )} + increaseViewportBy={400} + /> + ); +}; diff --git a/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx b/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx new file mode 100644 index 00000000..01eb1c3c --- /dev/null +++ b/src/modules/reader/components/overlay/navigation/mobile/ReaderBottomBarMobile.tsx @@ -0,0 +1,106 @@ +/* + * 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 SettingsIcon from '@mui/icons-material/Settings'; +import Stack from '@mui/material/Stack'; +import AppSettingsAltIcon from '@mui/icons-material/AppSettingsAlt'; +import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted'; +import { alpha } from '@mui/material/styles'; +import { bindDialog, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import Tooltip from '@mui/material/Tooltip'; +import { useTranslation } from 'react-i18next'; +import Slide from '@mui/material/Slide'; +import { useLayoutEffect } from 'react'; +import { ReaderBottomBarMobileProps } from '@/modules/reader/types/ReaderOverlay.types.ts'; +import { MobileReaderProgressBar } from '@/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx'; +import { ReaderChapterList } from '@/modules/reader/components/overlay/navigation/ReaderChapterList.tsx'; +import { ReaderBottomBarMobileQuickSettings } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx'; +import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; +import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; + +export const ReaderBottomBarMobile = ({ openSettings, isVisible }: ReaderBottomBarMobileProps) => { + 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' }); + + useLayoutEffect(() => { + chapterListPopupState.close(); + }, [currentChapter?.id]); + + return ( + <> + + + + alpha(theme.palette.background.paper, 0.95), + }} + > + + + + + + + + + + + + + + + + + + + + + {chapterListPopupState.isOpen && ( + + + + + + )} + {quickSettingsPopupState.isOpen && ( + + + + + + )} + + ); +}; diff --git a/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx new file mode 100644 index 00000000..30634956 --- /dev/null +++ b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileQuickSettings.tsx @@ -0,0 +1,45 @@ +/* + * 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 Stack from '@mui/material/Stack'; +import { useTranslation } from 'react-i18next'; +import { ReaderBottomBarMobileReadingMode } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx'; +import { ReaderBottomBarMobileReadingDirection } from '@/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx'; +import { createUpdateReaderSettings } from '@/modules/reader/services/ReaderSettingsMetadata.ts'; +import { makeToast } from '@/modules/core/utils/Toast.ts'; +import { IReaderSettings } from '@/modules/reader/types/Reader.types.ts'; +import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; +import { useReaderStateMangaContext } from '@/modules/reader/contexts/state/ReaderStateMangaContext.tsx'; + +export const ReaderBottomBarMobileQuickSettings = () => { + const { t } = useTranslation(); + + const { manga } = useReaderStateMangaContext(); + const { readingMode, readingDirection } = ReaderService.useSettings(); + + const updateReaderSettings = createUpdateReaderSettings< + keyof Pick + >(manga ?? { id: -1 }, () => makeToast(t('reader.settings.error.label.failed_to_save_settings'))); + + if (!manga) { + return null; + } + + return ( + + updateReaderSettings('readingMode', value)} + /> + updateReaderSettings('readingDirection', value)} + /> + + ); +}; diff --git a/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx new file mode 100644 index 00000000..8c840ff4 --- /dev/null +++ b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingDirection.tsx @@ -0,0 +1,46 @@ +/* + * 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 ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft'; +import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight'; +import { useTranslation } from 'react-i18next'; +import { IReaderSettings, ReadingDirection } from '@/modules/reader/types/Reader.types.ts'; +import { ValueToDisplayData } from '@/modules/core/Core.types.ts'; +import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx'; + +const VALUE_TO_DISPLAY_DATA: ValueToDisplayData = { + [ReadingDirection.LTR]: { + title: 'reader.settings.reading_direction.ltr', + icon: , + }, + [ReadingDirection.RTL]: { + title: 'reader.settings.reading_direction.rtl', + icon: , + }, +}; + +const READING_DIRECTION_VALUES = Object.values(ReadingDirection).filter((value) => typeof value === 'number'); + +export const ReaderBottomBarMobileReadingDirection = ({ + readingDirection, + setReadingDirection, +}: Pick & { + setReadingDirection: (readingDirection: ReadingDirection) => void; +}) => { + const { t } = useTranslation(); + + return ( + + ); +}; diff --git a/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx new file mode 100644 index 00000000..b785e5dc --- /dev/null +++ b/src/modules/reader/components/overlay/navigation/mobile/quick-settings/ReaderBottomBarMobileReadingMode.tsx @@ -0,0 +1,56 @@ +/* + * 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 { useTranslation } from 'react-i18next'; +import { IReaderSettings, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; +import { SinglePageIcon } from '@/assets/icons/svg/SinglePageIcon.tsx'; +import { DoublePageIcon } from '@/assets/icons/svg/DoublePageIcon.tsx'; +import { ContinuousVerticalPageIcon } from '@/assets/icons/svg/ContinuousVerticalPageIcon.tsx'; +import { ContinuousHorizontalPageIcon } from '@/assets/icons/svg/ContinuousHorizontalPageIcon.tsx'; +import { ValueToDisplayData } from '@/modules/core/Core.types.ts'; +import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx'; + +const VALUE_TO_DISPLAY_DATA: ValueToDisplayData = { + [ReadingMode.SINGLE_PAGE]: { + title: 'reader.settings.reader_type.label.single_page', + icon: , + }, + [ReadingMode.DOUBLE_PAGE]: { + title: 'reader.settings.reader_type.label.double_page', + icon: , + }, + [ReadingMode.CONTINUOUS_VERTICAL]: { + title: 'reader.settings.reader_type.label.continuous_vertical', + icon: , + }, + [ReadingMode.CONTINUOUS_HORIZONTAL]: { + title: 'reader.settings.reader_type.label.continuous_horizontal', + icon: , + }, +}; + +const READING_MODE_VALUES = Object.values(ReadingMode).filter((value) => typeof value === 'number'); + +export const ReaderBottomBarMobileReadingMode = ({ + readingMode, + setReadingMode, +}: Pick & { + setReadingMode: (mode: ReadingMode) => void; +}) => { + const { t } = useTranslation(); + + return ( + + ); +}; diff --git a/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx b/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx index 36e9c270..45aaecd2 100644 --- a/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx +++ b/src/modules/reader/components/overlay/progress-bar/variants/MobileReaderProgressBar.tsx @@ -18,7 +18,7 @@ import { ReaderProgressBarSlot } from '@/modules/reader/components/overlay/progr import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx'; import { useReaderStateChaptersContext } from '@/modules/reader/contexts/state/ReaderStateChaptersContext.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; -import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils'; +import { getPage } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; import { getOptionForDirection } from '@/theme.tsx'; import { ProgressBarPosition } from '@/modules/reader/types/Reader.types.ts'; import { ReaderProgressBarDirectionWrapper } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarDirectionWrapper.tsx'; diff --git a/src/modules/reader/constants/Reader.constants.ts b/src/modules/reader/constants/Reader.constants.ts index 3550c078..cc5ad1f6 100644 --- a/src/modules/reader/constants/Reader.constants.ts +++ b/src/modules/reader/constants/Reader.constants.ts @@ -11,6 +11,7 @@ import { ProgressBarPosition, ProgressBarType, ReadingDirection, + ReadingMode, } from '@/modules/reader/types/Reader.types.ts'; import { TapZoneLayouts } from '@/modules/reader/types/TapZoneLayout.types.ts'; @@ -29,4 +30,5 @@ export const DEFAULT_READER_SETTINGS: IReaderSettings = { progressBarSize: 4, progressBarPosition: ProgressBarPosition.BOTTOM, readingDirection: ReadingDirection.LTR, + readingMode: ReadingMode.SINGLE_PAGE, }; diff --git a/src/modules/reader/types/Reader.types.ts b/src/modules/reader/types/Reader.types.ts index 82953e5b..cc153611 100644 --- a/src/modules/reader/types/Reader.types.ts +++ b/src/modules/reader/types/Reader.types.ts @@ -25,6 +25,13 @@ export enum ReadingDirection { RTL, } +export enum ReadingMode { + SINGLE_PAGE, + DOUBLE_PAGE, + CONTINUOUS_VERTICAL, + CONTINUOUS_HORIZONTAL, +} + export interface ReaderStateChapters { chapters: TChapterReader[]; currentChapter?: TChapterReader | null; @@ -48,4 +55,5 @@ export interface IReaderSettings { progressBarSize: number; progressBarPosition: ProgressBarPosition; readingDirection: ReadingDirection; + readingMode: ReadingMode; } diff --git a/src/modules/reader/types/ReaderOverlay.types.ts b/src/modules/reader/types/ReaderOverlay.types.ts index 47a91fae..5707d3d6 100644 --- a/src/modules/reader/types/ReaderOverlay.types.ts +++ b/src/modules/reader/types/ReaderOverlay.types.ts @@ -6,6 +6,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -export interface MobileHeaderProps { +export interface BaseReaderOverlayProps { isVisible: boolean; } + +export interface MobileHeaderProps extends BaseReaderOverlayProps {} + +export interface ReaderBottomBarMobileProps extends BaseReaderOverlayProps { + openSettings: () => void; +}