diff --git a/components/PublicGrid.tsx b/components/PublicGrid.tsx index 0fe0d78..6c2d7ad 100644 --- a/components/PublicGrid.tsx +++ b/components/PublicGrid.tsx @@ -445,10 +445,11 @@ function playFlipSound(ctx: AudioContext | null) { } function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) { - const [spread, setSpread] = useState(0); - const [flipping, setFlipping] = useState<'forward' | 'backward' | null>(null); + const containerRef = useRef(null); + const flipRef = useRef(null); const audioRef = useRef(null); - const dragStartX = useRef(null); + const [currentPage, setCurrentPage] = useState(0); + const [pageCount, setPageCount] = useState(pages.length); const getCtx = () => { if (!audioRef.current) { @@ -459,29 +460,65 @@ function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) return audioRef.current; }; - const totalSpreads = Math.max(1, Math.ceil(pages.length / 2)); - const getPage = (i: number) => (i >= 0 && i < pages.length ? pages[i] : ''); - - const leftIdx = spread * 2; - const rightIdx = spread * 2 + 1; - const nextLeftIdx = (spread + 1) * 2; - const nextRightIdx = (spread + 1) * 2 + 1; - const prevLeftIdx = (spread - 1) * 2; - const prevRightIdx = (spread - 1) * 2 + 1; - - const goNext = useCallback(() => { - if (flipping !== null || spread >= totalSpreads - 1) return; - playFlipSound(getCtx()); - setFlipping('forward'); - window.setTimeout(() => { setSpread(s => s + 1); setFlipping(null); }, 750); - }, [flipping, spread, totalSpreads]); - - const goPrev = useCallback(() => { - if (flipping !== null || spread <= 0) return; - playFlipSound(getCtx()); - setFlipping('backward'); - window.setTimeout(() => { setSpread(s => s - 1); setFlipping(null); }, 750); - }, [flipping, spread]); + useEffect(() => { + if (!containerRef.current || pages.length === 0) return; + let cancelled = false; + + // page-flip's destroy() removes its own block from the DOM. Give it a child + // div we own so React's managed container stays intact across StrictMode cycles. + const block = document.createElement('div'); + block.style.width = '100%'; + block.style.height = '100%'; + containerRef.current.appendChild(block); + + import('page-flip').then(({ PageFlip }) => { + if (cancelled) return; + const flip = new PageFlip(block, { + width: 550, + height: 733, + size: 'stretch', + minWidth: 315, + maxWidth: 1400, + minHeight: 420, + maxHeight: 900, + drawShadow: true, + flippingTime: 800, + usePortrait: true, + maxShadowOpacity: 0.5, + showCover: true, + mobileScrollSupport: false, + useMouseEvents: true, + swipeDistance: 30, + showPageCorners: true, + disableFlipByClick: false, + }); + + flip.on('flip', (e) => { + const newPage = typeof e.data === 'number' ? e.data : 0; + setCurrentPage(newPage); + playFlipSound(getCtx()); + }); + flip.on('init', () => { + setPageCount(flip.getPageCount()); + }); + + flip.loadFromImages(pages); + flipRef.current = flip; + }).catch(() => {}); + + return () => { + cancelled = true; + if (flipRef.current) { + try { flipRef.current.destroy(); } catch {} + flipRef.current = null; + } + try { block.remove(); } catch {} + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const goNext = useCallback(() => flipRef.current?.flipNext(), []); + const goPrev = useCallback(() => flipRef.current?.flipPrev(), []); useEffect(() => { const onKey = (e: KeyboardEvent) => { @@ -493,43 +530,8 @@ function FlipBook({ pages, onClose }: { pages: string[]; onClose: () => void }) return () => window.removeEventListener('keydown', onKey); }, [onClose, goNext, goPrev]); - const onPointerDown = (e: React.PointerEvent) => { dragStartX.current = e.clientX; }; - const onPointerUp = (e: React.PointerEvent) => { - if (dragStartX.current === null) return; - const dx = e.clientX - dragStartX.current; - if (Math.abs(dx) > 50) (dx < 0 ? goNext() : goPrev()); - dragStartX.current = null; - }; - const onPointerCancel = () => { dragStartX.current = null; }; - - // Pages visible underneath the flipping overlay - const visibleLeft = flipping === 'backward' ? getPage(prevLeftIdx) : getPage(leftIdx); - const visibleRight = flipping === 'forward' ? getPage(nextRightIdx) : getPage(rightIdx); - - const currentPageNum = Math.min(leftIdx + 1, pages.length); - const lastPageNum = Math.min(rightIdx + 1, pages.length); - const indicatorLabel = currentPageNum === lastPageNum - ? `${currentPageNum} / ${pages.length}` - : `${currentPageNum}-${lastPageNum} / ${pages.length}`; - - const pageBoxClass = 'absolute inset-0 bg-white overflow-hidden'; - const pageImgClass = 'w-full h-full object-contain'; - return ( -
-