2022-09-11 15:58:32 +02:00
|
|
|
import { useEffect, useRef, useState, MutableRefObject, useCallback } from 'react';
|
2022-08-27 14:32:20 +02:00
|
|
|
import * as React from 'react';
|
2022-08-04 11:12:22 +02:00
|
|
|
import useIsVisible from './hooks/useIsVisible';
|
2022-09-11 15:58:32 +02:00
|
|
|
import useVisibleOnSelect, { VisibleOnSelect } from './hooks/useVisibleOnSelect';
|
2022-09-05 13:35:38 +02:00
|
|
|
import PdfDocument from './PdfDocument';
|
2022-09-11 15:58:32 +02:00
|
|
|
import { ScaledSize, RenderRequest } from './types';
|
2022-08-27 14:32:20 +02:00
|
|
|
import styled from 'styled-components';
|
2022-08-04 11:12:22 +02:00
|
|
|
|
2022-09-11 15:58:32 +02:00
|
|
|
|
|
|
|
require('./textLayer.css');
|
|
|
|
|
|
|
|
const PageWrapper = styled.div<{ isSelected?: boolean }>`
|
2022-08-27 14:32:20 +02:00
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
overflow: hidden;
|
2022-09-11 15:58:32 +02:00
|
|
|
border: ${props => props.isSelected ? 'solid 5px #0079ff' : 'solid thin rgba(120, 120, 120, 0.498)'};
|
2022-08-27 14:32:20 +02:00
|
|
|
background: rgb(233, 233, 233);
|
|
|
|
position: relative;
|
2022-09-11 15:58:32 +02:00
|
|
|
border-radius: ${props => props.isSelected ? '0.3rem' : '0px'};
|
2022-08-27 14:32:20 +02:00
|
|
|
`;
|
|
|
|
|
2022-09-11 15:58:32 +02:00
|
|
|
const PageInfo = styled.div<{ isSelected?: boolean }>`
|
2022-08-27 14:32:20 +02:00
|
|
|
position: absolute;
|
|
|
|
top: 0.5rem;
|
|
|
|
left: 0.5rem;
|
|
|
|
padding: 0.3rem;
|
2022-09-11 15:58:32 +02:00
|
|
|
background: ${props => props.isSelected ? '#0079ff' : 'rgba(203, 203, 203, 0.509)'};
|
2022-08-27 14:32:20 +02:00
|
|
|
border-radius: 0.3rem;
|
|
|
|
font-size: 0.8rem;
|
2022-09-11 15:58:32 +02:00
|
|
|
color: ${props => props.isSelected ? 'white' : 'rgba(91, 91, 91, 0.829)'};
|
2022-08-27 14:32:20 +02:00
|
|
|
backdrop-filter: blur(0.5rem);
|
|
|
|
cursor: default;
|
|
|
|
user-select: none;
|
|
|
|
&:hover{
|
|
|
|
opacity: 0.3;
|
|
|
|
}
|
|
|
|
`;
|
2022-08-04 11:12:22 +02:00
|
|
|
|
|
|
|
export interface PageProps {
|
2022-09-05 13:35:38 +02:00
|
|
|
pdfDocument: PdfDocument;
|
2022-08-04 11:12:22 +02:00
|
|
|
pageNo: number;
|
|
|
|
focusOnLoad: boolean;
|
|
|
|
isAnchored: boolean;
|
|
|
|
scaledSize: ScaledSize;
|
|
|
|
isDarkTheme: boolean;
|
2022-08-27 14:32:20 +02:00
|
|
|
container: MutableRefObject<HTMLElement>;
|
2022-09-11 15:58:32 +02:00
|
|
|
showPageNumbers: boolean;
|
|
|
|
isSelected: boolean;
|
|
|
|
textSelectable: boolean;
|
|
|
|
onTextSelect?: (text: string)=> void;
|
|
|
|
onClick?: (page: number)=> void;
|
|
|
|
onDoubleClick?: (page: number)=> void;
|
2022-08-04 11:12:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function Page(props: PageProps) {
|
|
|
|
const [error, setError] = useState(null);
|
2022-08-27 14:32:20 +02:00
|
|
|
const scaleRef = useRef<number>(null);
|
2022-08-04 11:12:22 +02:00
|
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
2022-09-11 15:58:32 +02:00
|
|
|
const textRef = useRef<HTMLDivElement>(null);
|
2022-08-04 11:12:22 +02:00
|
|
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
2022-09-11 15:58:32 +02:00
|
|
|
const isVisible = useIsVisible(wrapperRef, props.container);
|
|
|
|
useVisibleOnSelect({
|
|
|
|
isVisible,
|
|
|
|
isSelected: props.isSelected,
|
|
|
|
container: props.container,
|
|
|
|
wrapperRef,
|
|
|
|
} as VisibleOnSelect);
|
2022-08-04 11:12:22 +02:00
|
|
|
|
|
|
|
useEffect(() => {
|
2022-09-11 15:58:32 +02:00
|
|
|
const isCancelled = () => props.scaledSize.scale !== scaleRef.current;
|
2022-08-04 11:12:22 +02:00
|
|
|
|
2022-09-11 15:58:32 +02:00
|
|
|
const renderPage = async () => {
|
|
|
|
try {
|
2022-09-12 17:07:39 +02:00
|
|
|
if (canvasRef.current) {
|
|
|
|
canvasRef.current.style.height = '100%';
|
|
|
|
canvasRef.current.style.width = '100%';
|
|
|
|
}
|
2022-09-11 15:58:32 +02:00
|
|
|
const renderRequest: RenderRequest = {
|
|
|
|
pageNo: props.pageNo,
|
|
|
|
scaledSize: props.scaledSize,
|
|
|
|
getTextLayer: props.textSelectable,
|
|
|
|
isCancelled,
|
|
|
|
};
|
|
|
|
const { canvas, textLayerDiv } = await props.pdfDocument.renderPage(renderRequest);
|
|
|
|
|
|
|
|
wrapperRef.current.appendChild(canvas);
|
|
|
|
if (textLayerDiv) wrapperRef.current.appendChild(textLayerDiv);
|
|
|
|
|
|
|
|
if (canvasRef.current) canvasRef.current.remove();
|
|
|
|
if (textRef.current) textRef.current.remove();
|
|
|
|
|
|
|
|
canvasRef.current = canvas;
|
|
|
|
if (textLayerDiv) textRef.current = textLayerDiv;
|
|
|
|
} catch (error) {
|
|
|
|
if (isCancelled()) return;
|
|
|
|
error.message = `Error rendering page no. ${props.pageNo}: ${error.message}`;
|
|
|
|
setError(error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (isVisible && props.scaledSize && (props.scaledSize.scale !== scaleRef.current)) {
|
|
|
|
scaleRef.current = props.scaledSize.scale;
|
|
|
|
void renderPage();
|
2022-08-04 11:12:22 +02:00
|
|
|
}
|
2022-09-11 15:58:32 +02:00
|
|
|
|
|
|
|
}, [props.scaledSize, isVisible, props.textSelectable, props.pageNo, props.pdfDocument]);
|
2022-08-04 11:12:22 +02:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (props.focusOnLoad) {
|
|
|
|
props.container.current.scrollTop = wrapperRef.current.offsetTop;
|
|
|
|
// console.warn('setting focus on page', props.pageNo, wrapperRef.current.offsetTop);
|
|
|
|
}
|
2022-08-27 14:32:20 +02:00
|
|
|
}, [props.container, props.focusOnLoad]);
|
2022-08-04 11:12:22 +02:00
|
|
|
|
2022-09-11 15:58:32 +02:00
|
|
|
|
|
|
|
const onClick = useCallback(async (_e: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
if (props.onClick) props.onClick(props.pageNo);
|
|
|
|
}, [props.onClick, props.pageNo]);
|
|
|
|
|
2024-04-05 13:16:49 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
2022-08-04 11:12:22 +02:00
|
|
|
let style: any = {};
|
|
|
|
if (props.scaledSize) {
|
|
|
|
style = {
|
|
|
|
...style,
|
|
|
|
width: props.scaledSize.width,
|
|
|
|
height: props.scaledSize.height,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-09-11 15:58:32 +02:00
|
|
|
const onContextMenu = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
if (!props.textSelectable || !props.onTextSelect || !window.getSelection()) return;
|
|
|
|
const text = window.getSelection().toString();
|
|
|
|
if (!text) return;
|
|
|
|
props.onTextSelect(text);
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
}, [props.textSelectable, props.onTextSelect]);
|
|
|
|
|
|
|
|
const onDoubleClick = useCallback(() => {
|
|
|
|
if (props.onDoubleClick) props.onDoubleClick(props.pageNo);
|
|
|
|
}, [props.onDoubleClick, props.pageNo]);
|
|
|
|
|
2022-08-04 11:12:22 +02:00
|
|
|
return (
|
2022-09-11 15:58:32 +02:00
|
|
|
<PageWrapper onDoubleClick={onDoubleClick} isSelected={!!props.isSelected} onContextMenu={onContextMenu} onClick={onClick} ref={wrapperRef} style={style}>
|
|
|
|
{ error && <div>Error: {error}</div> }
|
|
|
|
{props.showPageNumbers && <PageInfo isSelected={!!props.isSelected}>{props.isAnchored ? '📌' : ''} Page {props.pageNo}</PageInfo>}
|
2022-08-27 14:32:20 +02:00
|
|
|
</PageWrapper>
|
2022-08-04 11:12:22 +02:00
|
|
|
);
|
|
|
|
}
|