book viewer full screen

This commit is contained in:
Hamid
2026-05-17 01:50:20 -07:00
parent 642155661c
commit 4390937b19
9 changed files with 164 additions and 55 deletions
+46 -43
View File
@@ -1,16 +1,22 @@
import { type FC, useRef, useCallback, useState, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from 'react';
import { type FC, useRef, useCallback, useState, useEffect, useMemo, useSyncExternalStore } from 'react';
import HTMLFlipBook from 'react-pageflip';
import { type PageData } from '../types';
import BookPage from './BookPage';
import { ArrowLeft2, ArrowRight2, Pause, Play } from 'iconsax-react';
import { ArrowLeft2, ArrowRight2, Maximize4, Pause, Play, RowVertical } from 'iconsax-react';
import { getPaperDimensions } from '@/config/paperSizes';
import type { DocumentSettings } from '@/pages/editor/store/editorStore';
import { useBookEntranceController } from '@/pages/viewer/hooks/useBookEntranceController';
import { useViewerViewport } from '@/pages/viewer/hooks/useViewerViewport';
import { useIsBrowserFullscreen } from '@/pages/viewer/hooks/useIsBrowserFullscreen';
import { toggleViewerFullscreen } from '@/pages/viewer/utils/viewerFullscreen';
/** فاصله پیش‌فرض بین هر ورق در پخش خودکار (میلی‌ثانیه) */
const AUTO_PLAY_INTERVAL_MS = 3000;
const VIEWER_SCALE = 0.5;
/** فاصلهٔ کتاب از بالای صفحه (پیکسل) */
const BOOK_TOP_GAP_DESKTOP = 48;
const BOOK_TOP_GAP_MOBILE = 32;
/** نسخهٔ فعلی `page-flip.mp3` دو ضربهٔ ورق پشت‌هم دارد؛ فقط تا این سهم از طول پخش می‌شود */
const PAGE_FLIP_SOUND_PLAY_FRACTION = 0.5;
/** هم‌راستا با `md` تیلویند (768px) — موبایل: زیر این عرض */
@@ -111,6 +117,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
const [isAutoPlayActive, setIsAutoPlayActive] = useState(false);
const [startPage, setStartPage] = useState(0);
const isDesktop = useIsDesktop();
const isBrowserFullscreen = useIsBrowserFullscreen();
const autoPlay = documentSettings?.autoPlay ?? false;
const pageFlipSound = documentSettings?.pageFlipSound ?? true;
@@ -130,54 +137,34 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
() => getPaperDimensions(catalogSize ?? 'a4'),
[catalogSize]
);
const displayWidth = Math.round(bookWidth * VIEWER_SCALE);
const displayHeight = Math.round(bookHeight * VIEWER_SCALE);
const hasMultiplePages = pages.length > 1;
const viewport = useViewerViewport();
const usePortrait = !isDesktop || displayStyle === 'single';
// موبایل: از ابعاد 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 = 20;
const edgeBleed = 6;
const reservedNav = 120;
const viewportFit = useMemo(() => {
const padX = isDesktop ? 48 : 20;
const edgeBleed = isDesktop ? 0 : 6;
const reservedNav = isDesktop ? 96 : 120;
const reservedTop = isDesktop ? BOOK_TOP_GAP_DESKTOP : BOOK_TOP_GAP_MOBILE;
const maxW = Math.max(1, viewport.w - padX - edgeBleed * 2);
const maxH = Math.max(1, viewport.h - reservedNav - edgeBleed);
const baseW = bookWidth * VIEWER_SCALE;
const baseH = bookHeight * VIEWER_SCALE;
const scale = Math.min(1, maxW / baseW, maxH / baseH);
const maxH = Math.max(1, viewport.h - reservedNav - reservedTop - edgeBleed);
const spreadCount = usePortrait ? 1 : 2;
const basePageW = bookWidth * VIEWER_SCALE;
const basePageH = bookHeight * VIEWER_SCALE;
const baseSpreadW = basePageW * spreadCount;
const scale = Math.min(maxW / baseSpreadW, maxH / basePageH);
return {
w: Math.max(1, Math.round(baseW * scale)),
h: Math.max(1, Math.round(baseH * scale)),
w: Math.max(1, Math.round(basePageW * scale)),
h: Math.max(1, Math.round(basePageH * scale)),
contentScale: VIEWER_SCALE * scale,
};
}, [isDesktop, bookWidth, bookHeight, viewport.w, viewport.h]);
}, [isDesktop, bookWidth, bookHeight, viewport.w, viewport.h, usePortrait]);
// دسکتاپ: دقیقاً مثل قبل — موبایل: مقیاس‌شده
const pagePixelWidth = isDesktop ? displayWidth : (mobileFit?.w ?? displayWidth);
const pagePixelHeight = isDesktop ? displayHeight : (mobileFit?.h ?? displayHeight);
const contentScale = isDesktop ? VIEWER_SCALE : (mobileFit?.contentScale ?? VIEWER_SCALE);
const pagePixelWidth = viewportFit.w;
const pagePixelHeight = viewportFit.h;
const contentScale = viewportFit.contentScale;
const flipKey = isDesktop
? 'desktop'
: `mobile-${pagePixelWidth}-${pagePixelHeight}`;
const usePortrait = !isDesktop || displayStyle === 'single';
const flipKey = `${usePortrait ? 'portrait' : 'spread'}-${pagePixelWidth}-${pagePixelHeight}`;
const {
scheduleEntranceForSpread,
@@ -455,9 +442,10 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
dir="rtl"
>
<div
className="relative w-full flex justify-center flex-1 max-md:min-h-0 items-center"
className="relative w-full flex flex-col flex-1 max-md:min-h-0 pt-8 md:pt-12 min-h-0"
style={{ overflow: isDesktop ? 'hidden' : 'visible' }}
>
<div className="relative flex flex-1 min-h-0 w-full items-center justify-center">
{/* Border و Shadow عمودی در وسط (فقط حالت دوصفحه‌ای دسکتاپ) */}
{!usePortrait && (
<div
@@ -523,6 +511,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
))}
</HTMLFlipBook>
</div>
</div>
</div>
<div className="shrink-0 max-md:min-h-12 flex items-center gap-3 md:gap-6 bg-white rounded-full px-4 md:px-6 py-2 md:py-3 shadow-lg">
@@ -582,6 +571,20 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
{hasMultiplePages && autoPlay && (
<AutoPlayToggleButton isActive={isAutoPlayActive} onToggle={toggleAutoPlay} />
)}
<button
type="button"
onClick={toggleViewerFullscreen}
className="p-2 md:p-2 min-h-11 min-w-11 md:min-h-0 md:min-w-0 inline-flex items-center justify-center rounded-full hover:bg-gray-100 active:bg-gray-200 transition-all"
aria-label={isBrowserFullscreen ? 'خروج از تمام‌صفحه' : 'تمام‌صفحه مرورگر'}
title={isBrowserFullscreen ? 'خروج از تمام‌صفحه' : 'تمام‌صفحه مرورگر'}
>
{isBrowserFullscreen ? (
<RowVertical color="black" size={20} className="text-gray-700 md:w-6 md:h-6" />
) : (
<Maximize4 color="black" size={20} className="text-gray-700 md:w-6 md:h-6" />
)}
</button>
</div>
</div>
);