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:
@@ -1,11 +1,20 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inherits LayoutComponentBase
|
||||
@implements IDisposable
|
||||
@inject ServiceLivresApi Api
|
||||
@inject EtatReseau Reseau
|
||||
|
||||
@* 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. *@
|
||||
<header class="bandeau">
|
||||
<a class="marque" href="">MaBibli</a>
|
||||
|
||||
@if (!Reseau.EnLigne)
|
||||
{
|
||||
@* Une pastille dans le bandeau, visible sur tous les écrans : l'état du réseau change
|
||||
l'usage de l'application, il ne doit pas se découvrir au premier clic qui échoue. *@
|
||||
<span class="pastille-hors-ligne" title="@EtatReseau.MotifHorsLigne">Hors ligne</span>
|
||||
}
|
||||
|
||||
@if (_utilisateur?.Identifiant is not null)
|
||||
{
|
||||
<span class="utilisateur" title="@(_utilisateur.Simule ? "Utilisateur simulé (développement)" : "Connecté via le portail YunoHost")">
|
||||
@@ -14,13 +23,105 @@
|
||||
}
|
||||
</header>
|
||||
|
||||
@if (!Reseau.EnLigne)
|
||||
{
|
||||
@*
|
||||
Dire d'où viennent les données et de quand elles datent. Sans cette phrase, une
|
||||
bibliothèque affichée hors-ligne est indiscernable d'une bibliothèque à jour — et un
|
||||
livre ajouté depuis un autre appareil manquerait sans explication.
|
||||
*@
|
||||
<p class="bandeau-reseau" role="status">
|
||||
<strong>Hors ligne.</strong>
|
||||
@(Reseau.DerniereSynchro is { } synchro
|
||||
? $" Données enregistrées {Quand(synchro)}. "
|
||||
: " Aucune donnée n'a encore pu être enregistrée sur cet appareil. ")
|
||||
Consultation et recherche fonctionnent ; les modifications sont impossibles.
|
||||
</p>
|
||||
}
|
||||
|
||||
<main class="contenu">
|
||||
@Body
|
||||
</main>
|
||||
|
||||
@code {
|
||||
private UtilisateurCourant? _utilisateur;
|
||||
private bool _etaitEnLigne = true;
|
||||
private bool _synchroEnCours;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
=> _utilisateur = await Api.ObtenirUtilisateurAsync();
|
||||
{
|
||||
Reseau.Change += SurChangementReseau;
|
||||
Reseau.SynchroChange += SurSynchro;
|
||||
|
||||
// Écoute des bascules online/offline avant tout appel : un démarrage hors-ligne doit
|
||||
// aller directement au cache, sans attendre l'échec d'une requête.
|
||||
await Reseau.DemarrerAsync();
|
||||
_etaitEnLigne = Reseau.EnLigne;
|
||||
|
||||
_utilisateur = await Api.ObtenirUtilisateurAsync();
|
||||
|
||||
// Rafraîchit tout le fonds, pas seulement l'écran ouvert : c'est ce qui rend la
|
||||
// bibliothèque entière consultable et cherchable après la coupure.
|
||||
await SynchroniserAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Au retour du réseau, on recharge : la bibliothèque a pu changer depuis un autre appareil,
|
||||
/// et les actions d'écriture redeviennent disponibles dans la foulée.
|
||||
/// </summary>
|
||||
private void SurChangementReseau()
|
||||
{
|
||||
var revenu = Reseau.EnLigne && !_etaitEnLigne;
|
||||
_etaitEnLigne = Reseau.EnLigne;
|
||||
|
||||
_ = InvokeAsync(async () =>
|
||||
{
|
||||
StateHasChanged();
|
||||
|
||||
if (revenu)
|
||||
{
|
||||
_utilisateur = await Api.ObtenirUtilisateurAsync();
|
||||
await SynchroniserAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async Task SynchroniserAsync()
|
||||
{
|
||||
if (_synchroEnCours)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_synchroEnCours = true;
|
||||
|
||||
try
|
||||
{
|
||||
await Api.SynchroniserAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_synchroEnCours = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Date de synchronisation en clair : l'heure suffit le jour même.</summary>
|
||||
private static string Quand(DateTimeOffset instant)
|
||||
{
|
||||
var local = instant.ToLocalTime();
|
||||
|
||||
return local.Date == DateTimeOffset.Now.Date
|
||||
? $"aujourd'hui à {local:HH:mm}"
|
||||
: $"le {local:dd/MM/yyyy} à {local:HH:mm}";
|
||||
}
|
||||
|
||||
/// <summary>La date affichée vient de changer : rien à recharger, juste à redessiner.</summary>
|
||||
private void SurSynchro() => _ = InvokeAsync(StateHasChanged);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Reseau.Change -= SurChangementReseau;
|
||||
Reseau.SynchroChange -= SurSynchro;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,32 @@
|
||||
max-width: 55%;
|
||||
}
|
||||
|
||||
/* L'état du réseau change ce que l'application permet : il se voit dans le bandeau, en
|
||||
permanence, et pas seulement au moment où une action échoue. */
|
||||
.pastille-hors-ligne {
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 999px;
|
||||
background: #f5d76e;
|
||||
color: #4a3800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Dire d'où viennent les données affichées, et de quand elles datent : sans cette phrase, un
|
||||
catalogue hors-ligne est indiscernable d'un catalogue à jour. */
|
||||
.bandeau-reseau {
|
||||
max-width: 46rem;
|
||||
margin: 0 auto;
|
||||
padding: 0.6rem 1rem;
|
||||
background: #fff8e1;
|
||||
border-bottom: 1px solid #e6d28a;
|
||||
color: #6b5200;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.contenu {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user