feat(web): zoom du lecteur — contrôles +/−/reset (50–300 %) sur PDF et CBZ

This commit is contained in:
Git Agent
2026-08-24 14:28:53 +02:00
parent b3bd678291
commit fcf1760031
7 changed files with 158 additions and 29 deletions

View File

@ -5,7 +5,8 @@ import { CbzReader } from "../reader/CbzReader";
import { EpubReader } from "../reader/EpubReader";
import { pageLocator, parseCbrPageLocator, parseCbzPageLocator, parsePdfPageLocator, pdfPagePercent } from "../reader/locators";
import { PdfReader } from "../reader/PdfReader";
import { ReaderShell, type ReaderControls } from "../reader/ReaderShell";
import { ReaderShell, type ReaderControls, type ReaderZoomControls } from "../reader/ReaderShell";
import { clampReaderZoom, READER_ZOOM_DEFAULT, READER_ZOOM_STEP } from "../reader/readerLayout";
import { useReaderProgress } from "../reader/useReaderProgress";
const idleControls: ReaderControls = {
@ -21,6 +22,7 @@ export function ReaderPage({ bookId }: { bookId: number }) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string>();
const [page, setPage] = useState(1);
const [zoom, setZoom] = useState(READER_ZOOM_DEFAULT);
const [readerControls, setReaderControls] = useState<ReaderControls>(idleControls);
const { progress, error: progressError, save, queueSave } = useReaderProgress(bookId);
@ -44,6 +46,7 @@ export function ReaderPage({ bookId }: { bookId: number }) {
useEffect(() => {
setReaderControls(idleControls);
setZoom(READER_ZOOM_DEFAULT);
}, [bookId]);
useEffect(() => {
@ -77,6 +80,34 @@ export function ReaderPage({ bookId }: { bookId: number }) {
);
const readerError = error ?? progressError;
const supportsZoom = book?.format === "pdf" || book?.format === "cbz" || book?.format === "cbr";
const changeZoom = useCallback((nextZoom: number | ((currentZoom: number) => number)) => {
const stage = document.querySelector(".reader-stage") as HTMLElement | null;
const scrollRatioX = stage && stage.scrollWidth > stage.clientWidth ? (stage.scrollLeft + stage.clientWidth / 2) / stage.scrollWidth : 0.5;
const scrollRatioY = stage && stage.scrollHeight > stage.clientHeight ? (stage.scrollTop + stage.clientHeight / 2) / stage.scrollHeight : 0.5;
setZoom((currentZoom) => clampReaderZoom(typeof nextZoom === "function" ? nextZoom(currentZoom) : nextZoom));
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (!stage) return;
stage.scrollLeft = Math.max(0, stage.scrollWidth * scrollRatioX - stage.clientWidth / 2);
stage.scrollTop = Math.max(0, stage.scrollHeight * scrollRatioY - stage.clientHeight / 2);
});
});
}, []);
const zoomControls = useMemo<ReaderZoomControls | undefined>(
() =>
supportsZoom
? {
zoom,
onZoomOut: () => changeZoom((currentZoom) => currentZoom - READER_ZOOM_STEP),
onZoomIn: () => changeZoom((currentZoom) => currentZoom + READER_ZOOM_STEP),
onZoomReset: () => changeZoom(READER_ZOOM_DEFAULT)
}
: undefined,
[changeZoom, supportsZoom, zoom]
);
return (
<ReaderShell
@ -85,6 +116,7 @@ export function ReaderPage({ bookId }: { bookId: number }) {
error={readerError}
onRetry={error ? () => void loadBook() : undefined}
controls={readerControls}
zoomControls={zoomControls}
>
{!book ? (
<div className="reader-fallback">
@ -98,6 +130,7 @@ export function ReaderPage({ bookId }: { bookId: number }) {
url={fileUrl}
page={page}
backHref={backHref}
zoom={zoom}
onPageCommit={savePdfPage}
onControlsChange={setReaderControls}
/>
@ -105,6 +138,7 @@ export function ReaderPage({ bookId }: { bookId: number }) {
<CbzReader
bookId={book.id}
page={page}
zoom={zoom}
onPageCommit={saveComicPage}
onControlsChange={setReaderControls}
/>