/* * 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 { BoxProps } from '@mui/material/Box'; import { ComponentProps, memo, ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import ClickAwayListener from '@mui/material/ClickAwayListener'; import { TypographyProps } from '@mui/material/Typography'; import { StackProps } from '@mui/material/Stack'; import { ReaderProgressBarProps, TReaderProgressBarContext } from '@/modules/reader/types/ReaderProgressBar.types.ts'; import { ReaderProgressBarPageNumber } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarPageNumber.tsx'; import { ReaderProgressBarContainer } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarContainer.tsx'; import { ReaderProgressBarRoot } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarRoot.tsx'; import { ReaderProgressBarSlotsContainer } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarSlotsContainer.tsx'; import { ProgressBarHighlightReadPages } from '@/modules/reader/components/overlay/progress-bar/ProgressBarHighlightReadPages.tsx'; import { ReaderProgressBarCurrentPageSlot } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarCurrentPageSlot.tsx'; import { getNextIndexFromPage, getPage, getPageForMousePos, getProgressBarPositionInfo, } from '@/modules/reader/utils/ReaderProgressBar.utils.tsx'; import { getOptionForDirection as getOptionForDirectionImpl } from '@/modules/theme/services/ThemeCreator.ts'; import { ReaderProgressBarSlotsActionArea } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarSlotsActionArea.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { ReaderControls } from '@/modules/reader/services/ReaderControls.ts'; import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; import { useReaderProgressBarContext } from '@/modules/reader/contexts/ReaderProgressBarContext.tsx'; import { ReaderProgressBarSlotWrapper } from '@/modules/reader/components/overlay/progress-bar/ReaderProgressBarSlotWrapper.tsx'; import { userReaderStatePagesContext } from '@/modules/reader/contexts/state/ReaderStatePagesContext.tsx'; import { useResizeObserver } from '@/modules/core/hooks/useResizeObserver.tsx'; import { IReaderSettings, ReadingMode } from '@/modules/reader/types/Reader.types.ts'; const BaseReaderProgressBar = ({ totalPages, pages, pageLoadStates, currentPageIndex, slotProps, slots, createProgressBarSlot, progressBarPosition, readingMode, isDragging, setIsDragging, openPage, direction, fullSegmentClicks, }: ReaderProgressBarProps & Pick & Pick & Pick, 'createProgressBarSlot'> & { slotProps?: { container?: StackProps; progressBarRoot?: StackProps; progressBarSlotsActionArea?: StackProps; progressBarSlotsContainer?: StackProps; progressBarSlot?: BoxProps; progressBarReadPages?: BoxProps; progressBarCurrentPageSlot?: BoxProps; progressBarPageTexts?: { base?: TypographyProps; current?: TypographyProps; total?: TypographyProps; }; }; slots?: { progressBarCurrentPage?: ReactNode; }; openPage: ReturnType; direction: ReturnType; fullSegmentClicks: boolean; }) => { const progressBarRef = useRef(null); const draggingDetectionTimeout = useRef(); const [totalPagesTextWidth, setTotalPagesTextWidth] = useState(0); const totalPagesTextRef = useRef(null); useResizeObserver( totalPagesTextRef, useCallback(() => { const element = totalPagesTextRef.current; if (!element) { return; } const { paddingLeft, paddingRight } = getComputedStyle(element); const PAGE_NAME_SEPARATOR_LENGTH = 6; // - const widthOfTotalPageText = element.clientWidth - parseFloat(paddingLeft) - parseFloat(paddingRight); const newWidth = readingMode === ReadingMode.DOUBLE_PAGE ? widthOfTotalPageText * 2 + PAGE_NAME_SEPARATOR_LENGTH : widthOfTotalPageText; setTotalPagesTextWidth(newWidth); }, [readingMode]), ); const currentPagesIndex = useMemo(() => getPage(currentPageIndex, pages).pagesIndex, [currentPageIndex, pages]); const isHorizontalPosition = getProgressBarPositionInfo(progressBarPosition).isHorizontal; const currentPage = useMemo(() => getPage(currentPageIndex, pages), [currentPageIndex, pages]); const getOptionForDirection = useCallback( (...args: Parameters>) => getOptionForDirectionImpl(args[0], args[1], direction), [direction], ); ReaderControls.useHandleProgressDragging( openPage, progressBarRef, isDragging, currentPage, pages, progressBarPosition, getOptionForDirection, fullSegmentClicks, ); const handleMouseDown = (e: React.MouseEvent | React.TouchEvent) => { if (!progressBarRef.current) { return; } const isTouchEvent = 'touches' in e; openPage( getNextIndexFromPage( getPageForMousePos( isTouchEvent ? e.touches[0] : e, progressBarRef.current.getBoundingClientRect(), pages, isHorizontalPosition, fullSegmentClicks, getOptionForDirection, ), ), undefined, false, ); clearTimeout(draggingDetectionTimeout.current); draggingDetectionTimeout.current = setTimeout(() => setIsDragging(true), 250); }; const handleMouseUp = () => { setIsDragging(false); clearTimeout(draggingDetectionTimeout.current); }; return ( openPage('previous', 'ltr', false)} > {currentPage.name} setIsDragging(false)}> {pages.map((page, pagesIndex) => ( currentPagesIndex} totalPages={pages.length} primaryPageLoadState={pageLoadStates[page.primary.index].loaded} secondaryPageLoadState={ page.secondary ? pageLoadStates[page.secondary.index].loaded : undefined } createProgressBarSlot={createProgressBarSlot} showDraggingStyle={pagesIndex === currentPagesIndex && isDragging} /> ))} {slots?.progressBarCurrentPage} openPage('next', 'ltr', false)} > {totalPages} ); }; export const ReaderProgressBar = withPropsFrom( memo(BaseReaderProgressBar), [ useReaderProgressBarContext, () => ({ openPage: ReaderControls.useOpenPage() }), () => ({ direction: ReaderService.useGetThemeDirection(), }), userReaderStatePagesContext, ReaderService.useSettingsWithoutDefaultFlag, ], [ 'isDragging', 'setIsDragging', 'openPage', 'direction', 'pages', 'pageLoadStates', 'totalPages', 'currentPageIndex', 'readingMode', ], );