diff --git a/src/pages/viewer/components/BookViewer.tsx b/src/pages/viewer/components/BookViewer.tsx index 723a562..682c67a 100644 --- a/src/pages/viewer/components/BookViewer.tsx +++ b/src/pages/viewer/components/BookViewer.tsx @@ -1,4 +1,4 @@ -import { type FC, useRef, useCallback, useState, useEffect, useMemo } from 'react'; +import { type FC, useRef, useCallback, useState, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from 'react'; import HTMLFlipBook from 'react-pageflip'; import { type PageData } from '../types'; import BookPage from './BookPage'; @@ -6,6 +6,22 @@ import { ArrowLeft2, ArrowRight2 } from 'iconsax-react'; import { getPaperDimensions } from '@/config/paperSizes'; const VIEWER_SCALE = 0.5; +/** هم‌راستا با `md` تیلویند (768px) — موبایل: زیر این عرض */ +const MOBILE_QUERY = '(min-width: 768px)'; + +function subscribeDesktopMql(callback: () => void) { + const mq = window.matchMedia(MOBILE_QUERY); + mq.addEventListener('change', callback); + return () => mq.removeEventListener('change', callback); +} + +function useIsDesktop() { + return useSyncExternalStore( + subscribeDesktopMql, + () => window.matchMedia(MOBILE_QUERY).matches, + () => true + ); +} export type BookViewerProps = { pages: PageData[]; @@ -34,6 +50,7 @@ interface FlipEvent { const BookViewer: FC = ({ pages, catalogSize }) => { const bookRef = useRef(null); const [currentPage, setCurrentPage] = useState(0); + const isDesktop = useIsDesktop(); // سایز کتاب بر اساس catalogSize (A3/A4/A5) const { width: bookWidth, height: bookHeight } = useMemo( @@ -43,8 +60,47 @@ const BookViewer: FC = ({ pages, catalogSize }) => { const displayWidth = Math.round(bookWidth * VIEWER_SCALE); const displayHeight = Math.round(bookHeight * VIEWER_SCALE); - // scale محتوا همان VIEWER_SCALE است چون سایز کتاب با سایز محتوا یکی است - const contentScale = VIEWER_SCALE; + // موبایل: از ابعاد viewport برای جا دادن کتاب در صفحه + const [viewport, setViewport] = useState(() => + typeof window !== 'undefined' + ? { w: window.innerWidth, h: window.innerHeight } + : { w: 1200, h: 800 } + ); + useLayoutEffect(() => { + const onResize = () => setViewport({ w: window.innerWidth, h: window.innerHeight }); + onResize(); + window.addEventListener('resize', onResize); + window.addEventListener('orientationchange', onResize); + return () => { + window.removeEventListener('resize', onResize); + window.removeEventListener('orientationchange', onResize); + }; + }, []); + + const mobileFit = useMemo(() => { + if (isDesktop) return null; + const padX = 16; + const reservedNav = 120; + const maxW = Math.max(1, viewport.w - padX); + const maxH = Math.max(1, viewport.h - reservedNav); + const baseW = bookWidth * VIEWER_SCALE; + const baseH = bookHeight * VIEWER_SCALE; + const scale = Math.min(1, maxW / baseW, maxH / baseH); + return { + w: Math.max(1, Math.round(baseW * scale)), + h: Math.max(1, Math.round(baseH * scale)), + contentScale: VIEWER_SCALE * scale, + }; + }, [isDesktop, bookWidth, bookHeight, viewport.w, viewport.h]); + + // دسکتاپ: دقیقاً مثل قبل — موبایل: مقیاس‌شده + const pagePixelWidth = isDesktop ? displayWidth : (mobileFit?.w ?? displayWidth); + const pagePixelHeight = isDesktop ? displayHeight : (mobileFit?.h ?? displayHeight); + const contentScale = isDesktop ? VIEWER_SCALE : (mobileFit?.contentScale ?? VIEWER_SCALE); + + const flipKey = isDesktop + ? 'desktop' + : `mobile-${pagePixelWidth}-${pagePixelHeight}`; // اطمینان از اینکه currentPage همیشه در محدوده معتبر است useEffect(() => { @@ -157,42 +213,53 @@ const BookViewer: FC = ({ pages, catalogSize }) => { } }, [currentPage, pages.length, goToNextPage, goToPrevPage]); + const usePortrait = !isDesktop; + return ( -
- {/* نمایش کتاب */} -
- {/* Border و Shadow عمودی در وسط کتاب */} +
+
+ {/* Border و Shadow عمودی در وسط (فقط دوسوّره/دسکتاپ) */}
-
+
= ({ pages, catalogSize }) => { key={page.id} page={page} scale={contentScale} - pageWidth={displayWidth} - pageHeight={displayHeight} + pageWidth={pagePixelWidth} + pageHeight={pagePixelHeight} onLinkClick={handleLinkClick} /> ))} @@ -212,13 +279,13 @@ const BookViewer: FC = ({ pages, catalogSize }) => {
- {/* کنترل‌های ناوبری */} -
+
{/* دکمه صفحه بعد (در RTL، صفحه سمت چپ) */}