Files
mabibli/MaBibli.Client/Services/FiltreLivresLocal.cs
T
mathieuandClaude Opus 5 ebc5f95d49 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>
2026-08-18 13:59:19 +02:00

72 lines
3.0 KiB
C#

using MaBibli.Shared.Catalogue;
using MaBibli.Shared.Dtos;
using MaBibli.Shared.Textes;
namespace MaBibli.Client.Services;
/// <summary>
/// Recherche, filtrage et tri du catalogue <b>hors-ligne</b>, appliqués à l'instantané complet
/// rangé en IndexedDB.
/// </summary>
/// <remarks>
/// C'est le pendant navigateur de <see cref="FiltreLivres"/>, qui s'exécute côté base. Les deux
/// ne peuvent pas être le même code : le serveur travaille sur des entités aux colonnes
/// normalisées déjà calculées, le client sur des <see cref="LivreDto"/> qui n'en portent pas.
/// La normalisation est donc refaite ici à la volée — sans coût perceptible sur une bibliothèque
/// de foyer, et surtout par <b>les mêmes fonctions</b> (<see cref="NormalisationTexte"/>,
/// <see cref="RapprochementAuteurs.Cle"/>), ce qui est la seule garantie que « emile » trouve
/// « Émile » des deux côtés.
/// <para>
/// Un test compare les deux implémentations sur les mêmes données : une divergence silencieuse
/// donnerait une bibliothèque qui ne se cherche pas pareil selon qu'on a du réseau ou non.
/// </para>
/// <para>
/// Aucun filtre sur <c>AjoutePar</c>, ici comme côté serveur : la bibliothèque est commune.
/// </para>
/// </remarks>
public static class FiltreLivresLocal
{
public static IReadOnlyList<LivreDto> Appliquer(
IEnumerable<LivreDto> source, CritereLivres criteres)
{
var livres = source;
if (criteres.Format is { } format)
{
livres = livres.Where(l => l.Format == format);
}
if (criteres.Statut is { } statut)
{
// LivreDto.Statut est déjà celui de l'utilisateur courant : le serveur l'a résolu
// avant de l'envoyer. Le client n'a donc personne à désigner, et ne le pourrait pas.
livres = livres.Where(l => l.Statut == statut);
}
if (criteres.AuteurId is { } auteurId)
{
livres = livres.Where(l => l.Auteurs.Any(a => a.Id == auteurId));
}
var terme = NormalisationTexte.Normaliser(criteres.Recherche);
if (terme.Length > 0)
{
var cle = RapprochementAuteurs.Cle(criteres.Recherche);
livres = livres.Where(l =>
NormalisationTexte.Normaliser(l.Titre).Contains(terme, StringComparison.Ordinal)
|| l.Auteurs.Any(a =>
NormalisationTexte.Normaliser(a.Nom).Contains(terme, StringComparison.Ordinal)
|| RapprochementAuteurs.Cle(a.Nom).Contains(cle, StringComparison.Ordinal)));
}
// Même ordre que le serveur : titre normalisé, puis identifiant pour rendre le tri
// déterministe. La comparaison est ordinale parce que c'est ce que fait SQLite sur une
// colonne sans collation — un tri « intelligent » ici ferait diverger les deux vues.
return livres
.OrderBy(l => NormalisationTexte.Normaliser(l.Titre), StringComparer.Ordinal)
.ThenBy(l => l.Id)
.ToList();
}
}