fix(web): lecteurs EPUB/PDF — gestion d'erreurs, worker pdf.js local
Composant ReaderError avec diagnostic et actions de repli, worker pdf.js servi localement (pdfWorker) pour éviter les CDN, typage du view foliate, navigation retour vers la fiche livre et styles lecteur associés. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,21 +1,36 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import * as pdfjs from "pdfjs-dist";
|
||||
import workerUrl from "pdfjs-dist/build/pdf.worker.mjs?url";
|
||||
import { ReaderError, readerErrorMessage } from "./ReaderError";
|
||||
import { pdfWorkerSrc } from "./pdfWorker";
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = pdfWorkerSrc;
|
||||
|
||||
export function PdfReader({ url, page, onPageCommit }: { url: string; page: number; onPageCommit: (page: number, pages: number) => void }) {
|
||||
export function PdfReader({
|
||||
url,
|
||||
page,
|
||||
backHref,
|
||||
onPageCommit
|
||||
}: {
|
||||
url: string;
|
||||
page: number;
|
||||
backHref: string;
|
||||
onPageCommit: (page: number, pages: number) => void;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const [pages, setPages] = useState(1);
|
||||
const [error, setError] = useState<string>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [attempt, setAttempt] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
let loadingTask: pdfjs.PDFDocumentLoadingTask | undefined;
|
||||
let renderTask: pdfjs.RenderTask | undefined;
|
||||
async function render() {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
const loadingTask = pdfjs.getDocument({ url, withCredentials: true });
|
||||
loadingTask = pdfjs.getDocument({ url, withCredentials: true });
|
||||
const document = await loadingTask.promise;
|
||||
if (cancelled) return;
|
||||
setPages(document.numPages);
|
||||
@ -27,37 +42,53 @@ export function PdfReader({ url, page, onPageCommit }: { url: string; page: numb
|
||||
canvas.height = viewport.height;
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) return;
|
||||
await pdfPage.render({ canvas, canvasContext: context, viewport }).promise;
|
||||
renderTask = pdfPage.render({ canvas, canvasContext: context, viewport });
|
||||
await renderTask.promise;
|
||||
if (!cancelled) setLoading(false);
|
||||
} catch (renderError) {
|
||||
setError(renderError instanceof Error ? renderError.message : "PDF indisponible");
|
||||
if (!cancelled) {
|
||||
setError(readerErrorMessage(renderError, "PDF indisponible"));
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
render();
|
||||
void render();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
renderTask?.cancel();
|
||||
void loadingTask?.destroy();
|
||||
};
|
||||
}, [url, page, attempt]);
|
||||
|
||||
return (
|
||||
<div className="pdf-reader">
|
||||
{error ? (
|
||||
<div className="reader-fallback">
|
||||
<span>{error}</span>
|
||||
<button className="ghost-button" onClick={() => setAttempt((value) => value + 1)}>
|
||||
Reessayer
|
||||
</button>
|
||||
</div>
|
||||
<ReaderError
|
||||
title="Lecture PDF indisponible"
|
||||
detail="ReadaBook n'a pas pu ouvrir ce fichier dans le lecteur web."
|
||||
technicalDetail={error}
|
||||
downloadUrl={url}
|
||||
backHref={backHref}
|
||||
onRetry={() => setAttempt((value) => value + 1)}
|
||||
/>
|
||||
) : (
|
||||
<canvas ref={canvasRef} />
|
||||
<>
|
||||
{loading && (
|
||||
<div className="reader-fallback">
|
||||
<span>Ouverture PDF</span>
|
||||
</div>
|
||||
)}
|
||||
<canvas ref={canvasRef} />
|
||||
</>
|
||||
)}
|
||||
<div className="reader-stepper">
|
||||
<button className="ghost-button" onClick={() => onPageCommit(Math.max(1, page - 1), pages)}>
|
||||
Precedent
|
||||
<button className="ghost-button" onClick={() => onPageCommit(Math.max(1, page - 1), pages)} disabled={Boolean(error) || loading}>
|
||||
Précédent
|
||||
</button>
|
||||
<span>
|
||||
{page} / {pages}
|
||||
</span>
|
||||
<button className="ghost-button" onClick={() => onPageCommit(Math.min(pages, page + 1), pages)}>
|
||||
<button className="ghost-button" onClick={() => onPageCommit(Math.min(pages, page + 1), pages)} disabled={Boolean(error) || loading}>
|
||||
Suivant
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user