From 26b32d669ff26d7f3c5122c0765d14ddaeee5f2e Mon Sep 17 00:00:00 2001 From: Wojciech Maj Date: Wed, 8 Mar 2023 13:16:02 +0100 Subject: [PATCH] Rewrite Page to React Hooks --- src/Page.jsx | 416 +++++++++++++++++++++++---------------------------- 1 file changed, 185 insertions(+), 231 deletions(-) diff --git a/src/Page.jsx b/src/Page.jsx index dd43ec656..33ef69245 100644 --- a/src/Page.jsx +++ b/src/Page.jsx @@ -1,4 +1,4 @@ -import React, { createRef, PureComponent } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import makeCancellable from 'make-cancellable-promise'; import makeEventProps from 'make-event-props'; @@ -31,181 +31,96 @@ import { const defaultScale = 1; -export class PageInternal extends PureComponent { - state = { - page: null, - }; - - pageElement = createRef(); - - componentDidMount() { - const { pdf } = this.props; - - invariant(pdf, 'Attempted to load a page, but no document was specified.'); - - this.loadPage(); - } - - componentDidUpdate(prevProps) { - const { pdf } = this.props; - - if ( - (prevProps.pdf && pdf !== prevProps.pdf) || - this.getPageNumber() !== this.getPageNumber(prevProps) - ) { - const { unregisterPage } = this.props; - - if (unregisterPage) unregisterPage(this.getPageIndex(prevProps)); - - this.loadPage(); - } - } - - componentWillUnmount() { - const { unregisterPage } = this.props; - - if (unregisterPage) unregisterPage(this.pageIndex); - - cancelRunningTask(this.runningTask); - } - - get childContext() { - const { pageIndex, pageNumber } = this; - const { page } = this.state; - - if (!page) { - return {}; - } - - const { - canvasBackground, - customTextRenderer, - devicePixelRatio, - onGetAnnotationsError, - onGetAnnotationsSuccess, - onGetTextError, - onGetTextSuccess, - onRenderAnnotationLayerError, - onRenderAnnotationLayerSuccess, - onRenderError, - onRenderSuccess, - onRenderTextLayerError, - onRenderTextLayerSuccess, - renderForms, - renderInteractiveForms, - } = this.props; - - return { - canvasBackground, - customTextRenderer, - devicePixelRatio, - onGetAnnotationsError, - onGetAnnotationsSuccess, - onGetTextError, - onGetTextSuccess, - onRenderAnnotationLayerError, - onRenderAnnotationLayerSuccess, - onRenderError, - onRenderSuccess, - onRenderTextLayerError, - onRenderTextLayerSuccess, - page, - pageIndex, - pageNumber, - renderForms: renderForms ?? renderInteractiveForms, // For backward compatibility - rotate: this.rotate, - scale: this.scale, - }; - } - - /** - * Called when a page is loaded successfully - */ - onLoadSuccess = () => { - const { onLoadSuccess, registerPage } = this.props; - const { page } = this.state; - - if (onLoadSuccess) onLoadSuccess(makePageCallback(page, this.scale)); - - if (registerPage) registerPage(this.pageIndex, this.pageElement.current); - }; - - /** - * Called when a page failed to load - */ - onLoadError = (error) => { - this.setState({ page: false }); - - warning(false, error); - - const { onLoadError } = this.props; - - if (onLoadError) onLoadError(error); - }; - - getPageIndex(props = this.props) { - if (isProvided(props.pageNumber)) { - return props.pageNumber - 1; +export function PageInternal({ + canvasBackground, + canvasRef, + children, + className, + customTextRenderer, + devicePixelRatio, + error, + height, + inputRef, + loading, + noData, + onGetAnnotationsError: onGetAnnotationsErrorProps, + onGetAnnotationsSuccess: onGetAnnotationsSuccessProps, + onGetTextError: onGetTextErrorProps, + onGetTextSuccess: onGetTextSuccessProps, + onLoadError: onLoadErrorProps, + onLoadSuccess: onLoadSuccessProps, + onRenderAnnotationLayerError: onRenderAnnotationLayerErrorProps, + onRenderAnnotationLayerSuccess: onRenderAnnotationLayerSuccessProps, + onRenderError: onRenderErrorProps, + onRenderSuccess: onRenderSuccessProps, + onRenderTextLayerError: onRenderTextLayerErrorProps, + onRenderTextLayerSuccess: onRenderTextLayerSuccessProps, + pageIndex: pageIndexProps, + pageNumber: pageNumberProps, + pdf, + registerPage, + renderAnnotationLayer: renderAnnotationLayerProps, + renderForms, + renderInteractiveForms, + renderMode, + renderTextLayer: renderTextLayerProps, + rotate: rotateProps, + scale: scaleProps, + unregisterPage, + width, + ...otherProps +}) { + const [page, setPage] = useState(null); + const pageElement = useRef(); + + invariant(pdf, 'Attempted to load a page, but no document was specified.'); + + const pageIndex = (() => { + if (isProvided(pageNumberProps)) { + return pageNumberProps - 1; } - if (isProvided(props.pageIndex)) { - return props.pageIndex; + if (isProvided(pageIndexProps)) { + return pageIndexProps; } return null; - } + })(); - getPageNumber(props = this.props) { - if (isProvided(props.pageNumber)) { - return props.pageNumber; + const pageNumber = (() => { + if (isProvided(pageNumberProps)) { + return pageNumberProps; } - if (isProvided(props.pageIndex)) { - return props.pageIndex + 1; + if (isProvided(pageIndexProps)) { + return pageIndexProps + 1; } return null; - } + })(); - get pageIndex() { - return this.getPageIndex(); - } - - get pageNumber() { - return this.getPageNumber(); - } - - get rotate() { - const { rotate } = this.props; - - if (isProvided(rotate)) { - return rotate; + const rotate = (() => { + if (isProvided(rotateProps)) { + return rotateProps; } - const { page } = this.state; - if (!page) { return null; } return page.rotate; - } - - get scale() { - const { page } = this.state; + })(); + const scale = (() => { if (!page) { return null; } - const { scale, width, height } = this.props; - const { rotate } = this; - // Be default, we'll render page at 100% * scale width. let pageScale = 1; // Passing scale explicitly null would cause the page not to render - const scaleWithDefault = scale === null ? defaultScale : scale; + const scaleWithDefault = scaleProps === null ? defaultScale : scaleProps; // If width/height is defined, calculate the scale of the page so it could be of desired width. if (width || height) { @@ -214,83 +129,140 @@ export class PageInternal extends PureComponent { } return scaleWithDefault * pageScale; + })(); + + function hook() { + return () => { + if (unregisterPage) { + unregisterPage(pageIndex); + } + }; } - get eventProps() { - return makeEventProps(this.props, () => { - const { page } = this.state; - if (!page) { - return page; + useEffect(hook, [pdf, pageIndex, unregisterPage]); + + /** + * Called when a page is loaded successfully + */ + const onLoadSuccess = useCallback( + (nextPage, nextScale) => { + if (onLoadSuccessProps) { + onLoadSuccessProps(makePageCallback(nextPage, nextScale)); } + }, + [onLoadSuccessProps], + ); - return makePageCallback(page, this.scale); - }); - } + /** + * Called when a page failed to load + */ + const onLoadError = useCallback( + (error) => { + warning(false, error); - get pageKey() { - return `${this.pageIndex}@${this.scale}/${this.rotate}`; - } + if (onLoadErrorProps) { + onLoadErrorProps(error); + } + }, + [onLoadErrorProps], + ); - get pageKeyNoScale() { - return `${this.pageIndex}/${this.rotate}`; + function resetPage() { + setPage(null); } - loadPage = () => { - const { pdf } = this.props; - - const pageNumber = this.getPageNumber(); + useEffect(resetPage, [pdf, pageIndex]); + function loadPage() { if (!pageNumber) { return; } - this.setState((prevState) => { - if (!prevState.page) { - return null; - } - return { page: null }; - }); - const cancellable = makeCancellable(pdf.getPage(pageNumber)); - this.runningTask = cancellable; + const runningTask = cancellable; cancellable.promise - .then((page) => { - this.setState({ page }, this.onLoadSuccess); + .then((nextPage) => { + setPage(nextPage); + onLoadSuccess(nextPage, scale); + + if (registerPage) { + registerPage(pageIndex, pageElement.current); + } }) .catch((error) => { - this.onLoadError(error); + setPage(false); + onLoadError(error); }); - }; - renderMainLayer() { - const { canvasRef, renderMode } = this.props; + return () => cancelRunningTask(runningTask); + } + + useEffect(loadPage, [ + pdf, + onLoadError, + onLoadSuccess, + pageIndex, + pageNumber, + registerPage, + scale, + ]); + + const childContext = page + ? { + canvasBackground, + customTextRenderer, + devicePixelRatio, + onGetAnnotationsError: onGetAnnotationsErrorProps, + onGetAnnotationsSuccess: onGetAnnotationsSuccessProps, + onGetTextError: onGetTextErrorProps, + onGetTextSuccess: onGetTextSuccessProps, + onRenderAnnotationLayerError: onRenderAnnotationLayerErrorProps, + onRenderAnnotationLayerSuccess: onRenderAnnotationLayerSuccessProps, + onRenderError: onRenderErrorProps, + onRenderSuccess: onRenderSuccessProps, + onRenderTextLayerError: onRenderTextLayerErrorProps, + onRenderTextLayerSuccess: onRenderTextLayerSuccessProps, + page, + pageIndex, + pageNumber, + renderForms: renderForms ?? renderInteractiveForms, // For backward compatibility + rotate: rotate, + scale: scale, + } + : null; + + const eventProps = useMemo( + () => makeEventProps(otherProps, () => (page ? makePageCallback(page, scale) : null)), + [otherProps, page, scale], + ); + const pageKey = `${pageIndex}@${scale}/${rotate}`; + + const pageKeyNoScale = `${pageIndex}/${rotate}`; + + function renderMainLayer() { switch (renderMode) { case 'none': return null; case 'svg': - return ; + return ; case 'canvas': default: - return ; + return ; } } - renderTextLayer() { - const { renderTextLayer } = this.props; - - if (!renderTextLayer) { + function renderTextLayer() { + if (!renderTextLayerProps) { return null; } - return ; + return ; } - renderAnnotationLayer() { - const { renderAnnotationLayer } = this.props; - - if (!renderAnnotationLayer) { + function renderAnnotationLayer() { + if (!renderAnnotationLayerProps) { return null; } @@ -298,71 +270,53 @@ export class PageInternal extends PureComponent { * As of now, PDF.js 2.0.943 returns warnings on unimplemented annotations in SVG mode. * Therefore, as a fallback, we render "traditional" AnnotationLayer component. */ - - return ; + return ; } - renderChildren() { - const { children } = this.props; - + function renderChildren() { return ( - - {this.renderMainLayer()} - {this.renderTextLayer()} - {this.renderAnnotationLayer()} + + {renderMainLayer()} + {renderTextLayer()} + {renderAnnotationLayer()} {children} ); } - renderContent() { - const { pageNumber } = this; - const { pdf } = this.props; - const { page } = this.state; - + function renderContent() { if (!pageNumber) { - const { noData } = this.props; - return {typeof noData === 'function' ? noData() : noData}; } if (pdf === null || page === null) { - const { loading } = this.props; - return ( {typeof loading === 'function' ? loading() : loading} ); } if (pdf === false || page === false) { - const { error } = this.props; - return {typeof error === 'function' ? error() : error}; } - return this.renderChildren(); + return renderChildren(); } - render() { - const { pageNumber } = this; - const { className, inputRef } = this.props; - - return ( -
- {this.renderContent()} -
- ); - } + return ( +
+ {renderContent()} +
+ ); } PageInternal.defaultProps = {