Consulter la bibliotheque hors-ligne depuis un cache IndexedDB
Le piege que CLAUDE.md signale : en Blazor WebAssembly le code tourne dans le navigateur alors que SQLite vit sur le serveur, et le service worker ne met en cache que les assets. Sans travail explicite, l'application demarre hors-ligne et affiche une bibliotheque vide. - js/cache-hors-ligne.js : instantanes JSON dans IndexedDB, et surveillance des bascules online/offline. Aucune logique metier. - CacheHorsLigne / EtatReseau : lecture-ecriture des instantanes, et etat reseau combinant navigator.onLine (fiable seulement par sa negation) avec le sort reel des appels HTTP. - ServiceLivresApi : les lectures retombent sur le cache, les ecritures sont refusees. Le catalogue entier est memorise, pas les reponses filtrees : c'est ce qui rend la recherche hors-ligne possible sur tout le fonds. - FiltreLivresLocal : pendant navigateur de FiltreLivres, avec un test qui confronte les deux implementations sur les memes donnees. - Interface : pastille et bandeau d'etat avec la date de synchronisation, et actions d'ecriture desactivees avec leur raison plutot que boutons morts. - js/mise-a-jour.js : les empreintes WASM etant desactivees, le service worker est le seul cache-busting du projet. L'enregistrement journalise desormais ses echecs, et un bandeau propose la nouvelle version sans attendre la fermeture de tous les onglets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
@page "/livres/{Id:int}/edition"
|
||||
@inject ServiceLivresApi Api
|
||||
@inject NavigationManager Navigation
|
||||
@inject EtatReseau Reseau
|
||||
@implements IDisposable
|
||||
|
||||
@*
|
||||
@@ -24,7 +25,13 @@
|
||||
|
||||
@if (_introuvable)
|
||||
{
|
||||
<p class="message-erreur" role="alert">Ce livre n'existe pas (ou plus).</p>
|
||||
@* Hors-ligne, « introuvable » ne veut pas dire « supprimé » : le livre peut simplement avoir
|
||||
été ajouté après la dernière synchronisation. Le dire évite un contresens inquiétant. *@
|
||||
<p class="message-erreur" role="alert">
|
||||
@(Reseau.EnLigne
|
||||
? "Ce livre n'existe pas (ou plus)."
|
||||
: "Ce livre n'est pas dans les données enregistrées sur cet appareil. Il a peut-être été ajouté depuis la dernière synchronisation.")
|
||||
</p>
|
||||
<a class="bouton bouton-discret" href="">Retour au catalogue</a>
|
||||
}
|
||||
else if (_livre is null || _saisie is null)
|
||||
@@ -37,6 +44,7 @@ else if (ModeEdition)
|
||||
LibelleValidation="Enregistrer les modifications"
|
||||
Erreur="@_erreur"
|
||||
EnCours="_enregistrement"
|
||||
MessageBlocage="@MotifBlocage"
|
||||
OnValider="EnregistrerAsync"
|
||||
OnAnnuler="RetourFiche" />
|
||||
|
||||
@@ -44,7 +52,9 @@ else if (ModeEdition)
|
||||
<section class="zone-danger">
|
||||
@if (!_confirmationSuppression)
|
||||
{
|
||||
<button type="button" class="bouton bouton-danger" @onclick="() => _confirmationSuppression = true">
|
||||
<button type="button" class="bouton bouton-danger"
|
||||
disabled="@(!Reseau.EnLigne)" title="@MotifBlocage"
|
||||
@onclick="() => _confirmationSuppression = true">
|
||||
Supprimer ce livre
|
||||
</button>
|
||||
}
|
||||
@@ -126,10 +136,16 @@ else
|
||||
<h2 class="titre-section">Statut de lecture <span class="champ-aide">(le vôtre)</span></h2>
|
||||
<ChoixStatut Valeur="_livre.Statut"
|
||||
ValeurChanged="ChangerStatutAsync"
|
||||
Desactive="_statutEnCours" />
|
||||
Desactive="@(_statutEnCours || !Reseau.EnLigne)" />
|
||||
@if (!Reseau.EnLigne)
|
||||
{
|
||||
<p class="message-discret">@EtatReseau.MotifHorsLigne</p>
|
||||
}
|
||||
</section>
|
||||
|
||||
<PretsLivre LivreId="Id" Format="_livre.Format" OnChangement="RechargerAsync" />
|
||||
<PretsLivre LivreId="Id" Format="_livre.Format"
|
||||
PreteA="@_livre.PreteA" PreteDepuis="_livre.PreteDepuis"
|
||||
OnChangement="RechargerAsync" />
|
||||
|
||||
<p class="message-discret">
|
||||
Ajouté le @_livre.DateAjout.ToLocalTime().ToString("dd/MM/yyyy")
|
||||
@@ -141,7 +157,15 @@ else
|
||||
</p>
|
||||
|
||||
<nav class="actions-flottantes">
|
||||
<a class="bouton bouton-principal" href="@($"livres/{Id}/edition")">Éditer</a>
|
||||
@if (Reseau.EnLigne)
|
||||
{
|
||||
<a class="bouton bouton-principal" href="@($"livres/{Id}/edition")">Éditer</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="bouton bouton-principal" disabled
|
||||
title="@EtatReseau.MotifHorsLigne">Éditer</button>
|
||||
}
|
||||
<a class="bouton bouton-discret" href="">Catalogue</a>
|
||||
</nav>
|
||||
}
|
||||
@@ -173,12 +197,38 @@ else
|
||||
/// (constaté : l'URL devenait « /edition » sans que l'écran bascule). On s'abonne donc au
|
||||
/// changement d'adresse pour redessiner nous-mêmes.
|
||||
/// </summary>
|
||||
protected override void OnInitialized() => Navigation.LocationChanged += SurNavigation;
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Navigation.LocationChanged += SurNavigation;
|
||||
Reseau.Change += SurChangementReseau;
|
||||
}
|
||||
|
||||
private void SurNavigation(object? emetteur, LocationChangedEventArgs e) =>
|
||||
InvokeAsync(StateHasChanged);
|
||||
|
||||
public void Dispose() => Navigation.LocationChanged -= SurNavigation;
|
||||
/// <summary>Le réseau a basculé : la fiche se relit (serveur ou cache) et les actions suivent.</summary>
|
||||
private void SurChangementReseau() => _ = InvokeAsync(async () =>
|
||||
{
|
||||
_livre = await Api.ObtenirAsync(Id) ?? _livre;
|
||||
|
||||
// On ne réécrit PAS le formulaire en cours d'édition : une coupure réseau au milieu
|
||||
// d'une saisie effacerait ce que l'utilisateur vient de taper.
|
||||
if (_livre is not null && !ModeEdition)
|
||||
{
|
||||
_saisie = Recopier(_livre);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
});
|
||||
|
||||
/// <summary>Raison affichable quand la fiche ne peut pas être modifiée, sinon <c>null</c>.</summary>
|
||||
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Navigation.LocationChanged -= SurNavigation;
|
||||
Reseau.Change -= SurChangementReseau;
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user