import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next"; import { Fragment, useCallback, useEffect, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import Slider from "rc-slider"; import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import { z } from "zod"; import { atom } from "jotai"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Enum_Componentmetadatabooks_Page_Order as PageOrder, GetLibraryItemScansQuery, UploadImageFragment, } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; import { sortRangedContent } from "helpers/others"; import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts"; import { getOpenGraph } from "helpers/openGraph"; import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; import { Img } from "components/Img"; import { getAssetFilename, ImageQuality } from "helpers/img"; import { cIf, cJoin } from "helpers/className"; import { clamp, isInteger } from "helpers/numbers"; import { SubPanel } from "components/Containers/SubPanel"; import { Button } from "components/Inputs/Button"; import { Ids } from "types/ids"; import { Switch } from "components/Inputs/Switch"; import { WithLabel } from "components/Inputs/WithLabel"; import { sendAnalytics } from "helpers/analytics"; import { ReturnButton } from "components/PanelComponents/ReturnButton"; import { ButtonGroup } from "components/Inputs/ButtonGroup"; import { Chip } from "components/Chip"; import { RecorderChip } from "components/RecorderChip"; import { ToolTip } from "components/ToolTip"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { TranslatedProps } from "types/TranslatedProps"; import { prettyInlineTitle, prettySlug } from "helpers/formatters"; import { useFullscreen } from "hooks/useFullscreen"; import { atoms } from "contexts/atoms"; import { useAtomGetter } from "helpers/atoms"; import { FilterSettings, useReaderSettings } from "hooks/useReaderSettings"; import { useTypedRouter } from "hooks/useTypedRouter"; import { useFormat } from "hooks/useFormat"; import { getFormat } from "helpers/i18n"; import { getScanArchiveURL } from "helpers/libraryItem"; type BookType = "book" | "manga"; type DisplayMode = "double" | "single"; /* * ╭─────────────╮ * ────────────────────────────────────────╯ CONSTANTS ╰────────────────────────────────────────── */ const CUSTOM_DARK_DROPSHADOW = ` drop-shadow(0 0 0.5em rgb(var(--theme-color-shade) / 30%)) drop-shadow(0 1em 1em rgb(var(--theme-color-shade) / 40%)) drop-shadow(0 2em 2em rgb(var(--theme-color-shade) / 60%)) drop-shadow(0 12em 12em rgb(var(--theme-color-shade) / 80%))`; const CUSTOM_LIGHT_DROPSHADOW = ` drop-shadow(0 0.3em 0.5em rgb(var(--theme-color-shade) / 70%)) drop-shadow(0 1em 1em rgb(var(--theme-color-shade) / 30%)) drop-shadow(0 2em 2em rgb(var(--theme-color-shade) / 30%)) drop-shadow(0 12em 12em rgb(var(--theme-color-shade) / 60%))`; const SIDEPAGES_PAGE_COUNT_ON_TEXTURE = 200; const SIDEPAGES_PAGE_WIDTH = 0.02; const queryParamSchema = z.object({ query: z.coerce.string().optional(), page: z.coerce.number().optional(), }); const isWebKitAtom = atom((get) => get(atoms.userAgent.engine) === "WebKit"); /* * ╭────────╮ * ──────────────────────────────────────────╯ PAGE ╰───────────────────────────────────────────── */ interface Props extends AppLayoutRequired { item: NonNullable< NonNullable["data"][number]["attributes"] >; pages: UploadImageFragment[]; pageOrder: PageOrder; bookType: BookType; pageWidth: number; pageRatio: string; itemSlug: string; } const LibrarySlug = ({ pages, bookType, pageOrder, pageWidth, itemSlug, item, ...otherProps }: Props): JSX.Element => { const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout); const { format } = useFormat(); const isDarkMode = useAtomGetter(atoms.settings.darkMode); const { filterSettings, isSidePagesEnabled, pageQuality, toggleBookFold, toggleLighting, togglePaperTexture, toggleDropShadow, toggleIsSidePagesEnabled, setPageQuality, setTeint, resetReaderSettings, } = useReaderSettings(); const [currentPageIndex, setCurrentPageIndex] = useState(0); const [currentZoom, setCurrentZoom] = useState(1); const [isGalleryMode, setIsGalleryMode] = useState(false); const [displayMode, setDisplayMode] = useState( is1ColumnLayout ? "single" : "double" ); const router = useTypedRouter(queryParamSchema); const isWebKit = useAtomGetter(isWebKitAtom); const { isFullscreen, toggleFullscreen, requestFullscreen } = useFullscreen(Ids.ContentPanel); const effectiveDisplayMode = currentPageIndex === 0 || currentPageIndex === pages.length - 1 ? "single" : displayMode; const ajustedSidepagesTotalWidth = pages.length * SIDEPAGES_PAGE_WIDTH * (120 / pageWidth); const changeCurrentPageIndex = useCallback( (callbackFn: (current: number) => number) => { setCurrentPageIndex((current) => clamp(callbackFn(current), 0, pages.length - 1)); }, [pages.length] ); useEffect(() => setDisplayMode(is1ColumnLayout ? "single" : "double"), [is1ColumnLayout]); useEffect(() => { if (router.isReady) router.updateQuery({ page: currentPageIndex - 1, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentPageIndex, router.isReady]); useEffect(() => { if (router.isReady) { if (isDefined(router.query.page)) setCurrentPageIndex(router.query.page + 1); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.isReady]); const changeDisplayMode = useCallback( (newDisplayMode: DisplayMode) => { setDisplayMode(newDisplayMode); changeCurrentPageIndex((current) => current); }, [changeCurrentPageIndex] ); const handlePageNavigation = useCallback( (navigateTo: "left" | "next" | "previous" | "right") => { if ( (navigateTo === "right" && pageOrder === PageOrder.LeftToRight) || (navigateTo === "left" && pageOrder === PageOrder.RightToLeft) || navigateTo === "next" ) { changeCurrentPageIndex((current) => current + (effectiveDisplayMode === "single" ? 1 : 2)); } else { changeCurrentPageIndex((current) => current - (effectiveDisplayMode === "single" ? 1 : 2)); } }, [changeCurrentPageIndex, effectiveDisplayMode, pageOrder] ); useHotkeys("left", () => handlePageNavigation("left"), { enabled: !isGalleryMode }, [ handlePageNavigation, ]); useHotkeys("up", () => setIsGalleryMode(true), { enabled: !isGalleryMode }, [setIsGalleryMode]); useHotkeys("down", () => setIsGalleryMode(false), { enabled: isGalleryMode }, [setIsGalleryMode]); useHotkeys("f", () => requestFullscreen(), { enabled: !isFullscreen }, [requestFullscreen]); useHotkeys("right", () => handlePageNavigation("right"), { enabled: !isGalleryMode }, [ handlePageNavigation, ]); const firstPage = pages[ effectiveDisplayMode === "double" && currentPageIndex % 2 === 0 ? currentPageIndex - 1 : currentPageIndex ]; const secondPage = pages[ effectiveDisplayMode === "double" && currentPageIndex % 2 === 0 ? currentPageIndex : currentPageIndex + 1 ]; const leftSidePagesCount = pageOrder === PageOrder.LeftToRight ? currentPageIndex : pages.length - 1 - currentPageIndex; const rightSidePagesCount = pageOrder === PageOrder.LeftToRight ? pages.length - 1 - currentPageIndex : currentPageIndex; const leftSidePagesWidth = `${ pageOrder === PageOrder.LeftToRight ? (currentPageIndex / pages.length) * ajustedSidepagesTotalWidth : ajustedSidepagesTotalWidth - (currentPageIndex / pages.length) * ajustedSidepagesTotalWidth }vmin`; const rightSidePagesWidth = `${ pageOrder === PageOrder.LeftToRight ? ajustedSidepagesTotalWidth - (currentPageIndex / pages.length) * ajustedSidepagesTotalWidth : (currentPageIndex / pages.length) * ajustedSidepagesTotalWidth }vmin`; const leftSideClipPath = `polygon( ${ isSidePagesEnabled ? ` ${leftSidePagesWidth} 100%, 0% calc(100% - ${leftSidePagesWidth} / 3), 0% calc(${leftSidePagesWidth} / 3), ${leftSidePagesWidth} 0%,` : "0% 100%, 0% 0%," } 70% 0%, ${ filterSettings.bookFold ? `90% .25%, 95% .5%, 98% .8%, 99% 1%, 101% 2%, 101% 98%, 99% 99%, 98% 99.2%, 95% 99.5%, 90% 99.75%,` : "101% 0%, 101% 100%," } 70% 100% )`; const rightSideClipPath = `polygon( ${ isSidePagesEnabled ? `calc(100% - ${rightSidePagesWidth}) 0%, 100% calc(${rightSidePagesWidth} / 3), 100% calc(100% - ${rightSidePagesWidth} / 3), calc(100% - ${rightSidePagesWidth}) 100%,` : "100% 0%, 100% 100%," } 30% 100%, ${ filterSettings.bookFold ? `10% 99.75%, 5% 99.5%, 2% 99.2%, 1% 99%, -1% 98%, -1% 2%, 1% 1%, 2% .8%, 5% .5%, 10% .25%,` : "-1% 100%, -1% 0%," } 30% 0% )`; const pageHeight = `calc(100vh - ${is1ColumnLayout ? 5 : 4}rem - 3rem)`; const subPanel = (
{!isWebKit && ( )}

{format("night_reader")}:

{ const value = (Array.isArray(event) ? event[0] : event) ?? 0; setTeint(value / 10); }} />

{format("reading_layout")}:

changeDisplayMode("single"), }, { icon: "auto_stories", tooltip: format("double_page_view"), active: displayMode === "double", onClick: () => changeDisplayMode("double"), }, ]} />

{format("quality")}:

setPageQuality(ImageQuality.Medium), }, { text: "HD", active: pageQuality === ImageQuality.Large, onClick: () => setPageQuality(ImageQuality.Large), }, ]} />