@page "/auteurs/{AuteurId:int}/bibliographie" @inject ServiceLivresApi Api @inject EtatReseau Reseau MaBibli — bibliographie @if (_bibliographie is null && _erreur is null) {

Bibliographie

Interrogation de la BnF…

} else if (_erreur is not null) {

Bibliographie

@* Une panne de réseau est le plus souvent passagère : sans ce bouton, il fallait recharger la page entière pour retenter. *@ @if (_reessayable) {
} } else if (_bibliographie is { } biblio) {

@if (biblio.EstNouveautes) { Nouveautés de @biblio.Auteur.Nom } else { @biblio.Auteur.Nom }

@* ⚠️ Les deux cas ci-dessous s'excluent, et c'est TOUT l'objet de ce bloc. Avant correction, une BnF muette produisait une liste vide, que l'écran commentait d'un rassurant « la BnF ne connaît aucun livre de cet auteur » — affiché juste sous l'avertissement qui disait le contraire. On affirmait une absence qu'on n'avait pas pu constater, ce qui est la pire des deux erreurs possibles ici : elle ferme la question au lieu de la laisser ouverte. La règle : on n'explique une absence de résultats QUE si la source a répondu. *@ @if (biblio.SourceMuette) {

La bibliographie n'a donc pas pu être établie : cet écran ne dit rien de ce que cet auteur a écrit.

} else if (biblio.Oeuvres.Count == 0) { @if (biblio.EstNouveautes && biblio.SeuilNouveautesInconnu) {

Impossible de dater le livre le plus récent que vous possédez dans les notices reçues. La BnF a répondu, mais aucune nouveauté fiable ne peut être déduite.

} else if (biblio.EstNouveautes) {

Aucune nouveauté trouvée depuis @biblio.AnneeSeuilNouveautes.

} else { @* La source a répondu, et elle ne connaît rien : là, et là seulement, on peut l'expliquer. *@

La BnF ne connaît aucun livre de cet auteur. C'est le cas attendu pour les auteurs étrangers : le dépôt légal français ne couvre que l'édition francophone, et un auteur traduit n'y a souvent pas de notice à son nom.

} } else if (biblio.Avertissement is not null) { @* Source répondue mais bancale (une page sur deux, par exemple) : la liste vaut quelque chose, on la montre en disant qu'elle est peut-être incomplète. *@ } else { @* Ces compteurs disent à l'utilisateur CE QU'IL REGARDE. Sur un auteur très réédité, la liste n'est qu'un extrait : le taire ferait croire qu'un livre absent n'existe pas. *@

@biblio.Oeuvres.Count œuvre@(biblio.Oeuvres.Count > 1 ? "s" : "") — @_possedees déjà chez vous @if (_masquees > 0) { — @_masquees masquée@(_masquees > 1 ? "s" : "") }

@if (biblio.EstNouveautes) {

Œuvres parues après @biblio.AnneeSeuilNouveautes, d'après les éditions BnF reçues.

} @* Deux phrases distinctes plutôt qu'une seule à trous : enchaîner des fragments conditionnels dans une même phrase produit une ponctuation bancale dès qu'un des fragments manque (« exister. .34 notices ont été écartées »). *@

@if (biblio.Tronquee) { D'après le catalogue de la BnF, à partir de @biblio.NoticesLues notices sur les @biblio.NoticesAnnoncees qu'elle annonce : cette liste est un extrait, classé par pertinence. Un livre absent d'ici peut très bien exister. } else { D'après le catalogue de la BnF, sur @biblio.NoticesLues notice@(biblio.NoticesLues > 1 ? "s" : ""). }

@if (biblio.NoticesEcartees > 0) {

@biblio.NoticesEcartees notice@(biblio.NoticesEcartees > 1 ? "s ont" : " a") été écartée@(biblio.NoticesEcartees > 1 ? "s" : "") : autre support (livre audio, jeu) ou ouvrage signé de quelqu'un d'autre.

} @if (_message is not null) { }
@if (_filtresOuverts) {
@if (_masquees > 0) {
}
} @if (_selection.Count > 0) {
} @* Le rapprochement est fait par titre, et il rate des choses (voir CleOeuvre). Le dire franchement vaut mieux que laisser l'utilisateur découvrir seul qu'un livre qu'il possède n'est pas grisé. *@

Le rapprochement avec votre bibliothèque se fait par titre. Un livre que vous possédez sous un titre différent (traduction, intégrale, édition retitrée) n'apparaîtra donc pas comme possédé. Rien n'est masqué : à vous de trancher.

} } @* « Retour aux auteurs » est parti : l'onglet Auteurs fait exactement cela, en permanence. Ne reste que ce qu'aucun onglet ne sait faire — le catalogue restreint à cet auteur. *@ @if (_bibliographie is { } b) { } @code { [Parameter] public int AuteurId { get; set; } [SupplyParameterFromQuery(Name = "nouveautes")] public bool Nouveautes { get; set; } private BibliographieDto? _bibliographie; private int _modePossession; private bool _filtresOuverts; private bool _afficherMasquees; private bool _enCours; private bool _chargement; /// Vrai quand l'échec vient du réseau, donc qu'une nouvelle tentative a un sens. private bool _reessayable; private string? _erreur; private string? _message; private int _possedees; private int _masquees; private IReadOnlyList _livresCatalogue = []; private readonly HashSet _oeuvresOuvertes = []; private readonly HashSet _selection = []; protected override async Task OnParametersSetAsync() => await ChargerAsync(); private IEnumerable Affichees(BibliographieDto biblio) => biblio.Oeuvres.Where(o => (_afficherMasquees || !o.Masquee) && (_modePossession == 0 || (_modePossession == 1 && o.Possede) || (_modePossession == 2 && o.ADecouvrir))); private int NombreFiltres => (_modePossession == 0 ? 0 : 1) + (_masquees > 0 && !_afficherMasquees ? 1 : 0); private async Task ChargerAsync() { _erreur = null; _reessayable = false; _chargement = true; try { _bibliographie = Nouveautes ? await Api.ObtenirNouveautesAsync(AuteurId) : await Api.ObtenirBibliographieAsync(AuteurId); _livresCatalogue = await Api.ListerAsync(new CritereLivres()); if (_bibliographie is null) { // Celui-là ne se retente pas : l'auteur n'existe pas, réessayer ne le fera // pas apparaître. _erreur = "Cet auteur n'existe pas dans votre bibliothèque."; return; } _possedees = _bibliographie.Oeuvres.Count(o => o.Possede); _masquees = _bibliographie.Oeuvres.Count(o => o.Masquee); } catch (Exception) { // ⚠️ Jamais le message brut de l'exception : hors-ligne, c'est un « 404 Not Found » // du service worker que l'utilisateur n'a aucun moyen d'interpréter. La // bibliographie interroge la BnF, elle ne peut PAS venir du cache — il n'y a rien // à réparer, seulement à dire. // // Ce chemin-ci est l'échec de l'appel à NOTRE serveur (délai du navigateur compris) ; // une BnF muette, elle, revient en 200 avec un état, et se traite plus haut. _erreur = Reseau.EnLigne ? "La bibliographie n'a pas pu être obtenue. La BnF est parfois lente à répondre." : "La bibliographie vient de la BnF : indisponible hors ligne."; _reessayable = true; } finally { _chargement = false; } } private void Selectionner(string titre, bool selectionnee) { if (selectionnee) { _selection.Add(titre); } else { _selection.Remove(titre); } } private async Task AjouterSelectionAsync() { if (_bibliographie is null) { return; } var saisies = _bibliographie.Oeuvres .Where(o => _selection.Contains(o.Titre) && o.ADecouvrir) .Select(o => new EnregistrementSouhait { Titre = o.Titre, Auteur = _bibliographie.Auteur.Nom, Editeur = o.Editeur, Annee = o.Annee, Isbn = o.Isbn, }) .ToList(); _enCours = true; _message = null; var resultats = await Api.AjouterSouhaitsAsync(saisies); _enCours = false; var erreurs = resultats.Where(r => !r.EstOk).Select(r => r.Erreur).Where(e => e is not null).ToList(); _message = erreurs.Count == 0 ? null : string.Join(" ", erreurs); _selection.Clear(); await ChargerAsync(); } private async Task MasquerAsync(OeuvreBibliographie oeuvre) { await ModifierMasquageAsync(oeuvre, false); } private async Task DemasquerAsync(OeuvreBibliographie oeuvre) { await ModifierMasquageAsync(oeuvre, true); } private async Task ModifierMasquageAsync(OeuvreBibliographie oeuvre, bool demasquer) { _enCours = true; _message = demasquer ? await Api.DemasquerOeuvreBibliographieAsync(AuteurId, oeuvre.Titre) : await Api.MasquerOeuvreBibliographieAsync(AuteurId, oeuvre.Titre); _enCours = false; if (_message is null) { await ChargerAsync(); } } private void BasculerOeuvre(string titre) { if (!_oeuvresOuvertes.Add(titre)) { _oeuvresOuvertes.Remove(titre); } } /// /// Ajoute une œuvre à la liste d'envies, en emportant ce que la BnF en sait. /// /// /// L'auteur enregistré est celui de l'écran, pas celui de la notice : c'est lui que /// l'utilisateur a en tête, et c'est sous ce nom qu'il retrouvera l'envie dans l'export. /// /// Après l'ajout, la bibliographie est relue plutôt que corrigée en mémoire : c'est /// le serveur qui sait rapprocher l'envie de l'œuvre, et refaire ce calcul ici le /// dupliquerait — avec le risque qu'il diverge. /// /// private async Task SouhaiterAsync(OeuvreBibliographie oeuvre) { if (_bibliographie is null) { return; } _enCours = true; _message = null; var resultat = await Api.AjouterSouhaitAsync(new EnregistrementSouhait { Titre = oeuvre.Titre, Auteur = _bibliographie.Auteur.Nom, Editeur = oeuvre.Editeur, Annee = oeuvre.Annee, // L'ISBN de la BnF est celui d'UNE édition relevée, souvent à 10 chiffres et parfois // épuisée. Il est repris tel quel : il aide à commander, sans engager le choix. Isbn = oeuvre.Isbn, }); _enCours = false; if (!resultat.EstOk) { _message = resultat.Erreur; return; } await ChargerAsync(); } }