Arbre des séries dépouillé, et ordre de lecture derrière son propre écran

- L'arbre ne montre plus que la descendance : titre, avancement, lien. Il dépliait
  auparavant la liste complète des tomes de chaque nœud, cartes de livres comprises —
  un cycle occupait plusieurs écrans et l'on ne voyait plus qui contient quoi. Corollaire :
  tout est déplié par défaut, et l'on replie ce dont on ne veut pas.
- /series ne charge plus le catalogue entier : l'arbre n'en a plus l'usage.
- ⚠️ Nouvelle route /series/{id}/ordre, sur le modèle de /souhaits/ordre. Les flèches
  n'existent plus qu'ici : en consultation, elles se déclenchaient au défilement du pouce
  et l'ordre de lecture d'une saga changeait sans qu'on l'ait voulu.
- Le mode ordre ne fait QUE réordonner : les actions par tome et l'ajout de tome n'y
  figurent pas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-20 20:45:14 +02:00
co-authored by Claude Opus 5
parent 4fd67e4e3b
commit bd4fea2638
4 changed files with 230 additions and 86 deletions
+72 -19
View File
@@ -1,5 +1,6 @@
@page "/series/{Id:int}"
@page "/series/{Id:int}/edition"
@page "/series/{Id:int}/ordre"
@inject ServiceLivresApi Api
@inject NavigationManager Navigation
@inject EtatReseau Reseau
@@ -39,6 +40,15 @@ else
}
</p>
@if (ModeOrdre)
{
<p class="message-discret">
Rangez les tomes dans l'ordre où vous voulez les <strong>lire</strong> — ce n'est
pas toujours l'ordre de publication : une préquelle se lit avant le livre paru dix
ans plus tôt. Rien d'autre ne se modifie depuis cet écran.
</p>
}
@if (_erreur is not null)
{
<p class="message-erreur" role="alert">@_erreur</p>
@@ -56,13 +66,17 @@ else
@if (!ModeEdition && _filles.Count > 0)
{
<h2 class="titre-section">Séries de ce cycle</h2>
<ul class="liste-sous-series">
<ul class="liste-sous-series arbre">
@foreach (var fille in _filles)
{
<ArbreSerie Serie="fille" Toutes="_toutes" Livres="_livresCatalogue"
Totaux="_totaux" Deplies="_deplies"
@* ⚠️ Les flèches n'apparaissent QUE dans l'écran d'ordre. En consultation, elles
se déclenchaient en faisant défiler du pouce, et l'ordre de lecture d'une saga
changeait sans qu'on l'ait voulu — même raison que pour la liste d'envies,
dont le réordonnancement vit sur son propre écran. *@
<ArbreSerie Serie="fille" Toutes="_toutes"
Totaux="_totaux" Replies="_replies"
Chemin="@CheminRacine"
ActionsActives="Reseau.EnLigne" MotifBlocage="@MotifBlocage"
ActionsActives="@(ModeOrdre && Reseau.EnLigne)" MotifBlocage="@MotifBlocage"
OnDeplacer="DeplacerSousSerieAsync" />
}
</ul>
@@ -83,19 +97,25 @@ else
{
<li class="carte-tome @(element.Possede ? null : "tome-manquant")" @key="element.Id">
<div class="rang-actions">
<button type="button" class="bouton bouton-compact"
disabled="@(_enCours || !Reseau.EnLigne || index == 0)"
title="@MotifBlocage"
aria-label="@($"Monter « {element.Titre} »")"
@onclick="() => DeplacerAsync(index, index - 1)">▲</button>
@if (ModeOrdre)
{
<button type="button" class="bouton bouton-compact"
disabled="@(_enCours || !Reseau.EnLigne || index == 0)"
title="@MotifBlocage"
aria-label="@($"Monter « {element.Titre} »")"
@onclick="() => DeplacerAsync(index, index - 1)">▲</button>
}
<span class="rang-numero" aria-hidden="true">@(index + 1)</span>
<button type="button" class="bouton bouton-compact"
disabled="@(_enCours || !Reseau.EnLigne || index == _serie.Elements.Count - 1)"
title="@MotifBlocage"
aria-label="@($"Descendre « {element.Titre} »")"
@onclick="() => DeplacerAsync(index, index + 1)">▼</button>
@if (ModeOrdre)
{
<button type="button" class="bouton bouton-compact"
disabled="@(_enCours || !Reseau.EnLigne || index == _serie.Elements.Count - 1)"
title="@MotifBlocage"
aria-label="@($"Descendre « {element.Titre} »")"
@onclick="() => DeplacerAsync(index, index + 1)">▼</button>
}
</div>
<div class="carte-corps">
@@ -134,6 +154,8 @@ else
</p>
}
@if (!ModeOrdre)
{
<div class="actions-tome">
@if (!element.Possede)
{
@@ -172,6 +194,7 @@ else
Retirer
</button>
</div>
}
@if (_rattachement == element.Id)
{
@@ -237,7 +260,7 @@ else
</ul>
}
@if (!ModeEdition)
@if (ModeConsultation)
{
<h2 class="titre-section">Ajouter un tome</h2>
@@ -333,11 +356,24 @@ else
</p>
}
}
@if (!ModeEdition)
@if (ModeOrdre)
{
<nav class="actions-flottantes">
<a class="bouton bouton-principal" href="@($"series/{Id}")">Terminé</a>
</nav>
}
else if (ModeConsultation)
{
<nav class="actions-flottantes">
@if (Reseau.EnLigne)
{
@* Deux écrans distincts, et ce n'est pas une subtilité : « Modifier » touche à la
fiche (nom, cycle, suppression), « Changer l'ordre » à la place des tomes. Les
mêmes flèches offertes en consultation se déclenchaient au défilement. *@
@if (_serie.Elements.Count > 1 || _filles.Count > 1)
{
<a class="bouton bouton-discret" href="@($"series/{Id}/ordre")">Changer l'ordre</a>
}
<a class="bouton bouton-principal" href="@($"series/{Id}/edition")">Modifier</a>
}
else
@@ -362,7 +398,8 @@ else
private List<SerieDto> _filles = [];
private IReadOnlyDictionary<int, (int Possedes, int Total)> _totaux =
new Dictionary<int, (int, int)>();
private readonly HashSet<int> _deplies = [];
/// <summary>Nœuds repliés à la main. Tout est déplié par défaut — voir ArbreSerie.</summary>
private readonly HashSet<int> _replies = [];
private bool _chargement = true;
private bool _enCours;
@@ -385,9 +422,25 @@ else
private string? MotifBlocage => Reseau.EnLigne ? null : EtatReseau.MotifHorsLigne;
private IReadOnlySet<int> CheminRacine => new HashSet<int> { Id };
private bool ModeEdition =>
private bool ModeEdition => Suffixe("/edition");
/// <summary>
/// Écran dédié au réordonnancement, sur le modèle de <c>/souhaits/ordre</c>.
/// </summary>
/// <remarks>
/// ⚠️ Le mode est dans l'ADRESSE, pas dans un booléen interne : c'est la convention du
/// projet depuis la fiche livre, et elle fait du bouton « retour » du navigateur une
/// sortie naturelle. Comme les trois routes partagent le même paramètre, le composant
/// s'abonne à <c>LocationChanged</c> — sans quoi le routeur ne redessinerait rien.
/// </remarks>
private bool ModeOrdre => Suffixe("/ordre");
/// <summary>Ni édition de la fiche, ni réordonnancement : l'écran qu'on ouvre pour regarder.</summary>
private bool ModeConsultation => !ModeEdition && !ModeOrdre;
private bool Suffixe(string fin) =>
Navigation.ToAbsoluteUri(Navigation.Uri).AbsolutePath.TrimEnd('/')
.EndsWith("/edition", StringComparison.OrdinalIgnoreCase);
.EndsWith(fin, StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Séries dans lesquelles celle-ci peut être rangée.