@peergrade/react-pdf
Version:
Display PDFs in your React app as easily as if they were images.
176 lines (147 loc) • 3.64 kB
JSX
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import mergeClassNames from 'merge-class-names';
import DocumentContext from './DocumentContext';
import OutlineContext from './OutlineContext';
import OutlineItem from './OutlineItem';
import {
callIfDefined,
cancelRunningTask,
errorOnDev,
makeCancellable,
} from './shared/utils';
import { makeEventProps } from './shared/events';
import { eventsProps, isClassName, isPdf } from './shared/propTypes';
export class OutlineInternal extends PureComponent {
state = {
outline: null,
}
componentDidMount() {
if (!this.props.pdf) {
throw new Error('Attempted to load an outline, but no document was specified.');
}
this.loadOutline();
}
componentDidUpdate(prevProps) {
if (prevProps.pdf && (this.props.pdf !== prevProps.pdf)) {
this.loadOutline();
}
}
componentWillUnmount() {
cancelRunningTask(this.runningTask);
}
loadOutline = async () => {
const { pdf } = this.props;
this.setState((prevState) => {
if (!prevState.outline) {
return null;
}
return { outline: null };
});
let outline = null;
try {
const cancellable = makeCancellable(pdf.getOutline());
this.runningTask = cancellable;
outline = await cancellable.promise;
this.setState({ outline }, this.onLoadSuccess);
} catch (error) {
this.setState({ outline: false });
this.onLoadError(error);
}
}
get childContext() {
return {
onClick: this.onItemClick,
};
}
get eventProps() {
return makeEventProps(this.props, () => this.state.outline);
}
/**
* Called when an outline is read successfully
*/
onLoadSuccess = () => {
callIfDefined(
this.props.onLoadSuccess,
this.state.outline,
);
}
/**
* Called when an outline failed to read successfully
*/
onLoadError = (error) => {
if (
error.name === 'RenderingCancelledException' ||
error.name === 'PromiseCancelledException'
) {
return;
}
errorOnDev(error);
callIfDefined(
this.props.onLoadError,
error,
);
}
onItemClick = ({ pageIndex, pageNumber }) => {
callIfDefined(
this.props.onItemClick,
{
pageIndex,
pageNumber,
},
);
}
renderOutline() {
const { outline } = this.state;
return (
<ul>
{
outline.map((item, itemIndex) => (
<OutlineItem
key={
typeof item.destination === 'string' ?
item.destination :
itemIndex
}
item={item}
/>
))
}
</ul>
);
}
render() {
const { pdf } = this.props;
const { outline } = this.state;
if (!pdf || !outline) {
return null;
}
const { className } = this.props;
return (
<div
className={mergeClassNames('react-pdf__Outline', className)}
ref={this.props.inputRef}
{...this.eventProps}
>
<OutlineContext.Provider value={this.childContext}>
{this.renderOutline()}
</OutlineContext.Provider>
</div>
);
}
}
OutlineInternal.propTypes = {
className: isClassName,
inputRef: PropTypes.func,
onItemClick: PropTypes.func,
onLoadError: PropTypes.func,
onLoadSuccess: PropTypes.func,
pdf: isPdf,
...eventsProps(),
};
const Outline = props => (
<DocumentContext.Consumer>
{context => <OutlineInternal {...context} {...props} />}
</DocumentContext.Consumer>
);
export default Outline;