fix(web): lecteur PDF/CBZ — pages blanches, timeouts et modes fullscreen

- Rendu PDF via module dédié pdfRender (worker), correction des pages
  blanches et gestion des timeouts de rendu
- Lecteur comics : crop plein écran, locators de page unifiés (pdf/cbz/cbr)
  avec ancrage pour le calcul de progression
- Sauvegarde de progression mise en file (queueSave) pour éviter les
  pertes lors des navigations rapides
- nginx/vite : en-têtes et proxy alignés pour le streaming des livres

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Git Agent
2026-08-24 09:11:25 +02:00
parent d79f502dd2
commit 84e789acc4
11 changed files with 494 additions and 104 deletions

View File

@ -3,7 +3,7 @@ import type { BookDto } from "@readabook/shared";
import { api, getApiFallback } from "../api/client";
import { CbzReader } from "../reader/CbzReader";
import { EpubReader } from "../reader/EpubReader";
import { parseCbrPageLocator, parseCbzPageLocator, parsePdfPageLocator, pdfPagePercent } from "../reader/locators";
import { pageLocator, parseCbrPageLocator, parseCbzPageLocator, parsePdfPageLocator, pdfPagePercent } from "../reader/locators";
import { PdfReader } from "../reader/PdfReader";
import { ReaderShell, type ReaderControls } from "../reader/ReaderShell";
import { useReaderPreferences } from "../reader/useReaderPreferences";
@ -24,7 +24,7 @@ export function ReaderPage({ bookId }: { bookId: number }) {
const [page, setPage] = useState(1);
const [readerControls, setReaderControls] = useState<ReaderControls>(idleControls);
const [controlsVisible, setControlsVisible] = useState(true);
const { progress, saving, error: progressError, save } = useReaderProgress(bookId);
const { progress, saving, error: progressError, save, queueSave } = useReaderProgress(bookId);
const { preferences, setMode, error: preferencesError } = useReaderPreferences(bookId);
async function loadBook() {
@ -58,20 +58,26 @@ export function ReaderPage({ bookId }: { bookId: number }) {
const fileUrl = useMemo(() => api.bookFileUrl(bookId), [bookId]);
const backHref = useMemo(() => (book ? `/book/${book.id}` : "/home"), [book]);
const savePdfPage = useCallback(
(nextPage: number, pages: number) => {
(nextPage: number, pages: number, anchor = 1, strategy: "immediate" | "queued" = "immediate") => {
setPage(nextPage);
void save(`pdf:page:${nextPage}`, pdfPagePercent(nextPage, pages));
const locator = pageLocator("pdf", nextPage, anchor);
const percent = pdfPagePercent(nextPage, pages, anchor);
if (strategy === "queued") queueSave(locator, percent);
else void save(locator, percent);
},
[save]
[queueSave, save]
);
const saveEpubLocator = useCallback((locator: string, percent: number) => void save(locator, percent), [save]);
const saveComicPage = useCallback(
(nextPage: number, pages: number) => {
(nextPage: number, pages: number, anchor = 1, strategy: "immediate" | "queued" = "immediate") => {
setPage(nextPage);
const prefix = book?.format === "cbr" ? "cbr" : "cbz";
void save(`${prefix}:page:${nextPage}`, pdfPagePercent(nextPage, pages));
const locator = pageLocator(prefix, nextPage, anchor);
const percent = pdfPagePercent(nextPage, pages, anchor);
if (strategy === "queued") queueSave(locator, percent);
else void save(locator, percent);
},
[book?.format, save]
[book?.format, queueSave, save]
);
const readerError = error ?? progressError ?? preferencesError;