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