From 5d45eb492ed9ae3cb8bc067f22249d0a9788b7bb Mon Sep 17 00:00:00 2001 From: LIMONIER Mathieu Date: Thu, 20 Aug 2026 13:03:14 +0200 Subject: [PATCH] =?UTF-8?q?Traiter=20le=20lot=20A=20d'IDEES.md=20:=20CSS?= =?UTF-8?q?=20onglets,=20pastilles,=20identit=C3=A9=20visuelle,=20couvertu?= =?UTF-8?q?re=20cass=C3=A9e,=20cache=20couvertures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - A1 : forcer la revalidation HTTP (Cache-Control: no-cache) sur les fichiers statiques, pour empêcher un navigateur de garder indéfiniment un CSS/JS périmé quand le service worker ne s'active pas. - A2 : le filtre du catalogue (statut, type de document) reprend les mêmes pastilles colorées que les cartes (Libelles.ClasseStatut / nouvelle Libelles.ClasseTypeDocument). - A3 : remplacement du logo par défaut de Blazor par un pictogramme de livre ouvert (favicon, icônes PWA, logo du bandeau, logo mabibli_ynh). - A4 : une couverture cassée désactive aussi son bouton d'agrandissement. - A5 : cache IndexedDB des couvertures pour la consultation hors-ligne, sans ralentir l'affichage en ligne (mise en cache en tâche de fond). L'allègement (redimensionnement, WebP) reste hors scope, à mesurer avant de s'y engager. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CLAUDE.md | 82 ++++++++++++++++++ IDEES.md | 68 ++------------- MaBibli.Api/Program.cs | 15 +++- MaBibli.Client/Composants/Couverture.razor | 66 ++++++++++++-- .../Composants/FormulaireLivre.razor | 2 +- MaBibli.Client/Composants/Libelles.cs | 12 +++ MaBibli.Client/Layout/MainLayout.razor | 5 +- MaBibli.Client/Layout/MainLayout.razor.css | 9 ++ MaBibli.Client/Pages/Catalogue.razor | 10 ++- MaBibli.Client/Services/CacheHorsLigne.cs | 40 +++++++++ MaBibli.Client/wwwroot/css/app.css | 21 +++++ MaBibli.Client/wwwroot/favicon.png | Bin 1148 -> 230 bytes MaBibli.Client/wwwroot/icon-192.png | Bin 2626 -> 1040 bytes MaBibli.Client/wwwroot/icon-512.png | Bin 6311 -> 3318 bytes MaBibli.Client/wwwroot/js/cache-hors-ligne.js | 49 ++++++++++- MaBibli.Client/wwwroot/logo-bandeau.svg | 13 +++ 16 files changed, 318 insertions(+), 74 deletions(-) create mode 100644 MaBibli.Client/wwwroot/logo-bandeau.svg diff --git a/CLAUDE.md b/CLAUDE.md index 0523cff..52ba41b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1684,6 +1684,88 @@ extérieur ou par Échap. Le calque est focalisé à l'ouverture (`FocusAsync`) **Le substitut à initiale n'est jamais cliquable** : le bouton déclencheur n'est rendu que lorsqu'une URL de couverture existe. Sans couverture, il n'y a rien à agrandir. +## Habillage — retours d'usage du 2026-08-19 traités (2026-08-20) + +Lot A d'IDEES.md (4ᵉ série). Quatre items traités ; le cinquième (allègement des couvertures, +WebP) reste volontairement hors scope, voir plus bas. + +### A1 : ce n'était pas un bug de style, mais l'absence de revalidation HTTP + +Les six onglets s'affichaient parfois en liens bruts, sans aucune règle de +`MainLayout.razor.css`. Cause retenue : `UseStaticFiles()` ne posait **aucun** en-tête +`Cache-Control` — un navigateur peut donc garder `MaBibli.Client.styles.css` en cache +heuristique, **indépendamment du service worker**. Le service worker revalide bien via +`cache: 'no-cache'`, mais seulement quand il parvient à s'activer, ce qui n'est pas garanti +(voir « Le service worker ne s'enregistre pas… »). + +**Correction** : `Program.cs` pose désormais `Cache-Control: no-cache, must-revalidate` sur tous +les fichiers statiques (`StaticFileOptions.OnPrepareResponse`). Les empreintes WASM étant +désactivées (noms de fichiers stables, voir plus haut), c'est cette revalidation forcée qui +empêche un navigateur de servir indéfiniment une version périmée — le service worker reste une +seconde ligne de défense, pas la seule. + +⚠️ `no-cache` ne veut pas dire « ne jamais mettre en cache » : ETag et Last-Modified, déjà posés +par défaut par `StaticFileMiddleware`, permettent une revalidation conditionnelle (304) sans +retélécharger un fichier inchangé. + +### A2 : le filtre reprend les pastilles de couleur des cartes + +Le filtre du catalogue (statut de lecture, type de document) affichait des segments neutres, +alors que `ChoixStatut` (statut personnel sur la fiche) colorait déjà ses segments avec +`Libelles.ClasseStatut`. Les segments et l'étiquette de la carte reprennent maintenant la même +classe — le filtre statut réutilise `ClasseStatut`, et une nouvelle méthode +`Libelles.ClasseTypeDocument` (bleu pour Roman, magenta pour BD, aucune couleur pour +« Non précisé », qui n'est pas une valeur choisie) est appliquée aux deux endroits : le segment +du filtre et l'étiquette `etiquette-type` de la carte. Le formulaire de saisie reprend la même +classe, pour ne pas diverger une troisième fois. + +### A3 : une identité visuelle, encore le logo par défaut de Blazor + +`favicon.png` / `icon-192.png` / `icon-512.png` étaient encore le logo « @ » violet du template +`dotnet new blazorwasm`. Remplacés par un pictogramme de livre ouvert (deux pages en V, tranche +centrale blanche), dans le bleu d'accent existant (`#1b3a5c`) — généré par script (Pillow), pas +par un outil de génération d'images IA, pour rester un simple dessin géométrique. Un petit +`logo-bandeau.svg` (blanc, net à toute résolution) accompagne « MaBibli » dans le bandeau. +`mabibli_ynh/logo.png` (256×256, fond transparent — convention du catalogue d'applications +YunoHost) reprend le même dessin. + +### A4 : une couverture cassée ne doit plus rien pouvoir agrandir + +Le `onerror` masquait déjà l'image (`image-cassee`), mais le bouton `.couverture-declencheur` +qui l'enveloppe restait cliquable : cliquer une vignette cassée ouvrait quand même le calque +d'agrandissement, avec la même image cassée dedans. Le `onerror` désactive maintenant aussi ce +bouton (`this.closest('.couverture-declencheur').disabled = true`), et `:disabled` porte un +curseur neutre plutôt que `zoom-in`. + +### A5 : les couvertures se mettent en cache pour la consultation hors-ligne + +Nouveau magasin IndexedDB `couvertures` (clé = URL OpenLibrary, valeur = `Blob`), à côté du +magasin `instantanes` existant — `cache-hors-ligne.js` passe en version 2. + +⚠️ **En ligne, l'affichage ne change pas** : `` pointe directement sur l'URL réseau, +aussi vite qu'avant. La mise en cache (`Cache.MettreEnCacheCouvertureAsync`) est un +fire-and-forget déclenché en parallèle, jamais attendu — elle ne doit rien à la rapidité +d'affichage. Le fetch qu'elle déclenche profite en général du cache HTTP du navigateur (même URL +que l'`` déjà chargée), donc pas de second téléchargement réel dans le cas courant. + +**Hors ligne**, `Couverture.razor` tente d'abord `Cache.LireCouvertureCacheeAsync` (URL `blob:` +via `URL.createObjectURL`) et ne retombe sur l'URL réseau — qui échouera simplement, comme avant +cette fonctionnalité — que si rien n'a été mis en cache. + +⚠️ **Le jeton de résolution compare `(Url, EnLigne)`, pas seulement `Url`** : passer en ligne → +hors ligne sans que l'URL change (le cas le plus courant, puisqu'on ne change pas de livre en +perdant le réseau) est précisément le cas qui aurait été raté avec une mémoïsation sur la seule +URL — la couverture serait restée invisible hors-ligne malgré un cache rempli. + +**Vérifié** : OpenLibrary envoie `Access-Control-Allow-Origin: *` (mesuré via `curl -I`), donc un +`fetch()` cross-origin ordinaire suffit pour lire les octets — pas besoin d'un contournement en +réponse opaque (`mode: 'no-cors'`). + +**Ce qui n'est délibérément pas fait** : l'allègement (redimensionner à la taille réellement +affichée, convertir en WebP). IDEES.md posait la question à mesurer avant de s'y engager, même +règle que pour l'AOT WASM — non fait tant que la taille du cache image n'a pas montré de +problème réel en usage. + ## Installation YunoHost — éprouvée en production le 2026-08-18 Le paquet vit dans le dépôt `mabibli_ynh` (voir « Deux dépôts distincts »). Installé, diff --git a/IDEES.md b/IDEES.md index 94ec3c5..7de93fa 100644 --- a/IDEES.md +++ b/IDEES.md @@ -116,69 +116,17 @@ sans rapport. ## Lot A — L'habillage : la navigation ne ressemble à rien -Le lot qui se voit à chaque écran, et le seul dont le défaut ressemble à une régression. +**A1 à A4 traités le 2026-08-20** (voir `CLAUDE.md`, section « Habillage — retours d'usage du +2026-08-19 traités »). Reste ouvert : -### A1. Les six onglets s'affichent en liens bruts (anomalie, pas une demande) +### A5. Alléger les couvertures (redimensionnement / WebP) -Constaté sur la capture du 2026-08-19 : `CatalogueAuteursSériesRevuesPrêtsEnvies` s'affiche en -bas de page, en bleu souligné, collés les uns aux autres — c'est-à-dire **sans aucune des règles -de `MainLayout.razor.css`** : ni `position: fixed`, ni `display: flex`, ni les libellés espacés. +Le cache hors-ligne des couvertures est fait (voir `CLAUDE.md`), mais chaque affichage en ligne +retélécharge toujours l'image OpenLibrary à sa taille d'origine — rien n'est redimensionné à la +taille réellement affichée, ni converti en WebP. -⚠️ **Ce n'est probablement pas un défaut de mise en forme à refaire, mais une feuille de style -périmée.** Sur la même capture, le bandeau supérieur **est** bleu foncé — or `.bandeau` et -`.onglets` vivent dans le **même fichier** scopé. Une moitié appliquée et l'autre pas ne -s'explique pas par le CSS lui-même : elle s'explique par un `MaBibli.Client.styles.css` **d'avant -l'ajout des onglets**, servi à côté d'un WASM à jour (le balisage, lui, montre bien les six -onglets). - -Hypothèse à vérifier **avant** de toucher au style, sous peine de « corriger » du code sain : - -1. le nom `MaBibli.Client.styles.css` est **stable** (les empreintes sont désactivées, voir - `CLAUDE.md`) — rien d'autre que le service worker ne le fait expirer ; -2. or le service worker **ne s'enregistre pas** dans certains contextes, et un onglet resté - ouvert diffère l'activation d'un nouveau worker. - -Contrôles qui trancheraient : ouvrir `/MaBibli.Client.styles.css` sur le serveur et y chercher -`.onglets` ; comparer avec le fichier du dernier publish ; recharger en vidant le cache. -Si la règle **est** présente et pourtant sans effet, alors seulement chercher du côté de -l'attribut de portée (`b-xxxxx`) que `NavLink` doit relayer via ses attributs additionnels. - -### A2. Les filtres reprennent le vocabulaire visuel des cartes - -Les états *À lire / En cours / Lu* et les types *Roman / BD* s'affichent en pastilles colorées -sur les cartes du catalogue, mais en segments neutres dans le panneau « Filtrer ». Demander la -**même pastille** dans le filtre, pour qu'on reconnaisse ce qu'on filtre. - -### A3. Une identité visuelle : trois images, trois emplacements distincts - -À ne pas confondre, les trois ne vivent pas au même endroit : - -| Où | Quoi | Porté par | -|---|---|---| -| Catalogue d'applications YunoHost | logo de l'app | `mabibli_ynh` (`doc/`, manifeste) | -| Bandeau de l'application | petit logo à côté de « MaBibli » | `MaBibli.Client/wwwroot` | -| Installation PWA (téléphone, PC) | icône d'accueil et écran de démarrage | `manifest.webmanifest` + icônes 192/512 | - -⚠️ La troisième est la seule qui a des contraintes dures (tailles, `purpose: maskable`), et -c'est aussi la seule que le service worker met en cache — donc celle dont un changement se voit -le plus tard. - -### A4. Couvertures : ne pas agrandir ce qui n'existe pas - -Cliquer une vignette absente ne doit rien ouvrir. `CLAUDE.md` affirme que le bouton n'est rendu -que lorsqu'une `CoverUrl` existe — **le cas remonté est donc probablement l'image *cassée*** : -URL présente, mais 502 ou 404 d'OpenLibrary (intermittence documentée). Le `onerror` remplace -l'image ; il devrait aussi **retirer le déclencheur d'agrandissement**. - -### A5. Garder les couvertures en cache, et les alléger - -Aujourd'hui chaque affichage retélécharge l'image chez OpenLibrary — donc rien hors-ligne, et -des vignettes manquantes au gré des 502. Piste : stocker l'image (IndexedDB, à côté des -instantanés) et la réduire à la taille réellement affichée. - -⚠️ À peser : c'est le premier contenu **binaire et volumineux** du cache hors-ligne, dont -`CLAUDE.md` note qu'il pèse aujourd'hui ~2,4 Ko pour 10 livres. Une conversion (WebP) n'a de sens -que si la mesure montre un gain — même règle que pour l'AOT. +⚠️ À peser avant de s'y engager, même règle que pour l'AOT : une conversion n'a de sens que si la +mesure montre un gain réel. Pas encore mesuré à l'échelle d'un vrai fonds. --- diff --git a/MaBibli.Api/Program.cs b/MaBibli.Api/Program.cs index 38357ea..12220c7 100644 --- a/MaBibli.Api/Program.cs +++ b/MaBibli.Api/Program.cs @@ -73,8 +73,21 @@ if (app.Environment.IsDevelopment()) // Le client Blazor WebAssembly est servi comme fichiers statiques par cette API : // un seul processus tourne sur le serveur. +// +// ⚠️ Les empreintes WASM sont désactivées (voir CLAUDE.md) : tous ces fichiers portent des noms +// stables. Sans en-tête Cache-Control, un navigateur peut garder une version périmée en cache +// heuristique (par exemple MaBibli.Client.styles.css) même si le service worker, lui, revalide +// déjà à l'installation — mais seulement quand il parvient à s'activer. `no-cache` force une +// revalidation conditionnelle (ETag/Last-Modified, déjà posés par défaut) à chaque requête : +// pas de re-téléchargement si le fichier n'a pas changé, mais jamais de contenu périmé servi +// sans repasser par le serveur. +var optionsFichiersStatiques = new StaticFileOptions +{ + OnPrepareResponse = ctx => + ctx.Context.Response.Headers.CacheControl = "no-cache, must-revalidate", +}; app.UseBlazorFrameworkFiles(); -app.UseStaticFiles(); +app.UseStaticFiles(optionsFichiersStatiques); app.MapIsbnEndpoints(); app.MapLivresEndpoints(); diff --git a/MaBibli.Client/Composants/Couverture.razor b/MaBibli.Client/Composants/Couverture.razor index fff65ec..8914692 100644 --- a/MaBibli.Client/Composants/Couverture.razor +++ b/MaBibli.Client/Composants/Couverture.razor @@ -1,3 +1,6 @@ +@using MaBibli.Client.Services +@inject CacheHorsLigne Cache +@inject EtatReseau Reseau @* Couverture d'un livre, avec substitut. @@ -10,7 +13,19 @@ le onerror a marqué comme cassé. Agrandissement (IDEES.md) : seulement quand il y a une URL. Sans couverture, il n'y a rien - à agrandir — le substitut à initiale reste un simple décor, jamais un bouton. + à agrandir - le substitut à initiale reste un simple décor, jamais un bouton. + + ⚠️ Image cassée après coup (404/502 intermittent d'OpenLibrary) : le onerror désactive aussi + le bouton déclencheur (this.closest('.couverture-declencheur').disabled = true), pas + seulement l'image. Sans ça, cliquer une vignette cassée agrandissait quand même une image + cassée (A4, IDEES.md). + + Cache hors-ligne (A5, IDEES.md) : EN LIGNE, l'affichage utilise l'URL réseau telle quelle — + rien ne doit ralentir la voie rapide — et une mise en cache démarre en tâche de fond, + sans attendre son résultat, pour que la même couverture reste visible hors-ligne. HORS + LIGNE, on tente d'abord le cache : l'URL réseau échouerait de toute façon. + ⚠️ L'allègement (redimensionner, convertir en WebP) n'est PAS fait ici : à mesurer avant + de s'y engager, comme pour l'AOT — voir IDEES.md. *@
@@ -24,19 +39,19 @@ } else { - Couverture de @Titre @@ -52,7 +67,7 @@ aria-label="Couverture de @Titre" tabindex="-1" @ref="_calque" @onclick="Fermer" @onkeydown="SurToucheAsync"> - Couverture de @Titre @@ -73,9 +88,18 @@ private bool _agrandie; private bool _aFocaliser; private ElementReference _calque; + private string? _urlCachee; + private (string? Url, bool EnLigne)? _urlResoluePour; private bool ADesPixels => !string.IsNullOrWhiteSpace(Url); + /// + /// URL réellement posée en src : l'URL réseau en ligne (voie rapide, inchangée), le + /// blob mis en cache hors-ligne quand il existe, sinon l'URL réseau telle quelle (qui échoue + /// simplement, comme avant cette fonctionnalité). + /// + private string? UrlAffichee => !Reseau.EnLigne && _urlCachee is not null ? _urlCachee : Url; + private string Initiale => string.IsNullOrWhiteSpace(Titre) ? "?" : Titre.Trim()[..1].ToUpperInvariant(); @@ -95,6 +119,33 @@ } } + protected override async Task OnParametersSetAsync() + { + // ⚠️ Le jeton compare aussi l'état réseau, pas seulement l'URL : sans quoi, passer + // en ligne → hors ligne sans que l'URL change (le cas le plus courant) ne relirait + // jamais le cache, et la couverture resterait invisible hors-ligne. + var jeton = (Url, Reseau.EnLigne); + if (!ADesPixels || _urlResoluePour == jeton) + { + return; + } + + _urlResoluePour = jeton; + _urlCachee = null; + + if (Reseau.EnLigne) + { + // Tâche de fond, jamais attendue : la voie d'affichage en ligne ne doit rien à + // ce cache, elle reste aussi rapide qu'avant. + _ = Cache.MettreEnCacheCouvertureAsync(Url!); + } + else + { + _urlCachee = await Cache.LireCouvertureCacheeAsync(Url!); + StateHasChanged(); + } + } + protected override async Task OnAfterRenderAsync(bool premierRendu) { if (_aFocaliser) @@ -104,3 +155,4 @@ } } } + diff --git a/MaBibli.Client/Composants/FormulaireLivre.razor b/MaBibli.Client/Composants/FormulaireLivre.razor index fefc0e9..480ac09 100644 --- a/MaBibli.Client/Composants/FormulaireLivre.razor +++ b/MaBibli.Client/Composants/FormulaireLivre.razor @@ -111,7 +111,7 @@ @foreach (var type in Types) { diff --git a/MaBibli.Client/Composants/Libelles.cs b/MaBibli.Client/Composants/Libelles.cs index b984633..6510825 100644 --- a/MaBibli.Client/Composants/Libelles.cs +++ b/MaBibli.Client/Composants/Libelles.cs @@ -54,4 +54,16 @@ public static class Libelles MaBibli.Shared.Entites.Statut.Lu => "statut-lu", _ => string.Empty, }; + + /// + /// Classe CSS associée au type de document, pour la pastille de couleur (A2, IDEES.md) : + /// le filtre doit reprendre le même vocabulaire visuel que la carte, pas des segments neutres. + /// « Non précisé » n'a pas de couleur : ce n'est pas une valeur choisie. + /// + public static string ClasseTypeDocument(TypeDocument type) => type switch + { + MaBibli.Shared.Entites.TypeDocument.Roman => "type-roman", + MaBibli.Shared.Entites.TypeDocument.BandeDessinee => "type-bd", + _ => string.Empty, + }; } diff --git a/MaBibli.Client/Layout/MainLayout.razor b/MaBibli.Client/Layout/MainLayout.razor index 7643e00..83c73cc 100644 --- a/MaBibli.Client/Layout/MainLayout.razor +++ b/MaBibli.Client/Layout/MainLayout.razor @@ -6,7 +6,10 @@ @* Mise en page pensée mobile d'abord : un bandeau, une colonne, rien à gauche. Le PC hérite de la même colonne, simplement centrée et limitée en largeur. *@
- MaBibli + + + MaBibli + @if (!Reseau.EnLigne) { diff --git a/MaBibli.Client/Layout/MainLayout.razor.css b/MaBibli.Client/Layout/MainLayout.razor.css index 14dfa78..28f597c 100644 --- a/MaBibli.Client/Layout/MainLayout.razor.css +++ b/MaBibli.Client/Layout/MainLayout.razor.css @@ -16,6 +16,15 @@ font-size: 1.15rem; font-weight: 600; text-decoration: none; + display: inline-flex; + align-items: center; + gap: 0.4rem; +} + +.marque-logo { + width: 1.3rem; + height: 1.3rem; + flex: 0 0 auto; } .utilisateur { diff --git a/MaBibli.Client/Pages/Catalogue.razor b/MaBibli.Client/Pages/Catalogue.razor index 84f7d89..e57eab0 100644 --- a/MaBibli.Client/Pages/Catalogue.razor +++ b/MaBibli.Client/Pages/Catalogue.razor @@ -65,7 +65,8 @@ @onclick="() => FiltrerType(null)">Tous @foreach (var type in Enum.GetValues()) { - }
@@ -79,7 +80,8 @@ @onclick="() => FiltrerStatut(null)">Tous @foreach (var statut in Enum.GetValues()) { - } @@ -150,7 +152,9 @@ else if (_livres is not null) « Non précisé » n'apprendrait rien et serait sur presque chaque ligne. *@ @if (livre.TypeDocument != TypeDocument.NonPrecise) { - @Libelles.TypeDocument(livre.TypeDocument) + + @Libelles.TypeDocument(livre.TypeDocument) + } @* Personnel : ce que montre cette pastille est MA lecture, pas celle du foyer. diff --git a/MaBibli.Client/Services/CacheHorsLigne.cs b/MaBibli.Client/Services/CacheHorsLigne.cs index 46e0835..42de125 100644 --- a/MaBibli.Client/Services/CacheHorsLigne.cs +++ b/MaBibli.Client/Services/CacheHorsLigne.cs @@ -151,6 +151,46 @@ public sealed class CacheHorsLigne(IJSRuntime js) : IAsyncDisposable } } + /// + /// Range une couverture en cache pour la consultation hors-ligne, en tâche de fond. + /// + /// + /// Fire-and-forget par nature (A5, IDEES.md) : appelé après l'affichage réussi d'une + /// couverture, sans jamais bloquer ni faire échouer ce qui s'est déjà bien passé. Une + /// couverture qui ne peut pas être mise en cache (quota, stockage refusé, 502 + /// intermittent d'OpenLibrary) reste simplement absente hors-ligne — jamais une erreur + /// visible en ligne. + /// + public async Task MettreEnCacheCouvertureAsync(string url) + { + try + { + var module = await ModuleAsync(); + await module.InvokeVoidAsync("couvertureMettreEnCache", url); + } + catch (Exception) + { + // Voir la remarque ci-dessus : rien à propager. + } + } + + /// + /// Relit une couverture déjà mise en cache, sous forme d'URL d'objet (blob:) utilisable + /// directement comme src. null si elle n'a jamais été mise en cache. + /// + public async Task LireCouvertureCacheeAsync(string url) + { + try + { + var module = await ModuleAsync(); + return await module.InvokeAsync("couvertureLire", url); + } + catch (Exception) + { + return null; + } + } + public async ValueTask DisposeAsync() { if (_module is null) diff --git a/MaBibli.Client/wwwroot/css/app.css b/MaBibli.Client/wwwroot/css/app.css index 885f64b..5548b0b 100644 --- a/MaBibli.Client/wwwroot/css/app.css +++ b/MaBibli.Client/wwwroot/css/app.css @@ -250,6 +250,11 @@ body { .segment.statut-en-cours.segment-actif { background: #b06b00; border-color: #b06b00; } .segment.statut-lu.segment-actif { background: #1f7a44; border-color: #1f7a44; } +/* Type de document : même vocabulaire que les pastilles de statut, deux couleurs distinctes + des trois du statut et de celle du prêt, pour qu'on reconnaisse ce qu'on filtre (A2, IDEES.md). */ +.segment.type-roman.segment-actif { background: #2a6f97; border-color: #2a6f97; } +.segment.type-bd.segment-actif { background: #a13d63; border-color: #a13d63; } + /* --- Boutons --- */ .bouton { @@ -580,6 +585,12 @@ body { cursor: zoom-in; } +/* Image cassée (404/502 intermittent) : le onerror du désactive ce bouton. + Curseur neutre, pour ne plus promettre un agrandissement qui n'ouvrirait rien. */ +.couverture-declencheur:disabled { + cursor: default; +} + .calque-couverture { position: fixed; inset: 0; @@ -638,6 +649,16 @@ body { .etiquette-statut.statut-en-cours { background: #b06b00; } .etiquette-statut.statut-lu { background: #1f7a44; } +/* Pastille du type de document sur la carte du catalogue : mêmes couleurs que le + filtre (A2, IDEES.md), pour reconnaître d'un coup d'œil ce qui a été filtré. */ +.etiquette-type { + color: #fff; + font-weight: 600; +} + +.etiquette-type.type-roman { background: #2a6f97; } +.etiquette-type.type-bd { background: #a13d63; } + /* --- Fiche du livre en lecture seule --- */ .couverture-grande { width: 8rem; height: 12rem; } diff --git a/MaBibli.Client/wwwroot/favicon.png b/MaBibli.Client/wwwroot/favicon.png index 8422b59695935d180d11d5dbe99653e711097819..d36979a06fdd776f1fe0eaf544073bde9aefbd24 100644 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Ydu{YLn`LHy?K!LkOPm)#rc7| zJk+l^RbFvYpR=G{;hm+zT8_?V_L=(GC*z{4jXfr*curcfckSz6e`Nlp+}nJkXy-cV z>+9Fvcw9Hz&$=OX_r-@Y`z21*w4}Lb_wuC4>DI9*?^m6|{nTk^fF5zwvv}2iWu~US s=6)OJMf2^Ew)q^b>It+~Xm>U9FVdQ&MBb@0P~DpVgLXD literal 1148 zcmV-?1cUpDP)9h26h2-Cs%i*@Moc3?#6qJID|D#|3|2Hn7gTIYEkr|%Xjp);YgvFmB&0#2E2b=| zkVr)lMv9=KqwN&%obTp-$<51T%rx*NCwceh-E+=&e(oLO`@Z~7gybJ#U|^tB2Pai} zRN@5%1qsZ1e@R(XC8n~)nU1S0QdzEYlWPdUpH{wJ2Pd4V8kI3BM=)sG^IkUXF2-j{ zrPTYA6sxpQ`Q1c6mtar~gG~#;lt=s^6_OccmRd>o{*=>)KS=lM zZ!)iG|8G0-9s3VLm`bsa6e ze*TlRxAjXtm^F8V`M1%s5d@tYS>&+_ga#xKGb|!oUBx3uc@mj1%=MaH4GR0tPBG_& z9OZE;->dO@`Q)nr<%dHAsEZRKl zedN6+3+uGHejJp;Q==pskSAcRcyh@6mjm2z-uG;s%dM-u0*u##7OxI7wwyCGpS?4U zBFAr(%GBv5j$jS@@t@iI8?ZqE36I^4t+P^J9D^ELbS5KMtZ z{Qn#JnSd$15nJ$ggkF%I4yUQC+BjDF^}AtB7w348EL>7#sAsLWs}ndp8^DsAcOIL9 zTOO!!0!k2`9BLk25)NeZp7ev>I1Mn={cWI3Yhx2Q#DnAo4IphoV~R^c0x&nw*MoIV zPthX?{6{u}sMS(MxD*dmd5rU(YazQE59b|TsB5Tm)I4a!VaN@HYOR)DwH1U5y(E)z zQqQU*B%MwtRQ$%x&;1p%ANmc|PkoFJZ%<-uq%PX&C!c-7ypis=eP+FCeuv+B@h#{4 zGx1m0PjS~FJt}3mdt4c!lel`1;4W|03kcZRG+DzkTy|7-F~eDsV2Tx!73dM0H0CTh zl)F-YUkE1zEzEW(;JXc|KR5{ox%YTh{$%F$a36JP6Nb<0%#NbSh$dMYF-{ z1_x(Vx)}fs?5_|!5xBTWiiIQHG<%)*e=45Fhjw_tlnmlixq;mUdC$R8v#j( zhQ$9YR-o%i5Uc`S?6EC51!bTRK=Xkyb<18FkCKnS2;o*qlij1YA@-nRpq#OMTX&RbL<^2q@0qja!uIvI;j$6>~k@IMwD42=8$$!+R^@5o6HX(*n~^9yR#C$yFHou&M5SlvtkdrAOfKEAy-$JtBv)%+r4KNF2Irp7f(f=3uOO!k-?wL(ilC zFZ#Gy{CiGep=I0oiGIzl>J1+^mwTIASu;FN7xCU^r46J_ip_uLF&sO;;#to3XXT9# zIPUQr*lfSJI{)x`wuC=2N28dHd&v0E1Tg`9Ls~7^B3>hmaEQ?`5(-#-d6}@ z*E4^3T5CoRVTR28%mqK4&(1#!;V~)X7X9^~DcoSfI6;l!5{ttu29P2q1z(0G zoY(|wZ~kID^0(^K>-ExheL9Ra6E1ImuV456&*$TB?4KL5Y}l0T9QS)$%SJiIHwiDY z?`}7kCVZf;ecu!i-QXPWC9(cAYr&I+`lX)^{ocVS@oU%p?Vla*Pn5D_aK8WTca5A) zPa>1Sms%@cxnu8ujARR5p<`)`5>=No(-VIjFTn07tS%h_g7Mqx7~D(${Y*YLy*BpK z*X8;5-`9lLs5g9d-`o9L{ItzahHY|R7k8-290+vZ`}OV^$+FD1zZM;cJ7{A3KXBHbMy5ZZv{bvrlr=gU!)vZ-rUsv zz2~Q};ve?gZ%(fDKWDz5`-w?{{=1ux?#}0YqLQGUZNs_#+rkK^2g++pSvUP!eu%Tt zJ?i(R--+_w8Z6g7owz<*{?pC{NeS!ox8!^}xTER8W#j0*tAEJFVdQ&MBb@0H+tNhX4Qo literal 2626 zcmV-I3cdA-P)v0A9xRwxP|bki~~&uFk>U z#P+PQh zyZ;-jwXKqnKbb6)@RaxQz@vm={%t~VbaZrdbaZrdbaeEeXj>~BG?&`J0XrqR#sSlO zg~N5iUk*15JibvlR1f^^1czzNKWvoJtc!Sj*G37QXbZ8LeD{Fzxgdv#Q{x}ytfZ5q z+^k#NaEp>zX_8~aSaZ`O%B9C&YLHb(mNtgGD&Kezd5S@&C=n~Uy1NWHM`t07VQP^MopUXki{2^#ryd94>UJMYW|(#4qV`kb7eD)Q=~NN zaVIRi@|TJ!Rni8J=5DOutQ#bEyMVr8*;HU|)MEKmVC+IOiDi9y)vz=rdtAUHW$yjt zrj3B7v(>exU=IrzC<+?AE=2vI;%fafM}#ShGDZx=0Nus5QHKdyb9pw&4>4XCpa-o?P(Gnco1CGX|U> z$f+_tA3+V~<{MU^A%eP!8R*-sD9y<>Jc7A(;aC5hVbs;kX9&Sa$JMG!W_BLFQa*hM zri__C@0i0U1X#?)Y=)>JpvTnY6^s;fu#I}K9u>OldV}m!Ch`d1Vs@v9 zb}w(!TvOmSzmMBa9gYvD4xocL2r0ds6%Hs>Z& z#7#o9PGHDmfG%JQq`O5~dt|MAQN@2wyJw_@``7Giyy(yyk(m8U*kk5$X1^;3$a3}N^Lp6hE5!#8l z#~NYHmKAs6IAe&A;bvM8OochRmXN>`D`{N$%#dZCRxp4-dJ?*3P}}T`tYa3?zz5BA zTu7uE#GsDpZ$~j9q=Zq!LYjLbZPXFILZK4?S)C-zE1(dC2d<7nO4-nSCbV#9E|E1MM|V<9>i4h?WX*r*ul1 z5#k6;po8z=fdMiVVz*h+iaTlz#WOYmU^SX5#97H~B32s-#4wk<1NTN#g?LrYieCu> zF7pbOLR;q2D#Q`^t%QcY06*X-jM+ei7%ZuanUTH#9Y%FBi*Z#22({_}3^=BboIsbg zR0#jJ>9QR8SnmtSS6x($?$}6$x+q)697#m${Z@G6Ujf=6iO^S}7P`q8DkH!IHd4lB zDzwxt3BHsPAcXFFY^Fj}(073>NL_$A%v2sUW(CRutd%{G`5ow?L`XYSO*Qu?x+Gzv zBtR}Y6`XF4xX7)Z04D+fH;TMapdQFFameUuHL34NN)r@aF4RO%x&NApeWGtr#mG~M z6sEIZS;Uj1HB1*0hh=O@0q1=Ia@L>-tETu-3n(op+97E z#&~2xggrl(LA|giII;RwBlX2^Q`B{_t}gxNL;iB11gEPC>v` zb4SJ;;BFOB!{chn>?cCeGDKuqI0+!skyWTn*k!WiPNBf=8rn;@y%( znhq%8fj2eAe?`A5mP;TE&iLEmQ^xV%-kmC-8mWao&EUK_^=GW-Y3z ksi~={si~={skwfB0gq6itke#r1ONa407*qoM6N<$g11Kq@c;k- diff --git a/MaBibli.Client/wwwroot/icon-512.png b/MaBibli.Client/wwwroot/icon-512.png index c2dd4842dc93df73c322218ee03eca142a19a338..c4d1fc1e4573035db5c0220f531e54113ed46e68 100644 GIT binary patch literal 3318 zcmeHKZBSF$8Gi0fkl+$PaUqB%DhS3|YsS9BzR>sKUTE{HHL zL27XT3yO}mxURAwf?7Uu5d@29$l_Kl3kDFkd)_nh}U z@AJO*o_Egk-XrUR0{D}sPX@qWyJq!o0g|1P;7wv5!}5D$fMwO%)xI0$`GakDG8`L7 z|Mg`$o60>dM{jDE+eJO@yq#pd%!j({e|C1E@Yc4lIiWpbfasdbYj=fEkU;IVNRI8~&^??bvS%7w=kQ9c z`Dr2fwY_YVkZbI|O6m+huQk_w*!p;3@j&|GaSTL+F5zY?)~AXnr|9&<^r8jzL!7u& z*)f}3gP-6aii!h;WXqPy4s$8D_1x^Cm$y??-Z*c3%0wC%}BpYA%t zQv5zTYc)M&zhj)p)h?6sEGHbGx27+~9P%y~UJmkCwm>@4LUjaz<@n}0Umm$)BUGc4 z2>L5W_qt;>Gp;r|eKKLjA+W4J19vp+oWx$RSWd5WB*f&nAM%n4SniI!;J`aPu^g zsAT7rd>cr*{}TTSaj&eQkL@oqB6I>p>_CiZ7NUr`qyc=I=M%c8BHS&x-*wQ#m_h!| z%)yC`#sIllEPzh&QrH^4#I-l()Xv0h=_idopO}2=hx%N+XPR<*-ndM`1$=|es0uc- zmQu$-cA4xo15XWvG+Gl~W()Af)|qIQC+sOT(FAkoHg7a_I<=b6?R@NKATBJBT{EJm z0ye|7h#R-YZbj^ElSiH@@ZX2d2JUAI(U%>`)gPdNL2>rg@-_>A%Z~HrlwOID7NvXp zTZ@rvofR|;A(dr@{%A~%9Y(BahYu0BRO*|q6$GeR#9QKjLD*b*?L?tj|8I^q&cLiS z><9S+e#Y;A2xwlgDI9SZn8cxmiTI(tu-{FBP)`^d)Qsl~eNx$Sg1R}dv^_TQq?#Gq zo%1a}EEx2;qHCWpTFpn6QZEvd+-rALwvf4*ppu%zBcjeLsCR*m{0)DiG=)(4f=+5& z#mvi`q6;IjZ|lIT?khOz1l`(HL;MYRv2-g`6Jaa;sZ3i)dvdz2pxp&)b_Hk@YP<2n z{>OR9{$k)^|9zEM>PJ$&4)H}}OI<8;Pe3|7^^{hwqMNu_wP>WNaR*UqgRsWt4INh~ zEe}BvJso=Wf`oEvF%SEq20C&dt-3*ZT42kzo{lh~Q?V;S2Rp>%#d3DX40A=tLvwj=cE*hq{KT@XDByee+%2UjV z5Q_*gZt1h52?fy8DyD9U(aW z9-RLBP=UYX@c!He|37DOI=rYF(r_j7vRlEtxvxpccyy4_-~5L3cY@TgUi93fj#LH0 zN8TI|pOH-5oeQb?VPEe*wU|kAK#9bC`Y->A0$1D;_iRoks$G$r?HiR69_m#=Uu5r_ zg?&x+y=3WB%xFAa+pl)bpC9K;&>Jj0@*8@|fTaidqNvRSXW7fm)kTHAaGZ@m6WxE5Z2h zOC+(aTvjnQhkc*%(uts-(Y}owT4eSgYT8KXb_2!b4VQirZdOe1ap-P98pyQCU0Jn% ztCsZR_V_wxGe&0s7d1u+uW~>T0V&`6AA8>6deR8pLZE4|%IzJa-Ee8v_-p?j-)#$R|PPo@(391`%K16w_Q_g+g|! z&I!V5K-KYm;0TZH9k!UvahEjF&k9XDVfs?XfhlaFi5@17>mv`^H=B0yBfq`#lJ#*Z z>E^$RA#~G#u9AKWiU%rkpsJ+8o3tU=Hm_Xx+yN{>Wb6fNf}~F$tvus3&qQ_2%9Aic zx>AJ(o(~tay!Ak1lqsFE6NQPqb*xDedZ4esM8r0Wh6eZnVd{*WaZk(virVUK8|KWK z0-v^h+EM1sZsqj{YgoD2QETJ`Dx;_Om-3Vz(r)Jn7gIU zX*TF7<$kG5%-8lW*6>iACM)Sshd%f7oJwNl&d@!>A~%K<9U;(HtIRoY#^`)gt2~~d z&8&lu#%|_Bz-xEiF#$qyv`jYSX|s@k literal 6311 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 diff --git a/MaBibli.Client/wwwroot/js/cache-hors-ligne.js b/MaBibli.Client/wwwroot/js/cache-hors-ligne.js index d062bae..b3c0c98 100644 --- a/MaBibli.Client/wwwroot/js/cache-hors-ligne.js +++ b/MaBibli.Client/wwwroot/js/cache-hors-ligne.js @@ -13,7 +13,11 @@ const NOM_BASE = 'mabibli'; const MAGASIN = 'instantanes'; -const VERSION = 1; +// Couvertures mises en cache (A5, IDEES.md) : magasin séparé, clé = l'URL OpenLibrary, +// valeur = le Blob de l'image. Sert uniquement à la consultation hors-ligne — en ligne, +// l' normale reste la voie rapide, ce cache se remplit en tâche de fond à côté. +const MAGASIN_COUVERTURES = 'couvertures'; +const VERSION = 2; let promesseBase = null; @@ -29,6 +33,7 @@ function ouvrir() { requete.onupgradeneeded = () => { const base = requete.result; if (!base.objectStoreNames.contains(MAGASIN)) base.createObjectStore(MAGASIN); + if (!base.objectStoreNames.contains(MAGASIN_COUVERTURES)) base.createObjectStore(MAGASIN_COUVERTURES); }; requete.onsuccess = () => resoudre(requete.result); requete.onerror = () => rejeter(requete.error); @@ -84,3 +89,45 @@ export function surveiller(reference) { self.addEventListener('offline', prevenir); return navigator.onLine !== false; } + +// --- Couvertures (A5, IDEES.md) --- +// +// En ligne, l' normale reste la voie d'affichage : rien ici ne doit la ralentir. Cette +// fonction tourne en tâche de fond, appelée sans attendre son résultat (fire-and-forget) après +// qu'une couverture s'est affichée avec succès. Le second fetch profite en général du cache HTTP +// du navigateur (même URL que l') : pas de second téléchargement réel dans le cas courant. +// +// OpenLibrary envoie `Access-Control-Allow-Origin: *` (vérifié) : un fetch cross-origin normal +// suffit, pas besoin du contournement `no-cors`/réponse opaque. +export async function couvertureMettreEnCache(url) { + try { + const base = await ouvrir(); + const tx = base.transaction(MAGASIN_COUVERTURES, 'readonly'); + const existe = await attendre(tx.objectStore(MAGASIN_COUVERTURES).getKey(url), tx); + if (existe !== undefined) return; // Déjà en cache : pas de re-téléchargement. + + const reponse = await fetch(url, { cache: 'force-cache' }); + if (!reponse.ok) return; // 404/502 intermittent (CLAUDE.md) : rien à ranger. + + const blob = await reponse.blob(); + const ecriture = base.transaction(MAGASIN_COUVERTURES, 'readwrite'); + await attendre(ecriture.objectStore(MAGASIN_COUVERTURES).put(blob, url), ecriture); + } catch (e) { + // Quota dépassé, stockage refusé : une couverture non mise en cache reste un + // agrément perdu, jamais une raison de faire échouer l'affichage en ligne. + } +} + +// Renvoie une URL d'objet (`blob:`) utilisable comme `src`, ou `null` si cette couverture n'a +// jamais été mise en cache — c'est alors à l'appelant de retomber sur l'URL réseau (qui échouera +// simplement si hors-ligne, exactement comme aujourd'hui). +export async function couvertureLire(url) { + try { + const base = await ouvrir(); + const tx = base.transaction(MAGASIN_COUVERTURES, 'readonly'); + const blob = await attendre(tx.objectStore(MAGASIN_COUVERTURES).get(url), tx); + return blob ? URL.createObjectURL(blob) : null; + } catch (e) { + return null; + } +} diff --git a/MaBibli.Client/wwwroot/logo-bandeau.svg b/MaBibli.Client/wwwroot/logo-bandeau.svg new file mode 100644 index 0000000..dff9508 --- /dev/null +++ b/MaBibli.Client/wwwroot/logo-bandeau.svg @@ -0,0 +1,13 @@ + +