queue
This commit is contained in:
@@ -37,16 +37,12 @@ const AppLayout: React.FC = () => {
|
||||
});
|
||||
|
||||
if (['logged-out', 'authenticating', 'selecting-tenant'].includes(appStatus)) {
|
||||
const shouldRememberLastLocation = appStatus !== 'logged-out';
|
||||
const redirectTarget = `${location.pathname}${location.search}${location.hash || ''}`;
|
||||
return (
|
||||
<Navigate
|
||||
to="/account/login"
|
||||
replace
|
||||
state={
|
||||
shouldRememberLastLocation
|
||||
? { from: location.pathname + location.search }
|
||||
: undefined
|
||||
}
|
||||
state={{ from: redirectTarget }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,12 @@ interface PageDescriptor {
|
||||
scale: number;
|
||||
}
|
||||
|
||||
interface RenderQueueRequest {
|
||||
pageNumber: number;
|
||||
resume: () => void;
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
const RERENDER_DELTA = 48;
|
||||
|
||||
const getAvailableViewportSize = (viewportNode: Element, stackNode: Element) => {
|
||||
@@ -85,6 +91,50 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX.
|
||||
const wasmUrlRef = useRef<string | null>(null);
|
||||
const pageNodeMapRef = useRef<Map<number, HTMLElement>>(new Map());
|
||||
const intersectionObserverRef = useRef<IntersectionObserver | null>(null);
|
||||
const renderQueueRef = useRef<RenderQueueRequest[]>([]);
|
||||
const activeRendersRef = useRef(new Set<number>());
|
||||
const maxConcurrentRendersRef = useRef(2);
|
||||
|
||||
const requestQueueFlush = useCallback(() => {
|
||||
const queue = renderQueueRef.current;
|
||||
while (queue.length > 0 && activeRendersRef.current.size < maxConcurrentRendersRef.current) {
|
||||
const next = queue.pop();
|
||||
if (!next) {
|
||||
break;
|
||||
}
|
||||
if (activeRendersRef.current.has(next.pageNumber)) {
|
||||
next.cancel();
|
||||
continue;
|
||||
}
|
||||
activeRendersRef.current.add(next.pageNumber);
|
||||
next.resume();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const enqueueRender = useCallback((request: RenderQueueRequest) => {
|
||||
renderQueueRef.current.push(request);
|
||||
requestQueueFlush();
|
||||
}, [requestQueueFlush]);
|
||||
|
||||
const releaseRenderSlot = useCallback((pageNumber: number) => {
|
||||
if (activeRendersRef.current.delete(pageNumber)) {
|
||||
requestQueueFlush();
|
||||
}
|
||||
}, [requestQueueFlush]);
|
||||
|
||||
const cancelRenderRequest = useCallback((pageNumber: number) => {
|
||||
const queue = renderQueueRef.current;
|
||||
const index = queue.findIndex((entry) => entry.pageNumber === pageNumber);
|
||||
if (index >= 0) {
|
||||
const [entry] = queue.splice(index, 1);
|
||||
entry.cancel();
|
||||
}
|
||||
releaseRenderSlot(pageNumber);
|
||||
}, [releaseRenderSlot]);
|
||||
|
||||
const handleRenderFinished = useCallback((pageNumber: number) => {
|
||||
releaseRenderSlot(pageNumber);
|
||||
}, [releaseRenderSlot]);
|
||||
const focusTargetRef = useRef<{
|
||||
ratioX: number;
|
||||
ratioY: number;
|
||||
@@ -428,11 +478,14 @@ const PdfViewer = ({ src, title, className, viewportRef }: PdfViewerProps): JSX.
|
||||
viewMode={viewMode}
|
||||
shouldRender={shouldRenderPage(page.number)}
|
||||
registerPageNode={registerPageNode}
|
||||
enqueueRender={enqueueRender}
|
||||
cancelRenderRequest={cancelRenderRequest}
|
||||
onRenderFinished={handleRenderFinished}
|
||||
/>
|
||||
</Fragment>
|
||||
))
|
||||
), [handlePageClick, pages, pixelRatio, registerPageNode, shouldRenderPage,
|
||||
viewportHeight, viewportWidth, viewMode]);
|
||||
), [cancelRenderRequest, enqueueRender, handlePageClick, handleRenderFinished,
|
||||
pages, pixelRatio, registerPageNode, shouldRenderPage, viewportHeight, viewportWidth, viewMode]);
|
||||
|
||||
return (
|
||||
<div className={viewerClasses}>
|
||||
@@ -466,6 +519,9 @@ interface PdfPageCanvasProps {
|
||||
viewMode: ViewMode;
|
||||
shouldRender: boolean;
|
||||
registerPageNode: (pageNumber: number, node: HTMLElement | null) => void;
|
||||
enqueueRender: (request: RenderQueueRequest) => void;
|
||||
cancelRenderRequest: (pageNumber: number) => void;
|
||||
onRenderFinished: (pageNumber: number) => void;
|
||||
}
|
||||
|
||||
function PdfPageCanvas({
|
||||
@@ -478,6 +534,9 @@ function PdfPageCanvas({
|
||||
viewMode,
|
||||
shouldRender,
|
||||
registerPageNode,
|
||||
enqueueRender,
|
||||
cancelRenderRequest,
|
||||
onRenderFinished,
|
||||
}: PdfPageCanvasProps): JSX.Element {
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
@@ -488,6 +547,18 @@ function PdfPageCanvas({
|
||||
registerPageNode(descriptor.number, node);
|
||||
}, [descriptor.number, registerPageNode]);
|
||||
const [canvasMounted, setCanvasMounted] = useState(false);
|
||||
const [allowRender, setAllowRender] = useState(false);
|
||||
|
||||
const resetCanvas = useCallback(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
canvas.width = 0;
|
||||
canvas.height = 0;
|
||||
const context = canvas.getContext('2d');
|
||||
context?.clearRect(0, 0, context.canvas.width || 0, context.canvas.height || 0);
|
||||
}, []);
|
||||
|
||||
const renderPage = useCallback(async () => {
|
||||
if (renderTaskRef.current || hasRenderedRef.current) {
|
||||
@@ -496,51 +567,31 @@ function PdfPageCanvas({
|
||||
const pdf = pdfRef.current;
|
||||
const canvas = canvasRef.current;
|
||||
if (!pdf || !canvas) {
|
||||
onRenderFinished(descriptor.number);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const page = await pdf.getPage(descriptor.number);
|
||||
const viewport = page.getViewport({ scale: descriptor.scale });
|
||||
const qualityScale = 1;
|
||||
const supportsOffscreen = typeof OffscreenCanvas !== 'undefined';
|
||||
let renderContext: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null = null;
|
||||
let offscreenCanvas: OffscreenCanvas | null = null;
|
||||
|
||||
if (supportsOffscreen) {
|
||||
offscreenCanvas = new OffscreenCanvas(
|
||||
viewport.width * pixelRatio * qualityScale,
|
||||
viewport.height * pixelRatio * qualityScale,
|
||||
);
|
||||
renderContext = offscreenCanvas.getContext('2d');
|
||||
} else {
|
||||
renderContext = canvas.getContext('2d');
|
||||
if (!renderContext) {
|
||||
return;
|
||||
}
|
||||
canvas.width = viewport.width * pixelRatio;
|
||||
canvas.height = viewport.height * pixelRatio;
|
||||
const renderContext = canvas.getContext('2d');
|
||||
if (!renderContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderContext?.setTransform(pixelRatio * qualityScale, 0, 0, pixelRatio * qualityScale, 0, 0);
|
||||
canvas.width = viewport.width * pixelRatio * qualityScale;
|
||||
canvas.height = viewport.height * pixelRatio * qualityScale;
|
||||
renderContext.setTransform(pixelRatio * qualityScale, 0, 0, pixelRatio * qualityScale, 0, 0);
|
||||
|
||||
const renderTask = page.render({
|
||||
canvasContext: renderContext as CanvasRenderingContext2D,
|
||||
canvasContext: renderContext,
|
||||
viewport,
|
||||
canvas: supportsOffscreen ? undefined : canvas,
|
||||
canvas,
|
||||
});
|
||||
renderTaskRef.current = renderTask;
|
||||
await renderTask.promise;
|
||||
hasRenderedRef.current = true;
|
||||
|
||||
if (offscreenCanvas) {
|
||||
const bitmap = offscreenCanvas.transferToImageBitmap();
|
||||
canvas.width = bitmap.width;
|
||||
canvas.height = bitmap.height;
|
||||
const destContext = canvas.getContext('2d');
|
||||
destContext?.drawImage(bitmap, 0, 0);
|
||||
bitmap.close();
|
||||
}
|
||||
|
||||
page.cleanup();
|
||||
|
||||
const wrapper = wrapperRef.current;
|
||||
@@ -554,8 +605,9 @@ function PdfPageCanvas({
|
||||
console.error('Failed to render PDF page', descriptor.number, error);
|
||||
} finally {
|
||||
renderTaskRef.current = null;
|
||||
onRenderFinished(descriptor.number);
|
||||
}
|
||||
}, [descriptor.number, descriptor.scale, pdfRef, pixelRatio]);
|
||||
}, [descriptor.number, descriptor.scale, onRenderFinished, pdfRef, pixelRatio]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasMounted) {
|
||||
@@ -579,10 +631,39 @@ function PdfPageCanvas({
|
||||
setCanvasMounted(false);
|
||||
hasRenderedRef.current = false;
|
||||
renderTaskRef.current?.cancel();
|
||||
}, [descriptor.number]);
|
||||
resetCanvas();
|
||||
}, [descriptor.number, resetCanvas]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldRender) {
|
||||
if (!shouldRender) {
|
||||
setAllowRender(false);
|
||||
cancelRenderRequest(descriptor.number);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
const request: RenderQueueRequest = {
|
||||
pageNumber: descriptor.number,
|
||||
resume: () => {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
setAllowRender(true);
|
||||
},
|
||||
cancel: () => {
|
||||
cancelled = true;
|
||||
setAllowRender(false);
|
||||
},
|
||||
};
|
||||
enqueueRender(request);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
setAllowRender(false);
|
||||
cancelRenderRequest(descriptor.number);
|
||||
};
|
||||
}, [cancelRenderRequest, descriptor.number, enqueueRender, shouldRender]);
|
||||
|
||||
useEffect(() => {
|
||||
if (allowRender) {
|
||||
setCanvasMounted((prev) => (prev ? prev : true));
|
||||
return;
|
||||
}
|
||||
@@ -593,16 +674,17 @@ function PdfPageCanvas({
|
||||
hasRenderedRef.current = false;
|
||||
renderTaskRef.current?.cancel();
|
||||
wrapperRef.current?.querySelector('.textLayer')?.remove();
|
||||
resetCanvas();
|
||||
return false;
|
||||
});
|
||||
}, [shouldRender]);
|
||||
}, [allowRender, resetCanvas]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasMounted) {
|
||||
if (!canvasMounted || !allowRender) {
|
||||
return;
|
||||
}
|
||||
void renderPage();
|
||||
}, [canvasMounted, renderPage]);
|
||||
}, [allowRender, canvasMounted, renderPage]);
|
||||
|
||||
const limitAxis: 'width' | 'height' = useMemo(() => {
|
||||
if (viewMode === 'fit-width') {
|
||||
|
||||
Reference in New Issue
Block a user