/* * 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 { ComponentProps, memo, useCallback } from 'react'; import { SpinnerImage } from '@/modules/core/components/SpinnerImage.tsx'; import { ReaderService } from '@/modules/reader/services/ReaderService.ts'; import { IReaderSettings, ReaderCustomFilter, ReaderPagerProps, TReaderScrollbarContext, } from '@/modules/reader/types/Reader.types.ts'; import { getImageMarginStyling, getImagePlaceholderStyling, getReaderImageStyling, } from '@/modules/reader/utils/ReaderPager.utils.tsx'; import { applyStyles } from '@/modules/core/utils/ApplyStyles.ts'; import { useReaderScrollbarContext } from '@/modules/reader/contexts/ReaderScrollbarContext.tsx'; import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx'; import { withPropsFrom } from '@/modules/core/hoc/withPropsFrom.tsx'; import { useNavBarContext } from '@/modules/navigation-bar/contexts/NavbarContext.tsx'; import { NavbarContextType } from '@/modules/navigation-bar/NavigationBar.types.ts'; const getCustomFilterString = (customFilter: ReaderCustomFilter): string => Object.keys(customFilter) .map((key) => { const filter = key as keyof ReaderCustomFilter; const value = customFilter[filter]; switch (filter) { case 'brightness': case 'contrast': case 'saturate': return (value as ReaderCustomFilter['brightness' | 'contrast' | 'saturate']).enabled ? `${filter}(${(value as ReaderCustomFilter['brightness' | 'contrast' | 'saturate']).value / 100})` : ''; case 'hue': return (value as ReaderCustomFilter['hue']).enabled ? `hue-rotate(${(value as ReaderCustomFilter['brightness' | 'contrast' | 'saturate']).value}deg)` : ''; case 'rgba': return ''; case 'sepia': case 'grayscale': case 'invert': return value ? `${filter}(${Number(value)})` : ''; default: throw new Error(`Unexpected "CustomFilter" (${filter})`); } }) .join(' '); const BaseReaderPage = ({ pageIndex, pagesIndex, isPrimaryPage, display, doublePage = false, position, marginTop, shouldLoad, readingMode, customFilter, pageScaleMode, shouldStretchPage, readerWidth, scrollbarXSize, scrollbarYSize, onLoad, onError, setRef, readerNavBarWidth, ...props }: Omit, 'ref' | 'spinnerStyle' | 'imgStyle' | 'onLoad' | 'onError'> & Pick & Pick & Pick & { pageIndex: number; pagesIndex: number; isPrimaryPage: boolean; display: boolean; doublePage?: boolean; position?: 'left' | 'right'; marginTop?: number; onLoad: ReaderPagerProps['onLoad']; onError: ReaderPagerProps['onError']; setRef?: (pagesIndex: number, ref: HTMLElement | null) => void; }) => { const { src } = props; const isTabletWidth = MediaQuery.useIsTabletWidth(); const handleLoad = useCallback( () => onLoad?.(pagesIndex, src, isPrimaryPage), [onLoad, pagesIndex, src, isPrimaryPage], ); const handleError = useCallback(() => onError?.(pageIndex, src), [onError, pageIndex, src]); const updateRef = useCallback((element: HTMLElement | null) => setRef?.(pagesIndex, element), [pagesIndex, setRef]); if (!display && !shouldLoad) { return null; } return ( ); }; export const ReaderPage = withPropsFrom( memo(BaseReaderPage), [ReaderService.useSettingsWithoutDefaultFlag, useReaderScrollbarContext, useNavBarContext], [ 'readingMode', 'customFilter', 'pageScaleMode', 'shouldStretchPage', 'readerWidth', 'scrollbarXSize', 'scrollbarYSize', 'readerNavBarWidth', ], );