fix(web): lecteur PDF — pages blanches après rendu et base minimale stable
Le canvas PDF restait vide après rendu (blank-detected): la détection d'encre reposait sur un seuil de blanc trop sensible et un fond de canvas non maîtrisé. Le rendu passe à un fond sentinel explicite avec classification rendered-ok/blank-detected et un seuil de pixels visibles. Le lecteur est par ailleurs ramené à une base minimale stable: layout plein viewport (app-main-reader), suppression des modes de lecture et des préférences par livre, helpers de pagination/viewport extraits dans readerLayout.ts. Shell, lecteurs CBZ/EPUB et styles alignés sur cette base. Refs #31 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,10 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import * as pdfjs from "pdfjs-dist";
|
||||
import { ReaderError } from "./ReaderError";
|
||||
import { pdfCanvasHasVisibleInk, pdfRenderScale } from "./pdfRender";
|
||||
import { classifyPdfCanvas, pdfCanvasVisible, pdfRenderScale, pdfSentinelBackground, type PdfRenderResult } from "./pdfRender";
|
||||
import { configurePdfWorker } from "./pdfWorker";
|
||||
import { clampReaderPage, readableViewportSize, readerPositionLabel, type ReaderSize } from "./readerLayout";
|
||||
import type { ReaderControls } from "./ReaderShell";
|
||||
import type { ReaderMode } from "../api/types";
|
||||
|
||||
configurePdfWorker(pdfjs);
|
||||
|
||||
@ -14,77 +14,196 @@ type PdfReaderProps = {
|
||||
url: string;
|
||||
page: number;
|
||||
backHref: string;
|
||||
mode: ReaderMode;
|
||||
onPageCommit: (page: number, pages: number, anchor?: number, strategy?: PageCommitStrategy) => void;
|
||||
onControlsChange: (controls: ReaderControls) => void;
|
||||
};
|
||||
|
||||
type PdfPageCanvasProps = {
|
||||
document: pdfjs.PDFDocumentProxy;
|
||||
pageNumber: number;
|
||||
mode: ReaderMode;
|
||||
frameSize: { width: number; height: number };
|
||||
onError: (message: string) => void;
|
||||
type PdfFailure = {
|
||||
detail: string;
|
||||
technicalDetail: string;
|
||||
};
|
||||
|
||||
let pdfLifecycle = Promise.resolve();
|
||||
type PdfPageStatus = "idle" | "loading" | PdfRenderResult | "render-failed";
|
||||
|
||||
function enqueuePdfLifecycle<T>(work: () => Promise<T>) {
|
||||
const run = pdfLifecycle.then(work, work);
|
||||
pdfLifecycle = run.then(() => undefined, () => undefined);
|
||||
return run;
|
||||
function pdfTechnicalMessage(error: unknown) {
|
||||
if (error instanceof Error && error.message.trim()) return `${error.name}: ${error.message}`;
|
||||
if (typeof error === "string" && error.trim()) return error;
|
||||
return "Erreur inconnue.";
|
||||
}
|
||||
|
||||
function pdfFailure(source: string, detail: string, context: Record<string, unknown>, error?: unknown): PdfFailure {
|
||||
return {
|
||||
detail,
|
||||
technicalDetail: JSON.stringify(
|
||||
{
|
||||
source,
|
||||
...context,
|
||||
error: error === undefined ? undefined : pdfTechnicalMessage(error)
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function isPdfTransitionError(error: unknown) {
|
||||
return error instanceof Error && (error.message.includes("worker is being destroyed") || error.message.includes("Rendering cancelled"));
|
||||
}
|
||||
|
||||
function pdfReaderErrorMessage(error: unknown) {
|
||||
if (isPdfTransitionError(error)) return "Le lecteur PDF termine une ouverture precedente.";
|
||||
return "Le PDF n'a pas pu s'ouvrir.";
|
||||
}
|
||||
|
||||
function scrollContainerFor(element: HTMLElement | null): HTMLElement | null {
|
||||
return element?.closest(".reader-stage") as HTMLElement | null;
|
||||
}
|
||||
|
||||
function PdfPageCanvas({ document, pageNumber, mode, frameSize, onError }: PdfPageCanvasProps) {
|
||||
function afterNextPaint() {
|
||||
return new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
|
||||
}
|
||||
|
||||
export function PdfReader({ url, page, backHref, onPageCommit, onControlsChange }: PdfReaderProps) {
|
||||
const frameRef = useRef<HTMLDivElement>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [visible, setVisible] = useState(mode === "horizontal");
|
||||
const [rendered, setRendered] = useState(false);
|
||||
const [documentProxy, setDocumentProxy] = useState<pdfjs.PDFDocumentProxy | null>(null);
|
||||
const [pages, setPages] = useState(1);
|
||||
const [documentError, setDocumentError] = useState<PdfFailure>();
|
||||
const [pageError, setPageError] = useState<PdfFailure>();
|
||||
const [loadingDocument, setLoadingDocument] = useState(true);
|
||||
const [loadingPage, setLoadingPage] = useState(false);
|
||||
const [pageRendered, setPageRendered] = useState(false);
|
||||
const [pageStatus, setPageStatus] = useState<PdfPageStatus>("idle");
|
||||
const [documentAttempt, setDocumentAttempt] = useState(0);
|
||||
const [pageAttempt, setPageAttempt] = useState(0);
|
||||
const [viewportSize, setViewportSize] = useState<ReaderSize | null>(null);
|
||||
|
||||
const currentPage = clampReaderPage(page, pages);
|
||||
const showCanvas = pdfCanvasVisible(pageRendered, Boolean(pageError));
|
||||
|
||||
const go = useCallback(
|
||||
(nextPage: number) => {
|
||||
const target = clampReaderPage(nextPage, pages);
|
||||
setPageError(undefined);
|
||||
onPageCommit(target, pages, 1, "immediate");
|
||||
},
|
||||
[onPageCommit, pages]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode === "horizontal") {
|
||||
setVisible(true);
|
||||
return;
|
||||
onControlsChange({
|
||||
canPrevious: !loadingDocument && !documentError && !pageError && currentPage > 1,
|
||||
canNext: !loadingDocument && !documentError && !pageError && currentPage < pages,
|
||||
positionLabel: loadingDocument ? "Chargement" : readerPositionLabel(currentPage, pages),
|
||||
onPrevious: () => go(currentPage - 1),
|
||||
onNext: () => go(currentPage + 1)
|
||||
});
|
||||
}, [currentPage, documentError, go, loadingDocument, onControlsChange, pageError, pages]);
|
||||
|
||||
useEffect(() => {
|
||||
const frame = frameRef.current;
|
||||
const stage = scrollContainerFor(frame);
|
||||
const observedElement = stage ?? frame;
|
||||
if (!observedElement) return;
|
||||
const updateSize = () => {
|
||||
const rect = observedElement.getBoundingClientRect();
|
||||
const nextSize = readableViewportSize({ width: rect.width, height: rect.height });
|
||||
if (nextSize) setViewportSize(nextSize);
|
||||
};
|
||||
updateSize();
|
||||
const frameId = requestAnimationFrame(updateSize);
|
||||
const observer = new ResizeObserver(updateSize);
|
||||
observer.observe(observedElement);
|
||||
return () => {
|
||||
cancelAnimationFrame(frameId);
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
let loadingTask: pdfjs.PDFDocumentLoadingTask | undefined;
|
||||
setLoadingDocument(true);
|
||||
setDocumentError(undefined);
|
||||
setPageError(undefined);
|
||||
setPageRendered(false);
|
||||
setPageStatus("idle");
|
||||
setDocumentProxy(null);
|
||||
|
||||
async function loadDocument() {
|
||||
try {
|
||||
try {
|
||||
new URL(url, window.location.href);
|
||||
} catch (error) {
|
||||
throw pdfFailure("uri", "PDF: URI de fichier invalide.", { url }, error);
|
||||
}
|
||||
configurePdfWorker(pdfjs);
|
||||
loadingTask = pdfjs.getDocument({ url, withCredentials: true });
|
||||
const loadedDocument = await loadingTask.promise;
|
||||
if (cancelled) {
|
||||
await loadingTask.destroy();
|
||||
return;
|
||||
}
|
||||
setPages(loadedDocument.numPages);
|
||||
setDocumentProxy(loadedDocument);
|
||||
setLoadingDocument(false);
|
||||
} catch (error) {
|
||||
if (cancelled && isPdfTransitionError(error)) return;
|
||||
if (!cancelled) {
|
||||
setDocumentError(
|
||||
typeof error === "object" && error && "detail" in error && "technicalDetail" in error
|
||||
? (error as PdfFailure)
|
||||
: pdfFailure(
|
||||
"getDocument",
|
||||
"PDF: getDocument() a échoué.",
|
||||
{
|
||||
url,
|
||||
resolvedUrl: (() => {
|
||||
try {
|
||||
return new URL(url, window.location.href).toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})(),
|
||||
workerSrc: pdfjs.GlobalWorkerOptions.workerSrc,
|
||||
hasWorkerPort: Boolean(pdfjs.GlobalWorkerOptions.workerPort)
|
||||
},
|
||||
error
|
||||
)
|
||||
);
|
||||
setLoadingDocument(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
const wrapper = wrapperRef.current;
|
||||
if (!wrapper) return;
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries.some((entry) => entry.isIntersecting)) setVisible(true);
|
||||
},
|
||||
{ root: scrollContainerFor(wrapper), rootMargin: "900px 0px" }
|
||||
);
|
||||
observer.observe(wrapper);
|
||||
return () => observer.disconnect();
|
||||
}, [mode]);
|
||||
|
||||
void loadDocument();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
void loadingTask?.destroy();
|
||||
};
|
||||
}, [documentAttempt, url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
if (!documentProxy || !viewportSize) return;
|
||||
const loadedDocument = documentProxy;
|
||||
const readerViewport = viewportSize;
|
||||
let cancelled = false;
|
||||
let renderTask: pdfjs.RenderTask | undefined;
|
||||
setRendered(false);
|
||||
setLoadingPage(true);
|
||||
setPageError(undefined);
|
||||
setPageRendered(false);
|
||||
setPageStatus("loading");
|
||||
|
||||
async function renderPage() {
|
||||
try {
|
||||
const pdfPage = await document.getPage(pageNumber);
|
||||
let pdfPage: pdfjs.PDFPageProxy;
|
||||
try {
|
||||
pdfPage = await loadedDocument.getPage(currentPage);
|
||||
} catch (error) {
|
||||
throw pdfFailure("getPage", `PDF: getPage(${currentPage}) a échoué.`, { pageNumber: currentPage }, error);
|
||||
}
|
||||
if (cancelled) return;
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
if (!canvas) {
|
||||
throw pdfFailure("react-canvas-ref", `PDF: canvas absent pour la page ${currentPage}.`, { pageNumber: currentPage });
|
||||
}
|
||||
const baseViewport = pdfPage.getViewport({ scale: 1 });
|
||||
const fitScale = pdfRenderScale(mode, frameSize, { width: baseViewport.width, height: baseViewport.height });
|
||||
const fitScale = pdfRenderScale(readerViewport, { width: baseViewport.width, height: baseViewport.height });
|
||||
const pixelRatio = Math.min(2, window.devicePixelRatio || 1);
|
||||
const renderScale = Math.max(0.25, Math.min(3, fitScale)) * pixelRatio;
|
||||
const viewport = pdfPage.getViewport({ scale: renderScale });
|
||||
@ -95,189 +214,136 @@ function PdfPageCanvas({ document, pageNumber, mode, frameSize, onError }: PdfPa
|
||||
canvas.style.width = `${cssWidth}px`;
|
||||
canvas.style.height = `${cssHeight}px`;
|
||||
const context = canvas.getContext("2d", { alpha: false });
|
||||
if (!context) return;
|
||||
context.fillStyle = "#f7f0df";
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
renderTask = pdfPage.render({ canvas, canvasContext: context, viewport });
|
||||
await renderTask.promise;
|
||||
if (!pdfCanvasHasVisibleInk(context.getImageData(0, 0, canvas.width, canvas.height))) {
|
||||
throw new Error(`La page PDF ${pageNumber} est blanche apres rendu.`);
|
||||
if (!context) {
|
||||
throw pdfFailure("canvas-context", `PDF: contexte canvas 2D indisponible pour la page ${currentPage}.`, {
|
||||
pageNumber: currentPage,
|
||||
canvasWidth: canvas.width,
|
||||
canvasHeight: canvas.height
|
||||
});
|
||||
}
|
||||
if (canvas.width <= 0 || canvas.height <= 0 || cssWidth <= 0 || cssHeight <= 0) {
|
||||
throw pdfFailure("canvas-size", `PDF: dimensions canvas invalides pour la page ${currentPage}.`, {
|
||||
pageNumber: currentPage,
|
||||
canvasWidth: canvas.width,
|
||||
canvasHeight: canvas.height,
|
||||
cssWidth,
|
||||
cssHeight,
|
||||
readerViewport
|
||||
});
|
||||
}
|
||||
context.fillStyle = pdfSentinelBackground();
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
try {
|
||||
renderTask = pdfPage.render({ canvas, canvasContext: context, viewport });
|
||||
await renderTask.promise;
|
||||
} catch (error) {
|
||||
throw pdfFailure(
|
||||
"render-failed",
|
||||
`PDF: render() a échoué pour la page ${currentPage}.`,
|
||||
{
|
||||
result: "render-failed",
|
||||
pageNumber: currentPage,
|
||||
baseViewport: { width: baseViewport.width, height: baseViewport.height },
|
||||
renderViewport: { width: viewport.width, height: viewport.height },
|
||||
canvasWidth: canvas.width,
|
||||
canvasHeight: canvas.height,
|
||||
cssWidth,
|
||||
cssHeight,
|
||||
readerViewport
|
||||
},
|
||||
error
|
||||
);
|
||||
}
|
||||
await afterNextPaint();
|
||||
if (cancelled) return;
|
||||
const renderResult = classifyPdfCanvas(context.getImageData(0, 0, canvas.width, canvas.height));
|
||||
if (renderResult === "blank-detected") {
|
||||
setPageStatus("blank-detected");
|
||||
setPageRendered(false);
|
||||
setLoadingPage(false);
|
||||
setPageError(
|
||||
pdfFailure("blank-detected", `PDF: page ${currentPage} blanche détectée après rendu.`, {
|
||||
result: "blank-detected",
|
||||
pageNumber: currentPage,
|
||||
baseViewport: { width: baseViewport.width, height: baseViewport.height },
|
||||
renderViewport: { width: viewport.width, height: viewport.height },
|
||||
canvasWidth: canvas.width,
|
||||
canvasHeight: canvas.height,
|
||||
cssWidth,
|
||||
cssHeight,
|
||||
readerViewport
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!cancelled) {
|
||||
setPageStatus("rendered-ok");
|
||||
setPageRendered(true);
|
||||
setLoadingPage(false);
|
||||
}
|
||||
if (!cancelled) setRendered(true);
|
||||
} catch (error) {
|
||||
if (!cancelled && !isPdfTransitionError(error)) onError(pdfReaderErrorMessage(error));
|
||||
if (cancelled || isPdfTransitionError(error)) return;
|
||||
setPageRendered(false);
|
||||
setPageStatus("render-failed");
|
||||
setLoadingPage(false);
|
||||
setPageError(
|
||||
typeof error === "object" && error && "detail" in error && "technicalDetail" in error
|
||||
? (error as PdfFailure)
|
||||
: pdfFailure("render-unclassified", `PDF: erreur non classée pendant le rendu de la page ${currentPage}.`, { pageNumber: currentPage }, error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void enqueuePdfLifecycle(renderPage);
|
||||
void renderPage();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
renderTask?.cancel();
|
||||
};
|
||||
}, [document, frameSize.height, frameSize.width, mode, onError, pageNumber, visible]);
|
||||
}, [currentPage, documentProxy, pageAttempt, viewportSize]);
|
||||
|
||||
return (
|
||||
<div className="pdf-page-frame" data-reader-page={pageNumber} ref={wrapperRef}>
|
||||
{!rendered && (
|
||||
<div className="reader-fallback">
|
||||
<span>Page {pageNumber}</span>
|
||||
</div>
|
||||
)}
|
||||
<canvas ref={canvasRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PdfReader({ url, page, backHref, mode, onPageCommit, onControlsChange }: PdfReaderProps) {
|
||||
const frameRef = useRef<HTMLDivElement>(null);
|
||||
const restoredRef = useRef(false);
|
||||
const [documentProxy, setDocumentProxy] = useState<pdfjs.PDFDocumentProxy | null>(null);
|
||||
const [pages, setPages] = useState(1);
|
||||
const [visiblePage, setVisiblePage] = useState(page);
|
||||
const [error, setError] = useState<string>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [attempt, setAttempt] = useState(0);
|
||||
const [frameSize, setFrameSize] = useState({ width: 980, height: 900 });
|
||||
|
||||
const currentPage = Math.max(1, Math.min(page, pages));
|
||||
const displayedPage = mode === "vertical" ? Math.max(1, Math.min(visiblePage, pages)) : currentPage;
|
||||
|
||||
const go = useCallback(
|
||||
(nextPage: number) => {
|
||||
const target = Math.max(1, Math.min(nextPage, pages));
|
||||
if (mode === "vertical") {
|
||||
frameRef.current?.querySelector(`[data-reader-page="${target}"]`)?.scrollIntoView({ block: "start" });
|
||||
onPageCommit(target, pages, 0, "immediate");
|
||||
setVisiblePage(target);
|
||||
} else {
|
||||
onPageCommit(target, pages, 1, "immediate");
|
||||
}
|
||||
},
|
||||
[mode, onPageCommit, pages]
|
||||
);
|
||||
|
||||
const goTop = useCallback(() => go(1), [go]);
|
||||
const handlePageError = useCallback((message: string) => {
|
||||
setError(message);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
onControlsChange({
|
||||
canPrevious: !loading && !error && displayedPage > 1,
|
||||
canNext: !loading && !error && displayedPage < pages,
|
||||
canTop: !loading && !error && displayedPage > 1,
|
||||
positionLabel: loading ? "Ouverture PDF" : `${displayedPage} / ${pages}`,
|
||||
onPrevious: () => go(displayedPage - 1),
|
||||
onNext: () => go(displayedPage + 1),
|
||||
onTop: goTop
|
||||
});
|
||||
}, [displayedPage, error, go, goTop, loading, onControlsChange, pages]);
|
||||
|
||||
useEffect(() => {
|
||||
const frame = frameRef.current;
|
||||
if (!frame) return;
|
||||
const updateSize = () => {
|
||||
const rect = frame.getBoundingClientRect();
|
||||
setFrameSize({ width: Math.max(320, rect.width), height: Math.max(320, rect.height) });
|
||||
};
|
||||
updateSize();
|
||||
const observer = new ResizeObserver(updateSize);
|
||||
observer.observe(frame);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
let loadingTask: pdfjs.PDFDocumentLoadingTask | undefined;
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
setDocumentProxy(null);
|
||||
restoredRef.current = false;
|
||||
|
||||
async function loadDocument() {
|
||||
try {
|
||||
configurePdfWorker(pdfjs);
|
||||
loadingTask = pdfjs.getDocument({ url, withCredentials: true });
|
||||
const loadedDocument = await loadingTask.promise;
|
||||
if (cancelled) {
|
||||
await loadingTask.destroy();
|
||||
return;
|
||||
}
|
||||
setPages(loadedDocument.numPages);
|
||||
setDocumentProxy(loadedDocument);
|
||||
setLoading(false);
|
||||
} catch (loadError) {
|
||||
if (cancelled && isPdfTransitionError(loadError)) return;
|
||||
if (!cancelled) {
|
||||
setError(pdfReaderErrorMessage(loadError));
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void enqueuePdfLifecycle(loadDocument);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
void loadingTask?.destroy();
|
||||
};
|
||||
}, [attempt, url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== "vertical") return;
|
||||
const root = scrollContainerFor(frameRef.current);
|
||||
if (!root || !documentProxy) return;
|
||||
const handleScroll = () => {
|
||||
const rootRect = root.getBoundingClientRect();
|
||||
const frames = Array.from(frameRef.current?.querySelectorAll<HTMLElement>("[data-reader-page]") ?? []);
|
||||
const active = frames
|
||||
.map((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const overlap = Math.min(rootRect.bottom, rect.bottom) - Math.max(rootRect.top, rect.top);
|
||||
const anchor = Math.max(0, Math.min(1, (rootRect.top - rect.top) / Math.max(1, rect.height)));
|
||||
return { page: Number(element.dataset.readerPage), overlap, anchor };
|
||||
})
|
||||
.filter((item) => Number.isInteger(item.page) && item.overlap > 0)
|
||||
.sort((left, right) => right.overlap - left.overlap)[0];
|
||||
if (!active) return;
|
||||
setVisiblePage(active.page);
|
||||
onPageCommit(active.page, pages, active.anchor, "queued");
|
||||
};
|
||||
root.addEventListener("scroll", handleScroll, { passive: true });
|
||||
handleScroll();
|
||||
return () => root.removeEventListener("scroll", handleScroll);
|
||||
}, [documentProxy, mode, onPageCommit, pages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== "vertical" || !documentProxy || restoredRef.current) return;
|
||||
restoredRef.current = true;
|
||||
requestAnimationFrame(() => {
|
||||
frameRef.current?.querySelector(`[data-reader-page="${currentPage}"]`)?.scrollIntoView({ block: "start" });
|
||||
setVisiblePage(currentPage);
|
||||
});
|
||||
}, [currentPage, documentProxy, mode]);
|
||||
|
||||
return (
|
||||
<div className={`pdf-reader pdf-reader-${mode}`} ref={frameRef}>
|
||||
{error ? (
|
||||
if (documentError) {
|
||||
return (
|
||||
<div className="pdf-reader" ref={frameRef}>
|
||||
<ReaderError
|
||||
title="Lecture PDF indisponible"
|
||||
detail={error}
|
||||
detail={documentError.detail}
|
||||
technicalDetail={documentError.technicalDetail}
|
||||
downloadUrl={url}
|
||||
backHref={backHref}
|
||||
onRetry={() => setAttempt((value) => value + 1)}
|
||||
onRetry={() => setDocumentAttempt((value) => value + 1)}
|
||||
/>
|
||||
) : loading || !documentProxy ? (
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pdf-reader" ref={frameRef}>
|
||||
{loadingDocument || !documentProxy ? (
|
||||
<div className="reader-fallback">
|
||||
<span>Ouverture PDF</span>
|
||||
</div>
|
||||
) : mode === "vertical" ? (
|
||||
<div className="pdf-strip">
|
||||
{Array.from({ length: pages }, (_, index) => (
|
||||
<PdfPageCanvas key={index + 1} document={documentProxy} pageNumber={index + 1} mode={mode} frameSize={frameSize} onError={handlePageError} />
|
||||
))}
|
||||
) : pageError ? (
|
||||
<div className="pdf-page-frame" data-reader-page={currentPage}>
|
||||
<div className="reader-fallback reader-fallback-error">
|
||||
<span>{pageError.detail}</span>
|
||||
<button className="ghost-button" onClick={() => setPageAttempt((value) => value + 1)}>
|
||||
Reessayer
|
||||
</button>
|
||||
<details>
|
||||
<summary>Détail technique</summary>
|
||||
<pre>{pageError.technicalDetail}</pre>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<PdfPageCanvas document={documentProxy} pageNumber={currentPage} mode={mode} frameSize={frameSize} onError={handlePageError} />
|
||||
<div className="pdf-page-frame" data-reader-page={currentPage}>
|
||||
{(!viewportSize || loadingPage || !pageRendered) && (
|
||||
<div className="reader-fallback">
|
||||
<span>Page {currentPage}</span>
|
||||
</div>
|
||||
)}
|
||||
<canvas ref={canvasRef} data-pdf-render-status={pageStatus} style={{ visibility: showCanvas ? "visible" : "hidden" }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user