UNPKG

@jaymanyoo/pdf-book-viewer

Version:

A React-compatible library for displaying PDFs as realistic flip books with 3D page turning animations

292 lines (250 loc) 7.76 kB
import { useState, useCallback, useRef, useEffect } from 'react'; // Simple interfaces for the hook interface PDFBookViewerState { isLoading: boolean; isReady: boolean; currentPage: number; totalPages: number; error: string | null; } interface PDFBookViewerMethods { load: (source: string | File) => Promise<void>; nextPage: () => void; prevPage: () => void; goToPage: (page: number) => void; destroy: () => void; getCurrentPage: () => number; getTotalPages: () => number; } // Hook for managing PDF viewer state export const usePDFViewerState = () => { const [state, setState] = useState<PDFBookViewerState>({ isLoading: false, isReady: false, currentPage: 0, totalPages: 0, error: null }); const setLoading = useCallback((loading: boolean) => { setState((prev: PDFBookViewerState) => ({ ...prev, isLoading: loading, error: loading ? null : prev.error })); }, []); const setReady = useCallback((ready: boolean) => { setState((prev: PDFBookViewerState) => ({ ...prev, isReady: ready })); }, []); const setCurrentPage = useCallback((page: number) => { setState((prev: PDFBookViewerState) => ({ ...prev, currentPage: page })); }, []); const setTotalPages = useCallback((total: number) => { setState((prev: PDFBookViewerState) => ({ ...prev, totalPages: total })); }, []); const setError = useCallback((error: string | null) => { setState((prev: PDFBookViewerState) => ({ ...prev, error, isLoading: false })); }, []); const resetState = useCallback(() => { setState({ isLoading: false, isReady: false, currentPage: 0, totalPages: 0, error: null }); }, []); return { state, setLoading, setReady, setCurrentPage, setTotalPages, setError, resetState }; }; // Hook for PDF navigation export const usePDFNavigation = (viewerRef: React.RefObject<PDFBookViewerMethods>) => { const [canGoNext, setCanGoNext] = useState(false); const [canGoPrev, setCanGoPrev] = useState(false); const updateNavigation = useCallback(() => { if (viewerRef.current) { const currentPage = viewerRef.current.getCurrentPage(); const totalPages = viewerRef.current.getTotalPages(); setCanGoPrev(currentPage > 0); setCanGoNext(currentPage < totalPages - 1); } }, [viewerRef]); const goNext = useCallback(() => { if (canGoNext) { viewerRef.current?.nextPage(); updateNavigation(); } }, [canGoNext, viewerRef, updateNavigation]); const goPrev = useCallback(() => { if (canGoPrev) { viewerRef.current?.prevPage(); updateNavigation(); } }, [canGoPrev, viewerRef, updateNavigation]); const goToPage = useCallback((page: number) => { viewerRef.current?.goToPage(page); updateNavigation(); }, [viewerRef, updateNavigation]); return { canGoNext, canGoPrev, goNext, goPrev, goToPage, updateNavigation }; }; // Hook for keyboard shortcuts export const usePDFKeyboardShortcuts = ( enabled: boolean, onNext: () => void, onPrev: () => void, onEscape?: () => void ) => { useEffect(() => { if (!enabled) return; const handleKeyDown = (event: KeyboardEvent) => { switch (event.key) { case 'ArrowRight': case ' ': // Spacebar event.preventDefault(); onNext(); break; case 'ArrowLeft': event.preventDefault(); onPrev(); break; case 'Escape': if (onEscape) { event.preventDefault(); onEscape(); } break; } }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [enabled, onNext, onPrev, onEscape]); }; // Hook for responsive sizing export const usePDFResponsiveSize = ( containerRef: React.RefObject<HTMLElement>, aspectRatio: number = 0.75 ) => { const [dimensions, setDimensions] = useState({ width: 400, height: 600 }); useEffect(() => { const updateSize = () => { if (containerRef.current) { const containerWidth = containerRef.current.offsetWidth; const maxWidth = Math.min(containerWidth * 0.9, 800); const width = Math.max(maxWidth, 300); const height = width * (1 / aspectRatio); setDimensions({ width: width / 2, height }); // Divide by 2 for PageFlip } }; updateSize(); window.addEventListener('resize', updateSize); return () => window.removeEventListener('resize', updateSize); }, [containerRef, aspectRatio]); return dimensions; }; // Hook for PDF loading with progress export const usePDFLoader = () => { const [loadingProgress, setLoadingProgress] = useState(0); const [loadingStep, setLoadingStep] = useState<string>(''); const updateProgress = useCallback((progress: number, step: string = '') => { setLoadingProgress(Math.max(0, Math.min(100, progress))); setLoadingStep(step); }, []); const resetProgress = useCallback(() => { setLoadingProgress(0); setLoadingStep(''); }, []); return { loadingProgress, loadingStep, updateProgress, resetProgress }; }; // Hook for PDF page analysis export const usePDFPageAnalysis = () => { const [pageTypes, setPageTypes] = useState<Array<'portrait' | 'landscape'>>([]); const [splitPages, setSplitPages] = useState<number[]>([]); const analyzePages = useCallback((viewer: PDFBookViewerMethods) => { // This would need to be implemented based on the internal viewer structure // For now, this is a placeholder for future functionality console.log('Page analysis would be implemented here'); }, []); return { pageTypes, splitPages, analyzePages }; }; // Hook for PDF bookmarks/table of contents export const usePDFBookmarks = () => { const [bookmarks, setBookmarks] = useState<Array<{ title: string; page: number; level: number; }>>([]); const addBookmark = useCallback((title: string, page: number, level: number = 0) => { setBookmarks(prev => [...prev, { title, page, level }].sort((a, b) => a.page - b.page)); }, []); const removeBookmark = useCallback((page: number) => { setBookmarks(prev => prev.filter(bookmark => bookmark.page !== page)); }, []); const goToBookmark = useCallback(( bookmark: { page: number }, viewer: PDFBookViewerMethods ) => { viewer.goToPage(bookmark.page); }, []); return { bookmarks, addBookmark, removeBookmark, goToBookmark }; }; // Hook for PDF search functionality export const usePDFSearch = () => { const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState<Array<{ page: number; text: string; position: { x: number; y: number }; }>>([]); const [currentSearchIndex, setCurrentSearchIndex] = useState(-1); const search = useCallback((query: string) => { setSearchQuery(query); // Search implementation would go here // This is a placeholder for future PDF text search functionality console.log('Search functionality would be implemented here'); }, []); const nextResult = useCallback(() => { if (searchResults.length > 0) { setCurrentSearchIndex(prev => prev < searchResults.length - 1 ? prev + 1 : 0 ); } }, [searchResults.length]); const prevResult = useCallback(() => { if (searchResults.length > 0) { setCurrentSearchIndex(prev => prev > 0 ? prev - 1 : searchResults.length - 1 ); } }, [searchResults.length]); return { searchQuery, searchResults, currentSearchIndex, search, nextResult, prevResult }; };