From b1068eec26f2891775483eda32b7a095119c56fe Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 6 May 2026 14:44:45 +0330 Subject: [PATCH] update book viewer auto play --- src/pages/viewer/Viewer.tsx | 10 ++- src/pages/viewer/components/BookPage.tsx | 17 ++++- src/pages/viewer/components/BookViewer.tsx | 85 ++++++++++++++++++---- 3 files changed, 95 insertions(+), 17 deletions(-) diff --git a/src/pages/viewer/Viewer.tsx b/src/pages/viewer/Viewer.tsx index 7bee578..f4fe756 100644 --- a/src/pages/viewer/Viewer.tsx +++ b/src/pages/viewer/Viewer.tsx @@ -2,6 +2,7 @@ import { type FC, useEffect, useState } from 'react'; import BookViewer from './components/BookViewer'; import { transformViewerDataToPages } from './utils/dataTransformer'; import type { PageData } from './types'; +import type { DocumentSettings } from '@/pages/editor/store/editorStore'; import { useParams } from 'react-router-dom'; import { useGetCatalogById } from '@/pages/home/hooks/useHomeData'; @@ -9,16 +10,21 @@ const Viewer: FC = () => { const { id } = useParams<{ id: string }>(); const { data, isLoading, error: queryError } = useGetCatalogById(id ?? ''); const [pages, setPages] = useState([]); + const [documentSettings, setDocumentSettings] = useState | undefined>(undefined); useEffect(() => { if (!data?.data?.content || !id) return; try { const parsed = JSON.parse(data.data.content); - const pagesArray = Array.isArray(parsed) ? parsed : []; + const pagesArray = Array.isArray(parsed) ? parsed : (parsed.pages ?? []); + const settings: Partial | undefined = Array.isArray(parsed) + ? undefined + : parsed.documentSettings; const transformedPages = transformViewerDataToPages( { pages: pagesArray } as Parameters[0] ); setPages(transformedPages); + setDocumentSettings(settings); } catch (err) { console.error('خطا در پردازش محتوای کاتالوگ:', err); } @@ -58,7 +64,7 @@ const Viewer: FC = () => { return (
- +
); }; diff --git a/src/pages/viewer/components/BookPage.tsx b/src/pages/viewer/components/BookPage.tsx index 462a3fe..2df7b9e 100644 --- a/src/pages/viewer/components/BookPage.tsx +++ b/src/pages/viewer/components/BookPage.tsx @@ -8,6 +8,9 @@ type BookPageProps = { pageWidth?: number; pageHeight?: number; onLinkClick?: (linkUrl: string) => void; + backgroundType?: 'color' | 'image'; + backgroundColor?: string; + backgroundImageUrl?: string; }; /** @@ -18,7 +21,7 @@ type BookPageProps = { * نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند */ const BookPage = forwardRef( - ({ page, scale = 1, pageWidth, pageHeight, onLinkClick }, ref) => { + ({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType = 'color', backgroundColor = '#ffffff', backgroundImageUrl = '' }, ref) => { // تابع برای تبدیل opacity به رنگ (مثل editor) // در editor، getColorWithOpacity انتظار opacity 0-100 دارد // در dataTransformer، opacity از 0-1 به 0-100 تبدیل شده است @@ -477,6 +480,16 @@ const BookPage = forwardRef( const width = pageWidth ?? 794 * scale; const height = pageHeight ?? 1123 * scale; + const bgStyle: React.CSSProperties = + backgroundType === 'image' && backgroundImageUrl + ? { + backgroundImage: `url(${backgroundImageUrl})`, + backgroundSize: 'cover', + backgroundPosition: 'center', + backgroundRepeat: 'no-repeat', + } + : { backgroundColor: backgroundColor || '#ffffff' }; + return (
( data-density="soft" style={{ position: 'relative', - backgroundColor: '#ffffff', + ...bgStyle, overflow: 'hidden', boxSizing: 'border-box', width: `${width}px`, diff --git a/src/pages/viewer/components/BookViewer.tsx b/src/pages/viewer/components/BookViewer.tsx index 21e4c8a..c55e7aa 100644 --- a/src/pages/viewer/components/BookViewer.tsx +++ b/src/pages/viewer/components/BookViewer.tsx @@ -4,6 +4,10 @@ import { type PageData } from '../types'; import BookPage from './BookPage'; import { ArrowLeft2, ArrowRight2 } from 'iconsax-react'; import { getPaperDimensions } from '@/config/paperSizes'; +import type { DocumentSettings } from '@/pages/editor/store/editorStore'; + +/** فاصله پیش‌فرض بین هر ورق در پخش خودکار (میلی‌ثانیه) */ +const AUTO_PLAY_INTERVAL_MS = 3000; const VIEWER_SCALE = 0.5; /** نسخهٔ فعلی `page-flip.mp3` دو ضربهٔ ورق پشت‌هم دارد؛ فقط تا این سهم از طول پخش می‌شود */ @@ -28,6 +32,7 @@ function useIsDesktop() { export type BookViewerProps = { pages: PageData[]; catalogSize?: string; + documentSettings?: Partial; }; interface PageFlipAPI { @@ -77,12 +82,26 @@ function triggerFlipPrev(api: PageFlipWithFlipController | undefined) { * این کامپوننت از HTMLFlipBook استفاده می‌کند که یک wrapper برای PageFlip است * از HTML برای رندر صفحات استفاده می‌شود (نه Canvas) برای سازگاری با Konva در آینده */ -const BookViewer: FC = ({ pages, catalogSize }) => { +const BookViewer: FC = ({ pages, catalogSize, documentSettings }) => { const bookRef = useRef(null); const pageFlipHandlerRef = useRef<(e: FlipEvent) => void>(() => {}); const [currentPage, setCurrentPage] = useState(0); + const [isAutoPlayActive, setIsAutoPlayActive] = useState(false); + const [startPage, setStartPage] = useState(0); const isDesktop = useIsDesktop(); + const autoPlay = documentSettings?.autoPlay ?? false; + const pageFlipSound = documentSettings?.pageFlipSound ?? true; + const leftToRightFlip = documentSettings?.leftToRightFlip ?? false; + const displayStyle = documentSettings?.displayStyle ?? 'double'; + const backgroundType = documentSettings?.backgroundType ?? 'color'; + const backgroundColor = documentSettings?.backgroundColor ?? '#ffffff'; + const backgroundImageUrl = documentSettings?.backgroundImageUrl ?? ''; + + useEffect(() => { + setIsAutoPlayActive(autoPlay); + }, [autoPlay]); + // سایز کتاب بر اساس catalogSize (A3/A4/A5) const { width: bookWidth, height: bookHeight } = useMemo( () => getPaperDimensions(catalogSize ?? 'a4'), @@ -210,13 +229,13 @@ const BookViewer: FC = ({ pages, catalogSize }) => { pageNum = pages.length - 1; } - if (lastFlipPageRef.current !== null && lastFlipPageRef.current !== pageNum) { + if (pageFlipSound && lastFlipPageRef.current !== null && lastFlipPageRef.current !== pageNum) { playPageFlipSound(); } lastFlipPageRef.current = pageNum; setCurrentPage(pageNum); - }, [pages.length, playPageFlipSound]); + }, [pages.length, pageFlipSound, playPageFlipSound]); pageFlipHandlerRef.current = handlePageFlip; @@ -264,6 +283,12 @@ const BookViewer: FC = ({ pages, catalogSize }) => { triggerFlipPrev(api); }, [currentPage]); + const stopAutoPlayByUser = useCallback(() => { + if (isAutoPlayActive) { + setIsAutoPlayActive(false); + } + }, [isAutoPlayActive]); + const handleLinkClick = useCallback((linkUrl: string) => { if (linkUrl.startsWith('page://')) { const action = linkUrl.replace('page://', ''); @@ -339,16 +364,33 @@ const BookViewer: FC = ({ pages, catalogSize }) => { } }, [currentPage, pages.length, goToNextPage, goToPrevPage]); - const usePortrait = !isDesktop; + // تک‌صفحه‌ای: موبایل یا تنظیم single، دوصفحه‌ای: دسکتاپ + تنظیم double + const usePortrait = !isDesktop || displayStyle === 'single'; const prevFlipKeyRef = useRef(flipKey); useEffect(() => { if (prevFlipKeyRef.current !== flipKey) { prevFlipKeyRef.current = flipKey; lastFlipPageRef.current = currentPage; + setStartPage(currentPage); } }, [flipKey, currentPage]); + // پخش خودکار + useEffect(() => { + if (!isAutoPlayActive || pages.length <= 1) return; + const timer = setInterval(() => { + if (currentPage >= pages.length - 1) { + bookRef.current?.pageFlip()?.turnToPage?.(0); + setCurrentPage(0); + lastFlipPageRef.current = 0; + return; + } + bookRef.current?.pageFlip()?.flipNext(); + }, AUTO_PLAY_INTERVAL_MS); + return () => clearInterval(timer); + }, [isAutoPlayActive, currentPage, pages.length]); + return (
= ({ pages, catalogSize }) => { drawShadow={isDesktop} flippingTime={800} usePortrait={usePortrait} - startPage={currentPage} + startPage={startPage} autoSize={false} maxShadowOpacity={isDesktop ? 0.5 : 0} showCover={true} @@ -412,6 +454,9 @@ const BookViewer: FC = ({ pages, catalogSize }) => { pageWidth={pagePixelWidth} pageHeight={pagePixelHeight} onLinkClick={handleLinkClick} + backgroundType={backgroundType} + backgroundColor={backgroundColor} + backgroundImageUrl={backgroundImageUrl} /> ))} @@ -419,13 +464,20 @@ const BookViewer: FC = ({ pages, catalogSize }) => {
- {/* دکمه صفحه بعد (در RTL، صفحه سمت چپ) */} + {/* دکمه راست: فارسی = صفحه بعد | لاتین = صفحه قبل */} @@ -437,13 +489,20 @@ const BookViewer: FC = ({ pages, catalogSize }) => {
- {/* دکمه صفحه قبل (در RTL، صفحه سمت راست) */} + {/* دکمه چپ: فارسی = صفحه قبل | لاتین = صفحه بعد */}