feat(reading): lecture adaptative pour mauvaises connexions — manifest de lecture avec fenêtre priorisée autour de la position du lecteur et préchargement progressif des pages voisines, ressources authentifiées servies avec Range/ETag (module reading API: controller/service/dto/ports, source archive et cache reconstructible filesystem), client web adaptatif useAdaptiveReading avec enrichissement du cache au fil de la lecture, EPUB chargé par ressources manifestées via loader Foliate avec credentials (fini le téléchargement complet du fichier), optimisation des images EPUB et fallback page PDF allégée, avec tests api/web — évolution #53

Ouvrir rapidement un très grand livre et continuer la lecture sans attendre le chargement complet sur très mauvaise connexion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Git Agent
2026-09-29 23:56:42 +02:00
parent bf5441d5c4
commit bdc6d58d45
27 changed files with 1384 additions and 95 deletions

View File

@ -14,6 +14,7 @@ import {
import { readerMovedBeyondTap, readerPinchZoom, readerPointDistance, type ReaderPoint } from "./readerGestures";
import { readerZoomLabel, READER_ZOOM_DEFAULT, READER_ZOOM_MAX, READER_ZOOM_MIN } from "./readerLayout";
import type { ReaderMode } from "./readerScroll";
import type { ReadingConnectionMode } from "../api/types";
export type ReaderControls = {
canPrevious: boolean;
@ -36,6 +37,11 @@ export type ReaderModeControls = {
onModeChange: (mode: ReaderMode) => void;
};
export type ReaderConnectionControls = {
mode: ReadingConnectionMode;
onModeChange: (mode: ReadingConnectionMode) => void;
};
type ReaderShellProps = {
title: string;
backHref: string;
@ -44,6 +50,8 @@ type ReaderShellProps = {
controls: ReaderControls;
zoomControls?: ReaderZoomControls;
modeControls?: ReaderModeControls;
connectionControls?: ReaderConnectionControls;
activity?: string;
children: ReactNode;
};
@ -105,7 +113,7 @@ function scheduleReaderZoomAnchorRestore(stage: HTMLElement, anchor: ReaderZoomA
});
}
export function ReaderShell({ title, backHref, error, onRetry, controls, zoomControls, modeControls, children }: ReaderShellProps) {
export function ReaderShell({ title, backHref, error, onRetry, controls, zoomControls, modeControls, connectionControls, activity, children }: ReaderShellProps) {
const readerRef = useRef<HTMLDivElement>(null);
const headerRef = useRef<HTMLElement>(null);
const stageRef = useRef<HTMLElement>(null);
@ -368,6 +376,16 @@ export function ReaderShell({ title, backHref, error, onRetry, controls, zoomCon
</select>
</label>
)}
{connectionControls && (
<label className="reader-mode-control reader-connection-control">
<span>Mode connexion lente</span>
<select value={connectionControls.mode} onChange={(event) => connectionControls.onModeChange(event.currentTarget.value as ReadingConnectionMode)}>
<option value="auto">Automatique</option>
<option value="speed">Toujours privilégier la vitesse</option>
<option value="quality">Toujours privilégier la qualité</option>
</select>
</label>
)}
<button className="ghost-button reader-nav-button reader-previous-button" onClick={controls.onPrevious} disabled={!controls.canPrevious} aria-label="Page precedente">
<ArrowLeft size={16} />
<span>Precedent</span>
@ -405,6 +423,11 @@ export function ReaderShell({ title, backHref, error, onRetry, controls, zoomCon
)}
</div>
)}
{!error && activity && (
<div className="reader-status reader-activity" role="status" aria-live="polite">
<span>{activity}</span>
</div>
)}
<main className="reader-stage" ref={stageRef}>
<div className="reader-content">{children}</div>
</main>