Ajouter le scan camera du code-barres ISBN

Le decodage EAN-13/EAN-8 est fait en C# par ZXing.Net (IsbnScanner), conformement
a la decision actee dans CLAUDE.md : le JavaScript ne fournit que les pixels de la
camera, aucune logique de decodage ne lui est confiee.

Le scan se greffe en amont du flux d'ajout par ISBN existant : des qu'un code est
lu et reconnu comme ISBN-13 valide, la recherche de notices est declenchee sans
que l'utilisateur ait a retaper quoi que ce soit. La saisie manuelle reste
accessible en permanence, y compris quand la camera echoue.

Points traites explicitement :
- camera arriere par defaut (facingMode "environment", en "ideal" pour ne pas
  echouer sur un PC sans camera arriere) ;
- liberation de la camera a l'annulation, au succes et au demontage du composant ;
- cadence limitee a ~12 images/s et decodage restreint a la bande centrale
  reduite a 640 px de large : inutile de decoder chaque frame en pleine taille ;
- messages distincts pour permission refusee, absence de camera, camera occupee
  et contexte non securise (getUserMedia exige HTTPS ou localhost : une IP de
  reseau local en http ne marchera jamais, c'est une source de confusion garantie).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-17 23:32:05 +02:00
co-authored by Claude Opus 5
parent b619de29c7
commit f7ad9582f6
9 changed files with 585 additions and 2 deletions
+28 -2
View File
@@ -9,7 +9,7 @@
@if (_etape == Etape.Saisie)
{
<p class="message-discret">
Saisissez l'ISBN imprimé sur le livre. Le scan par la caméra arrivera plus tard.
Scannez le code-barres, ou saisissez l'ISBN imprimé sur le livre.
</p>
<div class="barre-recherche">
@@ -24,10 +24,20 @@
@onclick="ChercherAsync">
@(_chargement ? "Recherche…" : "Chercher")
</button>
@* La saisie manuelle reste le recours quand le code-barres est abîmé, absent,
ou que la caméra est indisponible : elle ne disparaît jamais derrière le scan. *@
<button type="button" class="bouton bouton-discret" @onclick="OuvrirScanner">
Scanner
</button>
<a class="bouton bouton-discret" href="ajout/manuel">Saisir à la main</a>
</div>
}
@if (_etape == Etape.Scan)
{
<ScannerCodeBarres OnCodeDetecte="SurCodeDetecteAsync" OnAnnuler="Recommencer" />
}
@if (_erreur is not null)
{
<p class="message-erreur" role="alert">@_erreur</p>
@@ -103,7 +113,7 @@
}
@code {
private enum Etape { Saisie, Choix, Formulaire }
private enum Etape { Saisie, Scan, Choix, Formulaire }
private Etape _etape = Etape.Saisie;
private string _isbn = string.Empty;
@@ -115,6 +125,22 @@
private IReadOnlyList<string> _avertissements = [];
private EnregistrementLivre _saisie = new();
private void OuvrirScanner()
{
_erreur = null;
_avertissements = [];
_etape = Etape.Scan;
}
// Le scan enchaîne directement sur le flux de lookup existant : l'utilisateur
// ne retape jamais ce qui vient d'être scanné.
private async Task SurCodeDetecteAsync(string isbn)
{
_isbn = isbn;
_etape = Etape.Saisie;
await ChercherAsync();
}
private async Task SurTouche(KeyboardEventArgs e)
{
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_isbn))