next and prev page
This commit is contained in:
@@ -34,6 +34,7 @@ interface PageFlipAPI {
|
||||
flipNext: () => void;
|
||||
flipPrev: () => void;
|
||||
turnToPage: (page: number) => void;
|
||||
getCurrentPageIndex?: () => number;
|
||||
}
|
||||
|
||||
interface FlipBookInstance {
|
||||
@@ -44,6 +45,33 @@ interface FlipEvent {
|
||||
data: number;
|
||||
}
|
||||
|
||||
type PageFlipInitEvent = { data: { page: number; mode: string } };
|
||||
type PageFlipStateEvent = { data: string };
|
||||
|
||||
/** مختصات داخلی رندر کتاب (همان فضایی که getBoundsRect و flipNext استفاده میکنند) */
|
||||
type FlipBookRect = { left: number; top: number; width: number; height: number; pageWidth: number };
|
||||
|
||||
type PageFlipWithFlipController = PageFlipAPI & {
|
||||
getBoundsRect: () => FlipBookRect;
|
||||
getFlipController: () => { flip: (p: { x: number; y: number }) => void };
|
||||
};
|
||||
|
||||
/**
|
||||
* page-flip داخل flipPrev با x=10 ثابت میزند؛ با disableFlipByClick آزمایش گوشه رد میشود
|
||||
* (چون bookPos.x = 10 - rect.left معمولاً ≤ 0). flipNext همان rect.left را جمع میکند.
|
||||
*/
|
||||
function triggerFlipPrev(api: PageFlipWithFlipController | undefined) {
|
||||
if (!api?.getBoundsRect || !api.getFlipController) {
|
||||
api?.flipPrev();
|
||||
return;
|
||||
}
|
||||
const rect = api.getBoundsRect();
|
||||
api.getFlipController().flip({
|
||||
x: rect.left + 12,
|
||||
y: rect.top + 2,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* کامپوننت اصلی کتابخوان با استفاده از react-pageflip
|
||||
* این کامپوننت از HTMLFlipBook استفاده میکند که یک wrapper برای PageFlip است
|
||||
@@ -51,6 +79,7 @@ interface FlipEvent {
|
||||
*/
|
||||
const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
const bookRef = useRef<FlipBookInstance | null>(null);
|
||||
const pageFlipHandlerRef = useRef<(e: FlipEvent) => void>(() => {});
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const isDesktop = useIsDesktop();
|
||||
|
||||
@@ -189,6 +218,40 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
setCurrentPage(pageNum);
|
||||
}, [pages.length, playPageFlipSound]);
|
||||
|
||||
pageFlipHandlerRef.current = handlePageFlip;
|
||||
|
||||
const onFlipForwarded = useCallback((e: FlipEvent) => {
|
||||
pageFlipHandlerRef.current(e);
|
||||
}, []);
|
||||
|
||||
const syncPageIndexFromBook = useCallback(() => {
|
||||
const api = bookRef.current?.pageFlip();
|
||||
const idx = api?.getCurrentPageIndex?.();
|
||||
if (typeof idx !== 'number' || pages.length <= 0) return;
|
||||
const clamped = Math.max(0, Math.min(idx, pages.length - 1));
|
||||
setCurrentPage(clamped);
|
||||
}, [pages.length]);
|
||||
|
||||
const handleBookInit = useCallback(
|
||||
(e: PageFlipInitEvent) => {
|
||||
const p = e.data?.page;
|
||||
if (typeof p !== 'number' || pages.length <= 0) return;
|
||||
const clamped = Math.max(0, Math.min(p, pages.length - 1));
|
||||
setCurrentPage(clamped);
|
||||
lastFlipPageRef.current = clamped;
|
||||
},
|
||||
[pages.length]
|
||||
);
|
||||
|
||||
/** بعد از اتمام انیمیشن، ایندکس واقعی کتاب را میگیریم (RTL روی روت کتاب مختصات page-flip را بههم میزند) */
|
||||
const handleChangeState = useCallback(
|
||||
(e: PageFlipStateEvent) => {
|
||||
if (e.data !== 'read') return;
|
||||
syncPageIndexFromBook();
|
||||
},
|
||||
[syncPageIndexFromBook]
|
||||
);
|
||||
|
||||
const goToNextPage = useCallback(() => {
|
||||
if (currentPage < pages.length - 1) {
|
||||
bookRef.current?.pageFlip()?.flipNext();
|
||||
@@ -196,15 +259,15 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
}, [currentPage, pages.length]);
|
||||
|
||||
const goToPrevPage = useCallback(() => {
|
||||
if (currentPage > 0) {
|
||||
bookRef.current?.pageFlip()?.flipPrev();
|
||||
}
|
||||
if (currentPage <= 0) return;
|
||||
const api = bookRef.current?.pageFlip() as PageFlipWithFlipController | undefined;
|
||||
triggerFlipPrev(api);
|
||||
}, [currentPage]);
|
||||
|
||||
const handleLinkClick = useCallback((linkUrl: string) => {
|
||||
if (linkUrl.startsWith('page://')) {
|
||||
const action = linkUrl.replace('page://', '');
|
||||
const pageFlipAPI = bookRef.current?.pageFlip();
|
||||
const pageFlipAPI = bookRef.current?.pageFlip() as PageFlipWithFlipController | undefined;
|
||||
|
||||
if (!pageFlipAPI) return;
|
||||
|
||||
@@ -216,7 +279,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
// رفتن به صفحه اول
|
||||
const flipToFirst = () => {
|
||||
if (currentPage > 0) {
|
||||
pageFlipAPI.flipPrev();
|
||||
triggerFlipPrev(pageFlipAPI);
|
||||
setTimeout(() => {
|
||||
setCurrentPage(prev => {
|
||||
if (prev > 1) {
|
||||
@@ -262,7 +325,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
flipToTarget(remaining - 1);
|
||||
}, 300);
|
||||
} else {
|
||||
pageFlipAPI.flipPrev();
|
||||
triggerFlipPrev(pageFlipAPI);
|
||||
setTimeout(() => {
|
||||
setCurrentPage(prev => prev - 1);
|
||||
flipToTarget(remaining + 1);
|
||||
@@ -302,7 +365,12 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
boxShadow: '-5px 0 15px rgba(0, 0, 0, 0.15), 5px 0 15px rgba(0, 0, 0, 0.15)',
|
||||
}}
|
||||
/>
|
||||
{/*
|
||||
page-flip با مختصات LTR کار میکند؛ direction:rtl روی روت کتاب باعث ناهماهنگی
|
||||
ایندکس/onFlip با دکمهها میشود. محتوای فارسی داخل BookPage با dir=rtl است.
|
||||
*/}
|
||||
<div
|
||||
dir="ltr"
|
||||
className="max-w-full flex justify-center"
|
||||
style={{ overflow: isDesktop ? 'hidden' : 'visible', isolation: 'isolate' }}
|
||||
>
|
||||
@@ -331,10 +399,10 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
showPageCorners={true}
|
||||
disableFlipByClick={true}
|
||||
className="flipbook-container mx-auto max-w-full"
|
||||
style={{
|
||||
direction: 'rtl',
|
||||
}}
|
||||
onFlip={handlePageFlip}
|
||||
style={{}}
|
||||
onFlip={onFlipForwarded}
|
||||
onInit={handleBookInit}
|
||||
onChangeState={handleChangeState}
|
||||
>
|
||||
{pages.map((page) => (
|
||||
<BookPage
|
||||
@@ -355,11 +423,11 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={goToNextPage}
|
||||
disabled={currentPage <= 0}
|
||||
disabled={currentPage >= pages.length - 1}
|
||||
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 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
|
||||
aria-label="صفحه بعد"
|
||||
>
|
||||
<ArrowLeft2 size={20} className="text-gray-700 md:w-6 md:h-6" />
|
||||
<ArrowRight2 color='black' size={20} className="text-gray-700 md:w-6 md:h-6" />
|
||||
</button>
|
||||
|
||||
{/* شماره صفحه */}
|
||||
@@ -373,11 +441,11 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize }) => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={goToPrevPage}
|
||||
disabled={currentPage >= pages.length - 1}
|
||||
disabled={currentPage <= 0}
|
||||
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 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
|
||||
aria-label="صفحه قبل"
|
||||
>
|
||||
<ArrowRight2 size={20} className="text-gray-700 md:w-6 md:h-6" />
|
||||
<ArrowLeft2 color='black' size={20} className="text-gray-700 md:w-6 md:h-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user