Clôt la décision du premier lot, qui écartait les périodiques faute de modèle. Douze numéros d'un magazine ne font plus douze fiches identiques. Table à part, pour la troisième fois et pour le même motif que la liste d'envies et les séries : logée dans Livres, une revue entrerait dans le catalogue, ses compteurs, les doublons et les bibliographies, et il faudrait écrire « et qui n'est pas une revue » à chaque lecture. Un numéro est recensé, rien de plus — ni prêt ni statut, tous deux liés à Livre par clé étrangère. Réversible. « Créer ou retrouver » et non « créer » : scanner le numéro suivant du même magazine retombe forcément sur la même revue. L'unicité de l'ISSN est un index partiel, sans quoi les revues sans ISSN se bloqueraient entre elles. Le flux 977 mène désormais à la fiche de la revue, et ne pré-remplit plus le formulaire d'un livre. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
150 lines
5.3 KiB
Plaintext
150 lines
5.3 KiB
Plaintext
@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")">
|
|
@_utilisateur.Affichage@(_utilisateur.Simule ? " (dev)" : "")
|
|
</span>
|
|
}
|
|
</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>
|
|
|
|
@*
|
|
Navigation permanente, en bas de l'écran : six onglets, toujours au même endroit.
|
|
Elle remplace les listes de liens que chaque page portait dans sa barre d'actions — cinq
|
|
boutons y passaient sur deux ou trois lignes sur un téléphone, et l'entrée « Envies » ne
|
|
figurait pas partout. Les barres d'actions restantes ne portent plus que des ACTIONS.
|
|
|
|
⚠️ Le pouce atteint le bas de l'écran, pas le haut : c'est ce qui justifie le bas plutôt
|
|
qu'un rang d'onglets sous le bandeau. Les actions de page se placent juste au-dessus
|
|
(variable --mb-onglets-hauteur, partagée avec .actions-flottantes).
|
|
|
|
Les onglets restent actifs hors-ligne : les six écrans se consultent depuis leurs
|
|
instantanés. Ce sont les écritures qui se désactivent, pas la navigation.
|
|
*@
|
|
<nav class="onglets" aria-label="Navigation principale">
|
|
<NavLink class="onglet" href="" Match="NavLinkMatch.All">Catalogue</NavLink>
|
|
<NavLink class="onglet" href="auteurs">Auteurs</NavLink>
|
|
<NavLink class="onglet" href="series">Séries</NavLink>
|
|
<NavLink class="onglet" href="revues">Revues</NavLink>
|
|
<NavLink class="onglet" href="prets">Prêts</NavLink>
|
|
<NavLink class="onglet" href="souhaits">Envies</NavLink>
|
|
</nav>
|
|
|
|
@code {
|
|
private UtilisateurCourant? _utilisateur;
|
|
private bool _etaitEnLigne = true;
|
|
private bool _synchroEnCours;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
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;
|
|
}
|
|
}
|