fix(web,api): lecteur — worker pdf.js dédié, shell commun et préférences par livre
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>
This commit is contained in:
@ -1,15 +1,20 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { api } from "../api/client";
|
||||
import type { CbzPagesDto } from "../api/types";
|
||||
import type { CbzPagesDto, ReaderMode } from "../api/types";
|
||||
import type { ReaderControls } from "./ReaderShell";
|
||||
|
||||
export function CbzReader({
|
||||
bookId,
|
||||
page,
|
||||
onPageCommit
|
||||
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>();
|
||||
@ -37,13 +42,23 @@ export function CbzReader({
|
||||
const currentPage = Math.max(1, Math.min(page, pageCount));
|
||||
const currentName = pages?.pages.find((item) => item.page === currentPage)?.name;
|
||||
|
||||
function go(nextPage: number) {
|
||||
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">
|
||||
<div className={`cbz-reader cbz-reader-${mode}`}>
|
||||
{error || imageError ? (
|
||||
<div className="reader-fallback">
|
||||
<span>{error ?? "Page CBZ indisponible."}</span>
|
||||
@ -54,17 +69,6 @@ export function CbzReader({
|
||||
) : (
|
||||
<img src={api.cbzPageUrl(bookId, currentPage)} alt={currentName ?? `Page ${currentPage}`} onError={() => setImageError(true)} />
|
||||
)}
|
||||
<div className="reader-stepper">
|
||||
<button className="ghost-button" onClick={() => go(currentPage - 1)}>
|
||||
Precedent
|
||||
</button>
|
||||
<span>
|
||||
{currentPage} / {pageCount}
|
||||
</span>
|
||||
<button className="ghost-button" onClick={() => go(currentPage + 1)}>
|
||||
Suivant
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user