Régression worker PDF : le worker pdf.js est désormais instancié une seule fois via un port dédié (?worker&url) et reconfiguré à chaque montage, au lieu d'un workerSrc recalculé qui cassait le rendu. - ReaderShell : chrome commun aux lecteurs (toolbar, zones de tap, statut) et contrat ReaderControls pour EPUB/PDF/CBZ - préférences de lecture par livre (mode horizontal/vertical, fit) : table reader_preferences + migrations idempotentes, module API, DTO partagés, client web avec fallback localStorage hors-ligne Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75 lines
2.3 KiB
TypeScript
75 lines
2.3 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
import { api } from "../api/client";
|
|
import type { CbzPagesDto, ReaderMode } from "../api/types";
|
|
import type { ReaderControls } from "./ReaderShell";
|
|
|
|
export function CbzReader({
|
|
bookId,
|
|
page,
|
|
mode,
|
|
onPageCommit,
|
|
onControlsChange
|
|
}: {
|
|
bookId: number;
|
|
page: number;
|
|
mode: ReaderMode;
|
|
onPageCommit: (page: number, pages: number) => void;
|
|
onControlsChange: (controls: ReaderControls) => void;
|
|
}) {
|
|
const [pages, setPages] = useState<CbzPagesDto | null>(null);
|
|
const [error, setError] = useState<string>();
|
|
const [imageError, setImageError] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let alive = true;
|
|
setError(undefined);
|
|
api
|
|
.cbzPages(bookId)
|
|
.then((nextPages) => {
|
|
if (!alive) return;
|
|
setPages(nextPages);
|
|
if (page > nextPages.pageCount) onPageCommit(nextPages.pageCount, nextPages.pageCount);
|
|
})
|
|
.catch(() => {
|
|
if (alive) setError("Archive CBZ indisponible.");
|
|
});
|
|
return () => {
|
|
alive = false;
|
|
};
|
|
}, [bookId]);
|
|
|
|
const pageCount = pages?.pageCount ?? 1;
|
|
const currentPage = Math.max(1, Math.min(page, pageCount));
|
|
const currentName = pages?.pages.find((item) => item.page === currentPage)?.name;
|
|
|
|
const go = useCallback((nextPage: number) => {
|
|
setImageError(false);
|
|
onPageCommit(Math.max(1, Math.min(nextPage, pageCount)), pageCount);
|
|
}, [onPageCommit, pageCount]);
|
|
|
|
useEffect(() => {
|
|
onControlsChange({
|
|
canPrevious: !error && !imageError && currentPage > 1,
|
|
canNext: !error && !imageError && currentPage < pageCount,
|
|
positionLabel: pages ? `${currentPage} / ${pageCount}` : "Ouverture archive",
|
|
onPrevious: () => go(currentPage - 1),
|
|
onNext: () => go(currentPage + 1)
|
|
});
|
|
}, [currentPage, error, go, imageError, onControlsChange, pageCount, pages]);
|
|
|
|
return (
|
|
<div className={`cbz-reader cbz-reader-${mode}`}>
|
|
{error || imageError ? (
|
|
<div className="reader-fallback">
|
|
<span>{error ?? "Page CBZ indisponible."}</span>
|
|
<button className="ghost-button" onClick={() => go(currentPage)}>
|
|
Reessayer
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<img src={api.cbzPageUrl(bookId, currentPage)} alt={currentName ?? `Page ${currentPage}`} onError={() => setImageError(true)} />
|
|
)}
|
|
</div>
|
|
);
|
|
}
|