Files
mabibli/MaBibli.Client/Pages/Series.razor
T
mathieuandClaude Opus 5 b8a5122b6f Rend l'attente visible, et met le champ de création sur une ligne
Une recherche BnF coûte environ une seconde par page, et jusqu'à dix pages pour
les nouveautés : sans rien à l'écran, l'application se lit comme plantée. Le lot
H l'avait montré — un écran qui ne dit rien pousse à recliquer, donc à relancer
l'appel.

Un indicateur ne remplace aucun message d'échec : une source muette garde ses
`EtatSourceBibliographie` et ses motifs. Ici on dit « ça travaille », jamais
« ça a marché ». L'animation se neutralise sous `prefers-reduced-motion`, la
rondelle restant visible : c'est elle qui porte l'information, pas sa rotation.

Le champ « nom d'une nouvelle série » et son bouton tiennent maintenant sur une
ligne. `min-width: 0` est ce qui la fait tenir à 320 px : sans lui, un élément de
formulaire refuse de rétrécir sous sa largeur intrinsèque et pousse le bouton
hors de l'écran.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 11:25:09 +02:00

122 lines
3.8 KiB
Plaintext

@page "/series"
@inject ServiceLivresApi Api
@inject EtatReseau Reseau
@implements IDisposable
<PageTitle>MaBibli — séries</PageTitle>
<h1 class="titre-page">Séries</h1>
<p class="message-discret">
Sagas, cycles et trilogies, dans leur ordre de <strong>lecture</strong> — qui n'est pas
toujours celui de publication. Les séries sont communes au foyer, comme le catalogue.
</p>
@if (_erreur is not null)
{
<p class="message-erreur" role="alert">@_erreur</p>
}
@* Créer une série depuis la liste : c'est le seul endroit d'où l'on part, et un écran de plus
pour un unique champ ne se justifierait pas. *@
@* M1 : le champ et son bouton sur UNE ligne. Le champ prend la place restante, le bouton
garde la sienne et son libellé ne se tronque pas — vérifié jusqu'à 320 px. *@
<div class="ligne-creation">
<input class="champ-saisie" type="text" placeholder="Nom d'une nouvelle série…"
@bind="_nouveau" @bind:event="oninput" @onkeydown="SurTouche"
aria-label="Nom de la nouvelle série" autocomplete="off" />
<button type="button" class="bouton bouton-principal"
disabled="@(_enCours || !Reseau.EnLigne || string.IsNullOrWhiteSpace(_nouveau))"
title="@MotifBlocage"
@onclick="CreerAsync">
Créer
</button>
</div>
@if (_series is null)
{
<p class="message-discret" role="status">Chargement…</p>
}
else if (_series.Count == 0)
{
<p class="message-discret">
Aucune série pour l'instant. Créez-en une, puis ajoutez-y vos livres et les tomes
qui vous manquent : c'est ce qui permet de voir les trous.
</p>
}
else
{
<ul class="liste-series arbre">
@foreach (var serie in Racines)
{
<ArbreSerie Serie="serie" Toutes="_series" Totaux="_totaux" Replies="_replies" />
}
</ul>
}
@code {
private IReadOnlyList<SerieDto>? _series;
private IReadOnlyDictionary<int, (int Possedes, int Total)> _totaux =
new Dictionary<int, (int, int)>();
/// <summary>Nœuds repliés à la main. Tout est déplié par défaut — voir ArbreSerie.</summary>
private readonly HashSet<int> _replies = [];
private string _nouveau = string.Empty;
private bool _enCours;
private string? _erreur;
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
/// <summary>Séries de premier niveau. Celles rangées dans un cycle s'affichent sous lui.</summary>
private IEnumerable<SerieDto> Racines =>
_series?.Where(s => s.SerieParenteId is null) ?? [];
protected override void OnInitialized() => Reseau.Change += SurChangementReseau;
private void SurChangementReseau() => _ = InvokeAsync(async () =>
{
await ChargerAsync();
StateHasChanged();
});
public void Dispose() => Reseau.Change -= SurChangementReseau;
protected override Task OnInitializedAsync() => ChargerAsync();
private async Task ChargerAsync()
{
// ⚠️ Le catalogue n'est plus chargé ici : l'arbre ne montre que la descendance des
// séries, plus leurs tomes. C'était une lecture complète du fonds pour rien.
_series = await Api.ListerSeriesAsync();
_totaux = TotauxSeries.Calculer(_series);
}
private async Task SurTouche(KeyboardEventArgs e)
{
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_nouveau) && Reseau.EnLigne)
{
await CreerAsync();
}
}
private async Task CreerAsync()
{
_enCours = true;
_erreur = null;
var resultat = await Api.CreerSerieAsync(new EnregistrementSerie { Titre = _nouveau });
_enCours = false;
if (!resultat.EstOk)
{
_erreur = resultat.Erreur;
return;
}
_nouveau = string.Empty;
await ChargerAsync();
}
}