@@ -5,12 +5,15 @@ import { useBookEntranceController } from "@/pages/viewer/hooks/useBookEntranceC
|
||||
import { useIsBrowserFullscreen } from "@/pages/viewer/hooks/useIsBrowserFullscreen";
|
||||
import { useViewerViewport } from "@/pages/viewer/hooks/useViewerViewport";
|
||||
import { isLegacyIOSSafari } from "@/pages/viewer/utils/isLegacyIOSSafari";
|
||||
import { resolvePageBackground } from "@/pages/viewer/utils/pageBackground";
|
||||
import { toggleViewerFullscreen } from "@/pages/viewer/utils/viewerFullscreen";
|
||||
import { ArrowLeft2, ArrowRight2, Maximize4, Pause, Play, RowVertical } from "iconsax-react";
|
||||
import { type FC, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
||||
import HTMLFlipBook from "react-pageflip";
|
||||
import { type PageData } from "../types";
|
||||
import BookPage from "./BookPage";
|
||||
import Magnifier from "./Magnifier/Magnifier";
|
||||
import ZoomButton from "./Toolbar/ZoomButton";
|
||||
|
||||
/** فاصله پیشفرض بین هر ورق در پخش خودکار (میلیثانیه) */
|
||||
const AUTO_PLAY_INTERVAL_MS = 3000;
|
||||
@@ -118,10 +121,13 @@ function triggerFlipPrev(api: PageFlipWithFlipController | undefined) {
|
||||
*/
|
||||
const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings }) => {
|
||||
const bookRef = useRef<FlipBookInstance | null>(null);
|
||||
const bookAreaRef = useRef<HTMLDivElement | null>(null);
|
||||
const flipbookWrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const pageFlipHandlerRef = useRef<(e: FlipEvent) => void>(() => {});
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const [isAutoPlayActive, setIsAutoPlayActive] = useState(false);
|
||||
const [startPage, setStartPage] = useState(0);
|
||||
const [magnifierEnabled, setMagnifierEnabled] = useState(false);
|
||||
const isDesktop = useIsDesktop();
|
||||
const legacyIOS = useLegacyIOSSafari();
|
||||
const isBrowserFullscreen = useIsBrowserFullscreen();
|
||||
@@ -139,6 +145,10 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
|
||||
const backgroundColor = documentSettings?.backgroundColor ?? "#ffffff";
|
||||
const backgroundGradient = documentSettings?.backgroundGradient;
|
||||
const backgroundImageUrl = documentSettings?.backgroundImageUrl ?? "";
|
||||
const pageBackgroundDefaults = useMemo(
|
||||
() => ({ backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl }),
|
||||
[backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsAutoPlayActive(autoPlay);
|
||||
@@ -359,6 +369,21 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
|
||||
setIsAutoPlayActive((prev) => !prev);
|
||||
}, [pages.length]);
|
||||
|
||||
const toggleMagnifier = useCallback(() => {
|
||||
setMagnifierEnabled((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!magnifierEnabled) return;
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
setMagnifierEnabled(false);
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [magnifierEnabled]);
|
||||
|
||||
const navigateToPageIndex = useCallback(
|
||||
(targetIndex: number) => {
|
||||
const pageFlipAPI = bookRef.current?.pageFlip();
|
||||
@@ -441,7 +466,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4 md:gap-6 w-full h-full max-md:min-h-0 max-md:max-w-full max-md:overflow-x-hidden max-md:pb-[env(safe-area-inset-bottom,0px)]" dir="rtl">
|
||||
<div className="relative w-full flex flex-col flex-1 max-md:min-h-0 pt-8 md:pt-12 min-h-0 overflow-hidden">
|
||||
<div className="relative flex flex-1 min-h-0 w-full items-center justify-center">
|
||||
<div ref={bookAreaRef} className="relative flex flex-1 min-h-0 w-full items-center justify-center">
|
||||
{/* Border و Shadow عمودی در وسط (فقط حالت دوصفحهای دسکتاپ) */}
|
||||
{!usePortrait && (
|
||||
<div
|
||||
@@ -456,9 +481,11 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
|
||||
ایندکس/onFlip با دکمهها میشود. محتوای فارسی داخل BookPage با dir=rtl است.
|
||||
*/}
|
||||
<div
|
||||
ref={flipbookWrapperRef}
|
||||
dir="ltr"
|
||||
className="max-w-full flex justify-center shrink-0 rounded-sm"
|
||||
style={{
|
||||
cursor: magnifierEnabled ? "zoom-in" : undefined,
|
||||
overflow: "hidden",
|
||||
width: usePortrait ? pagePixelWidth : pagePixelWidth * 2,
|
||||
height: pagePixelHeight,
|
||||
@@ -484,35 +511,54 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
|
||||
showCover={hasMultiplePages}
|
||||
mobileScrollSupport={!usePortrait}
|
||||
clickEventForward={true}
|
||||
useMouseEvents={hasMultiplePages}
|
||||
useMouseEvents={hasMultiplePages && !magnifierEnabled}
|
||||
swipeDistance={hasMultiplePages ? (usePortrait ? 48 : 30) : 9999}
|
||||
startZIndex={1}
|
||||
showPageCorners={hasMultiplePages}
|
||||
disableFlipByClick={true}
|
||||
className={clx("flipbook-container mx-auto max-w-full", legacyIOS && "flipbook-legacy-ios")}
|
||||
className={clx(
|
||||
"flipbook-container mx-auto max-w-full",
|
||||
legacyIOS && "flipbook-legacy-ios",
|
||||
magnifierEnabled && "flipbook-magnifier-active",
|
||||
)}
|
||||
style={{}}
|
||||
onFlip={onFlipForwarded}
|
||||
onInit={handleBookInit}
|
||||
onChangeState={handleChangeState}
|
||||
>
|
||||
{displayPages.map((page) => (
|
||||
<BookPage
|
||||
key={page.id}
|
||||
page={page}
|
||||
entrancePhase={getEntrancePhase(page.id)}
|
||||
scale={contentScale}
|
||||
pageWidth={pagePixelWidth}
|
||||
pageHeight={pagePixelHeight}
|
||||
onLinkClick={handleLinkClick}
|
||||
backgroundType={page.backgroundType ?? backgroundType}
|
||||
backgroundColor={page.backgroundColor ?? backgroundColor}
|
||||
backgroundGradient={page.backgroundGradient ?? backgroundGradient}
|
||||
backgroundImageUrl={page.backgroundImageUrl ?? backgroundImageUrl}
|
||||
useHardPage={legacyIOS}
|
||||
/>
|
||||
))}
|
||||
{displayPages.map((page) => {
|
||||
const background = resolvePageBackground(page, pageBackgroundDefaults);
|
||||
return (
|
||||
<BookPage
|
||||
key={page.id}
|
||||
page={page}
|
||||
entrancePhase={getEntrancePhase(page.id)}
|
||||
scale={contentScale}
|
||||
pageWidth={pagePixelWidth}
|
||||
pageHeight={pagePixelHeight}
|
||||
onLinkClick={handleLinkClick}
|
||||
backgroundType={background.backgroundType}
|
||||
backgroundColor={background.backgroundColor}
|
||||
backgroundGradient={background.backgroundGradient}
|
||||
backgroundImageUrl={background.backgroundImageUrl}
|
||||
useHardPage={legacyIOS}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</HTMLFlipBook>
|
||||
</div>
|
||||
<Magnifier
|
||||
enabled={magnifierEnabled}
|
||||
containerRef={bookAreaRef}
|
||||
interactionRef={flipbookWrapperRef}
|
||||
pages={displayPages}
|
||||
pageWidth={pagePixelWidth}
|
||||
pageHeight={pagePixelHeight}
|
||||
contentScale={contentScale}
|
||||
pageDefaults={pageBackgroundDefaults}
|
||||
useHardPage={legacyIOS}
|
||||
pageIndex={currentPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -570,6 +616,8 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
|
||||
|
||||
{hasMultiplePages && autoPlay && <AutoPlayToggleButton isActive={isAutoPlayActive} onToggle={toggleAutoPlay} />}
|
||||
|
||||
<ZoomButton isActive={magnifierEnabled} onToggle={toggleMagnifier} />
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleViewerFullscreen}
|
||||
|
||||
Reference in New Issue
Block a user