import * as React from 'react'; import { styled, themed, distance, Skeleton, StandardProps } from 'precise-ui'; import { useDebouncedCallback } from 'use-debounce'; import PdfJs from '../utils/PdfJs'; import { range } from '../utils/hacks'; import Observer, { VisibilityChanged } from './Observer'; const Page = styled.div` margin: auto auto ${distance.xlarge}; user-select: ${props => props.disableSelect && 'none'}; display: block; background-color: ${themed(({ theme = {} }: StandardProps) => theme.ui1)}; box-shadow: rgba(172, 181, 185, 0.4) 0 0 8px 0; `; export interface PDFViewerPageProps { document?: PdfJs.PdfDocument; pageNumber: number; scale: number; disableSelect?: boolean; loaded: boolean; onPageVisibilityChanged(pageIndex: number, ratio: number): boolean; onPageLoaded(pageNumber: number, width: number, height: number): void; } /** * The `Document` is a wrapper to load PDFs and render all the pages */ const PDFViewerPageInner: React.FC = props => { const { document, pageNumber, scale, onPageVisibilityChanged, onPageLoaded, loaded } = props; const [page, setPage] = React.useState(); const [isCalculated, setIsCalculated] = React.useState(false); const canvasRef = React.createRef(); const renderTask = React.useRef(); const debouncedLoad = useDebouncedCallback(() => loadPage(), 100, { leading: true }); const debouncedRender = useDebouncedCallback(() => renderPage(), 100, { leading: true }); const intersectionThreshold = [...Array(10)].map((_, i) => i / 10); React.useEffect(() => { debouncedRender(); }, [page, scale]); function loadPage() { if (document && !page && !isCalculated) { setIsCalculated(true); document.getPage(pageNumber).then(page => { const viewport = page.getViewport({ scale: 1 }); onPageLoaded(pageNumber, viewport.width, viewport.height); setPage(page); }); } } function renderPage() { if (page) { const task = renderTask.current; if (task) { task.cancel(); } const canvasEle = canvasRef.current as HTMLCanvasElement; if (!canvasEle) { return; } const devicePixelRatio = window.devicePixelRatio || 1 const viewport = page.getViewport({ scale }); canvasEle.height = Math.floor(viewport.height * devicePixelRatio); canvasEle.width = Math.floor(viewport.width * devicePixelRatio); canvasEle.style.width = Math.floor(viewport.width) + "px"; canvasEle.style.height = Math.floor(viewport.height) + "px"; const canvasContext = canvasEle.getContext('2d', { alpha: false }) as CanvasRenderingContext2D; const transform = devicePixelRatio !== 1 ? [devicePixelRatio, 0, 0, devicePixelRatio, 0, 0] : null renderTask.current = page.render({ canvasContext, viewport, transform }) renderTask.current.promise.then( // eslint-disable-next-line @typescript-eslint/no-empty-function () => { }, // eslint-disable-next-line @typescript-eslint/no-empty-function () => { }, ); } } function visibilityChanged(params: VisibilityChanged) { const ratio = params.isVisible ? params.ratio : 0; const changed = onPageVisibilityChanged(pageNumber, ratio); if (params.isVisible && changed) { debouncedLoad(); } } return ( {!loaded && ( <>

{range(5).map(index => (








))} )}
); }; function areEqual(prevProps: PDFViewerPageProps, nextProps: PDFViewerPageProps) { return ( prevProps.pageNumber === nextProps.pageNumber && prevProps.disableSelect === nextProps.disableSelect && prevProps.document === nextProps.document && prevProps.loaded === nextProps.loaded && (!nextProps.loaded || prevProps.scale === nextProps.scale) // if it's loading ignore the scale ); } export const PDFViewerPage = React.memo(PDFViewerPageInner, areEqual);