Files
mabibli/MaBibli.Client/Pages/APropos.razor
T
mathieu dbb103f6db - Ajout d'un bouton pour forcer la mise à jour (si coupure internet pdt le dl, il faut tout désinstaller et re-instalter...).
- Pour les grandes bibli, ajouter un visuel que l'appli est en train de charger les données en cache
2026-09-10 17:58:27 +02:00

224 lines
8.4 KiB
Plaintext

@page "/a-propos"
@inject ServiceLivresApi Api
@inject EtatReseau Reseau
@inject IJSRuntime JS
@implements IDisposable
@*
Ce que l'application dit d'elle-même : à qui écrire, où trouver de l'aide, quelle version
tourne réellement, et sous quelle licence elle est distribuée.
⚠️ Ce n'est pas une page d'ornement. Elle porte la version, c'est-à-dire la première chose
qu'on demande à quelqu'un dont l'application se comporte bizarrement — le projet a déjà connu
un appareil au cache dépareillé, où seule une version affichée aurait tranché. Et elle porte
le lien vers la source, que l'AGPL v3 attend qu'un service en réseau offre à ses utilisateurs.
*@
<PageTitle>MaBibli — à propos</PageTitle>
<h1 class="titre-page">À propos</h1>
<section class="bloc-apropos">
<h2 class="titre-section">Manuel</h2>
@*
Le manuel vit hors de l'application, sur le site de l'auteur : il se corrige et s'illustre
sans qu'il faille republier une version. Même règle que les autres liens sortants — un
<a> ne se désactivant pas, hors-ligne il devient un bouton inerte qui porte sa raison,
plutôt qu'une navigation qui abandonnerait l'utilisateur sur la page d'erreur du navigateur.
*@
@if (Reseau.EnLigne)
{
<p>
<a href="@Manuel" target="_blank" rel="noopener">Comment utiliser MaBibli</a>
</p>
}
else
{
<p>
<button type="button" class="bouton bouton-discret" disabled
title="@EtatReseau.MotifHorsLigne">Comment utiliser MaBibli</button>
</p>
}
<p class="message-discret">
Chaque écran porte par ailleurs ses explications à l'endroit où elles servent.
</p>
</section>
<section class="bloc-apropos">
<h2 class="titre-section">Contact</h2>
@*
⚠️ Le « mailto: » reste un lien même hors-ligne, et c'est délibéré : il ne charge aucune
page, il passe la main au client de messagerie de l'appareil, lequel sait mettre un
message en attente d'envoi. Le désactiver empêcherait précisément d'écrire « ça ne marche
pas, je n'ai plus de réseau ».
*@
<p><a href="mailto:info@limonier.be">info@limonier.be</a></p>
@*
Le site, lui, est une vraie navigation vers l'extérieur : hors-ligne elle finirait sur la
page d'erreur du navigateur, hors de l'application. Un <a> ne se désactivant pas, il
bascule en bouton inerte portant sa raison — même règle que les liens d'export des envies.
*@
@if (Reseau.EnLigne)
{
<p>
<a href="@Site" target="_blank" rel="noopener">www.limonier.be</a>
</p>
}
else
{
<p>
<button type="button" class="bouton bouton-discret" disabled
title="@EtatReseau.MotifHorsLigne">www.limonier.be</button>
</p>
}
</section>
<section class="bloc-apropos">
<h2 class="titre-section">Version</h2>
@if (_version is null)
{
<p class="message-discret">Chargement…</p>
}
else if (_version.Publiee)
{
<p>
<strong>@_version.Numero</strong>
<span class="message-discret"> — compilée @Quand(_version.DateBuild!.Value)</span>
</p>
@if (!Reseau.EnLigne)
{
<p class="message-discret">
Hors ligne : c'est la dernière version annoncée par le serveur, pas forcément
celle qui y tourne en ce moment.
</p>
}
@*
⚠️ Ce numéro vient du SERVEUR (voir plus haut) : il ne prouve pas que le navigateur a
lui-même récupéré la nouvelle version. Le service worker n'active un nouveau cache
qu'une fois TOUS les onglets fermés — un appareil qui reste ouvert en arrière-plan
(téléphone surtout) peut rester bloqué sur l'ancienne version indéfiniment. Ce bouton
court-circuite cette attente : désinscrire le worker et vider son cache, recharger.
Hors-ligne il est désactivé — le déclencher sans réseau couperait l'accès hors-ligne
en cours, sans rien pouvoir retélécharger à la place.
*@
<p>
@if (Reseau.EnLigne)
{
<button type="button" class="bouton bouton-discret" disabled="@_forcageEnCours"
@onclick="ForcerMiseAJourAsync">
@(_forcageEnCours ? "Mise à jour…" : "Forcer la mise à jour")
</button>
}
else
{
<button type="button" class="bouton bouton-discret" disabled
title="@EtatReseau.MotifHorsLigne">Forcer la mise à jour</button>
}
</p>
<p class="message-discret">
Vide le cache de l'application et la recharge entièrement — à utiliser si une nouvelle
version tarde à apparaître malgré la fermeture de tous les onglets.
</p>
}
else
{
@*
⚠️ Ne JAMAIS afficher ici le « 1.0.0 » que le SDK .NET pose par défaut : il se lirait
comme une vraie version. Mieux vaut ne rien annoncer que d'annoncer un faux numéro,
surtout sur la valeur qui sert à diagnostiquer.
*@
<p class="message-discret">
Version de développement : ce binaire n'a pas été produit par la chaîne de
publication, il n'a donc pas de numéro de version à annoncer.
</p>
}
</section>
<section class="bloc-apropos">
<h2 class="titre-section">Licence</h2>
<p>
MaBibli est distribuée sous licence <strong>GNU AGPL v3</strong>.
</p>
@*
Le lien vers le dépôt n'est pas un ornement : l'AGPL demande que les utilisateurs d'un
service accessible par le réseau puissent en obtenir le code source.
*@
@if (Reseau.EnLigne)
{
<p>
Son code source est disponible :
<a href="@Depot" target="_blank" rel="noopener">@Depot</a>
</p>
}
else
{
<p>
Son code source est disponible à l'adresse <code>@Depot</code>
<button type="button" class="bouton bouton-discret" disabled
title="@EtatReseau.MotifHorsLigne">Ouvrir le dépôt</button>
</p>
}
</section>
@code {
/// <summary>Site de l'auteur, ouvert dans une autre fenêtre.</summary>
private const string Site = "https://www.limonier.be";
/// <summary>Manuel d'utilisation, hébergé avec les autres tutoriels du site.</summary>
private const string Manuel = "https://limonier.be/tuto/utiliser_mabibli/";
/// <summary>Dépôt du code — ce que l'AGPL attend qu'on rende accessible.</summary>
private const string Depot = "https://git.akbar.nohost.me/mathieu/mabibli";
private VersionApplication? _version;
private bool _forcageEnCours;
protected override async Task OnInitializedAsync()
{
Reseau.Change += SurChangementReseau;
_version = await Api.ObtenirVersionAsync();
}
/// <summary>
/// Au retour du réseau, la version se redemande : hors-ligne on affichait la dernière connue.
/// </summary>
private void SurChangementReseau() => _ = InvokeAsync(async () =>
{
_version = await Api.ObtenirVersionAsync();
StateHasChanged();
});
/// <summary>
/// Désinscrit le service worker et vide son cache avant de recharger — voir js/forcer-maj.js.
/// Le bouton reste désactivé pendant l'opération : un double clic déclencherait deux
/// désinscriptions concurrentes pour un seul rechargement.
/// </summary>
private async Task ForcerMiseAJourAsync()
{
_forcageEnCours = true;
try
{
await using var module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/forcer-maj.js");
await module.InvokeVoidAsync("forcerMiseAJour");
}
catch
{
// Le rechargement forcé qui suit normalement l'appel JS ne survient pas ici : on
// rend la main pour que l'utilisateur puisse réessayer plutôt que de rester sur un
// bouton grisé pour rien.
_forcageEnCours = false;
}
}
private static string Quand(DateTimeOffset instant)
{
var local = instant.ToLocalTime();
return $"le {local:dd/MM/yyyy} à {local:HH:mm}";
}
public void Dispose() => Reseau.Change -= SurChangementReseau;
}