Traiter le lot A d'IDEES.md : CSS onglets, pastilles, identité visuelle, couverture cassée, cache couvertures
- 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>
This commit is contained in:
co-authored by
Copilot
parent
ce58294fd1
commit
5d45eb492e
@@ -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.
|
||||
*@
|
||||
<div class="couverture @Classe">
|
||||
<span class="couverture-substitut" aria-hidden="true">@Initiale</span>
|
||||
@@ -24,19 +39,19 @@
|
||||
<button type="button" class="couverture-declencheur"
|
||||
aria-label="Agrandir la couverture de @Titre"
|
||||
@onclick="Ouvrir">
|
||||
<img @key="Url"
|
||||
<img @key="UrlAffichee"
|
||||
class="couverture-image"
|
||||
src="@Url"
|
||||
src="@UrlAffichee"
|
||||
alt="Couverture de @Titre"
|
||||
loading="lazy"
|
||||
onerror="this.classList.add('image-cassee')" />
|
||||
onerror="this.classList.add('image-cassee'); const b=this.closest('.couverture-declencheur'); if(b){b.disabled=true;}" />
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<img @key="Url"
|
||||
<img @key="UrlAffichee"
|
||||
class="couverture-image"
|
||||
src="@Url"
|
||||
src="@UrlAffichee"
|
||||
alt="Couverture de @Titre"
|
||||
loading="lazy"
|
||||
onerror="this.classList.add('image-cassee')" />
|
||||
@@ -52,7 +67,7 @@
|
||||
aria-label="Couverture de @Titre"
|
||||
tabindex="-1" @ref="_calque"
|
||||
@onclick="Fermer" @onkeydown="SurToucheAsync">
|
||||
<img class="calque-image" src="@Url" alt="Couverture de @Titre"
|
||||
<img class="calque-image" src="@UrlAffichee" alt="Couverture de @Titre"
|
||||
@onclick:stopPropagation="true"
|
||||
onerror="this.classList.add('image-cassee')" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// URL réellement posée en <c>src</c> : 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é).
|
||||
/// </summary>
|
||||
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 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
@foreach (var type in Types)
|
||||
{
|
||||
<button type="button"
|
||||
class="segment @(Saisie.TypeDocument == type ? "segment-actif" : null)"
|
||||
class="segment @Libelles.ClasseTypeDocument(type) @(Saisie.TypeDocument == type ? "segment-actif" : null)"
|
||||
@onclick="() => Saisie.TypeDocument = type">
|
||||
@Libelles.TypeDocument(type)
|
||||
</button>
|
||||
|
||||
@@ -54,4 +54,16 @@ public static class Libelles
|
||||
MaBibli.Shared.Entites.Statut.Lu => "statut-lu",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static string ClasseTypeDocument(TypeDocument type) => type switch
|
||||
{
|
||||
MaBibli.Shared.Entites.TypeDocument.Roman => "type-roman",
|
||||
MaBibli.Shared.Entites.TypeDocument.BandeDessinee => "type-bd",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user