From 30b17be43edf011df887ceea131b9474a79397b1 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 22 Jun 2026 15:12:34 -0400 Subject: [PATCH] Refactor mobile bib scanning functionality and enhance UI feedback This commit refines the mobile bib scanning process by introducing a new capture element and updating the UI to provide clearer feedback during scanning. It removes the unused OCR interval and enhances the event handling for capture actions. Additionally, new messages are added for user guidance, and the version code is incremented to 4.72.696 to reflect these changes. The SQL file is updated to include new translation keys for improved user instructions. --- css/style.css | 60 +++++++++++++---- inc_footer_scripts.php | 85 ++++++++++++++++++++---- php/inc_fx_inscriptions_mobile.php | 8 ++- php/inc_settings.php | 2 +- sql/MSIN-inscriptions-mobile-qr-i18n.sql | 10 ++- 5 files changed, 134 insertions(+), 31 deletions(-) diff --git a/css/style.css b/css/style.css index 5158585..e1119e9 100644 --- a/css/style.css +++ b/css/style.css @@ -2487,6 +2487,52 @@ a.ms1-trad-link.btn-aide-trad{ color:#fff; } +.inscr-mobile-bib-scan-capture{ + position:relative; + overflow:hidden; + border-radius:4px; + background:#111; + max-height:100px; + cursor:pointer; + outline:none; +} + +.inscr-mobile-bib-scan-capture.is-ready{ + box-shadow:0 0 0 2px rgba(111,66,193,.35); +} + +.inscr-mobile-bib-scan-capture[aria-disabled="true"]{ + cursor:wait; + pointer-events:none; +} + +.inscr-mobile-bib-scan-video{ + max-height:100px; +} + +.inscr-mobile-bib-scan-video video{ + display:block; + width:100%; + max-height:100px; + object-fit:cover; + pointer-events:none; +} + +.inscr-mobile-bib-scan-capture-label{ + position:absolute; + left:50%; + bottom:8px; + transform:translateX(-50%); + padding:4px 10px; + border-radius:999px; + background:rgba(0,0,0,.65); + color:#fff; + font-size:11px; + font-weight:600; + white-space:nowrap; + pointer-events:none; +} + .inscr-mobile-bib-scan-panel{ margin-top:8px; } @@ -2507,20 +2553,6 @@ a.ms1-trad-link.btn-aide-trad{ animation:inscr-mobile-qr-pulse 1.2s ease-in-out infinite; } -.inscr-mobile-bib-scan-video{ - overflow:hidden; - border-radius:4px; - background:#111; - max-height:100px; -} - -.inscr-mobile-bib-scan-video video{ - display:block; - width:100%; - max-height:100px; - object-fit:cover; -} - .inscr-mobile-bib-row input.inscr-mobile-bib--proposed{ border-color:#6f42c1; box-shadow:0 0 0 2px rgba(111,66,193,.2); diff --git a/inc_footer_scripts.php b/inc_footer_scripts.php index 7411f89..823c3cd 100644 --- a/inc_footer_scripts.php +++ b/inc_footer_scripts.php @@ -3716,7 +3716,6 @@ if ($strLangue == 'fr') { (function () { var BIB_MAX_LEN = 6; var OCR_MAX_WIDTH = 320; - var OCR_INTERVAL_MS = 1200; var tesseractPromise = null; var globalOcrWorker = null; var globalOcrWorkerPromise = null; @@ -3817,13 +3816,15 @@ if ($strLangue == 'fr') { this.panel = block.querySelector('.inscr-mobile-bib-scan-panel'); this.toggleBtn = block.querySelector('.inscr-mobile-bib-scan-toggle'); this.statusEl = this.panel ? this.panel.querySelector('.inscr-mobile-bib-scan-status') : null; - this.videoWrap = this.panel ? this.panel.querySelector('.inscr-mobile-bib-scan-video') : null; + this.captureEl = this.panel ? this.panel.querySelector('.inscr-mobile-bib-scan-capture') : null; + this.videoWrap = this.captureEl ? this.captureEl.querySelector('.inscr-mobile-bib-scan-video') : null; + this.captureLabel = this.captureEl ? this.captureEl.querySelector('.inscr-mobile-bib-scan-capture-label') : null; this.inputId = this.panel ? this.panel.getAttribute('data-bib-input-id') : ''; this.stream = null; this.video = null; - this.intervalId = null; this.ocrBusy = false; this.workerReady = false; + this.onCaptureClick = null; } BibScanSession.prototype.getInput = function () { @@ -3838,11 +3839,54 @@ if ($strLangue == 'fr') { this.statusEl.classList.toggle('is-active', !!active); }; - BibScanSession.prototype.stop = function () { - if (this.intervalId) { - window.clearInterval(this.intervalId); - this.intervalId = null; + BibScanSession.prototype.setCaptureReady = function (ready) { + if (this.captureEl) { + this.captureEl.classList.toggle('is-ready', !!ready); + this.captureEl.setAttribute('aria-disabled', ready ? 'false' : 'true'); } + }; + + BibScanSession.prototype.showReady = function () { + this.setStatus(this.panel.getAttribute('data-msg-ready') || '', false); + this.setCaptureReady(true); + if (this.captureLabel) { + this.captureLabel.hidden = false; + } + }; + + BibScanSession.prototype.unbindCapture = function () { + if (this.captureEl && this.onCaptureClick) { + this.captureEl.removeEventListener('click', this.onCaptureClick); + this.captureEl.removeEventListener('keydown', this.onCaptureKey); + } + this.onCaptureClick = null; + this.onCaptureKey = null; + }; + + BibScanSession.prototype.bindCapture = function () { + var self = this; + self.unbindCapture(); + if (!self.captureEl) { + return; + } + self.onCaptureClick = function () { + if (!self.workerReady || self.ocrBusy || activeScan !== self) { + return; + } + self.runOcr(); + }; + self.onCaptureKey = function (e) { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + self.onCaptureClick(); + } + }; + self.captureEl.addEventListener('click', self.onCaptureClick); + self.captureEl.addEventListener('keydown', self.onCaptureKey); + }; + + BibScanSession.prototype.stop = function () { + this.unbindCapture(); if (this.stream) { this.stream.getTracks().forEach(function (track) { track.stop(); }); this.stream = null; @@ -3856,6 +3900,7 @@ if ($strLangue == 'fr') { } this.ocrBusy = false; this.workerReady = false; + this.setCaptureReady(false); this.setStatus(''); if (this.panel) { this.panel.hidden = true; @@ -3892,6 +3937,11 @@ if ($strLangue == 'fr') { return; } self.ocrBusy = true; + self.setCaptureReady(false); + if (self.captureLabel) { + self.captureLabel.hidden = true; + } + self.setStatus(self.panel.getAttribute('data-msg-scanning') || '', true); getGlobalOcrWorker().then(function (worker) { return worker.recognize(canvas); }).then(function (result) { @@ -3899,9 +3949,21 @@ if ($strLangue == 'fr') { var strNum = extractBibNumber(rawText); if (strNum) { self.applyNumber(strNum); + return; } + self.setStatus(self.panel.getAttribute('data-msg-invalid') || '', false); + window.setTimeout(function () { + if (activeScan === self) { + self.showReady(); + } + }, 1800); }).catch(function () { - /* ignore transient OCR errors */ + self.setStatus(self.panel.getAttribute('data-msg-invalid') || '', false); + window.setTimeout(function () { + if (activeScan === self) { + self.showReady(); + } + }, 1800); }).then(function () { self.ocrBusy = false; }); @@ -3936,7 +3998,6 @@ if ($strLangue == 'fr') { return; } self.workerReady = true; - self.setStatus(self.panel.getAttribute('data-msg-scanning') || '', true); self.stream = results[0]; self.video = document.createElement('video'); self.video.setAttribute('playsinline', 'true'); @@ -3950,10 +4011,8 @@ if ($strLangue == 'fr') { if (activeScan !== self || !self.workerReady) { return; } - self.runOcr(); - self.intervalId = window.setInterval(function () { - self.runOcr(); - }, OCR_INTERVAL_MS); + self.bindCapture(); + self.showReady(); }).catch(function () { self.setStatus(self.panel.getAttribute('data-msg-camera') || '', false); self.stop(); diff --git a/php/inc_fx_inscriptions_mobile.php b/php/inc_fx_inscriptions_mobile.php index a0babed..3ae5269 100644 --- a/php/inc_fx_inscriptions_mobile.php +++ b/php/inc_fx_inscriptions_mobile.php @@ -207,7 +207,9 @@ function fxInscrMobileFallbackTexte($strClef, $strLangue) { 'inscr_mobile_qr_wrong_event' => $blnFr ? 'Ce code QR appartient à un autre événement : %s' : 'This QR code belongs to another event: %s', 'inscr_mobile_qr_camera_error' => $blnFr ? 'Impossible d\'accéder à la caméra.' : 'Unable to access the camera.', 'inscr_mobile_bib_scan_btn' => $blnFr ? 'Scanner le numéro' : 'Scan number', - 'inscr_mobile_bib_scan_hint' => $blnFr ? 'Cadrez un seul numéro au centre, puis maintenez stable.' : 'Frame a single number in the centre and hold steady.', + 'inscr_mobile_bib_scan_hint' => $blnFr ? 'Ouvrez la caméra, cadrez le numéro, puis touchez l\'image pour lire.' : 'Open the camera, frame the number, then tap the image to read.', + 'inscr_mobile_bib_scan_ready' => $blnFr ? 'Cadrez le numéro, puis touchez l\'image' : 'Frame the number, then tap the image', + 'inscr_mobile_bib_scan_tap' => $blnFr ? 'Toucher pour lire' : 'Tap to read', 'inscr_mobile_bib_scan_scanning' => $blnFr ? 'Lecture en cours...' : 'Reading...', 'inscr_mobile_bib_scan_loading' => $blnFr ? 'Initialisation (1re fois)...' : 'Initializing (first time)...', 'inscr_mobile_bib_scan_found' => $blnFr ? 'Numéro %s détecté — validez avec OK.' : 'Number %s detected — press OK.', @@ -958,12 +960,16 @@ function fxInscrMobileRenderBibScanBlock($strBibInputId, $strLangue) { echo ' data-bib-input-id="' . fxInscrMobileEsc($strBibInputId) . '"'; echo ' data-msg-scanning="' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_bib_scan_scanning')) . '"'; echo ' data-msg-loading="' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_bib_scan_loading')) . '"'; + echo ' data-msg-ready="' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_bib_scan_ready')) . '"'; echo ' data-msg-found="' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_bib_scan_found')) . '"'; echo ' data-msg-invalid="' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_bib_scan_invalid')) . '"'; echo ' data-msg-camera="' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_qr_camera_error')) . '">'; echo '

' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_bib_scan_hint')) . '

'; echo '
'; + echo '
'; echo '
'; + echo '' . fxInscrMobileEsc(fxInscrMobileT('inscr_mobile_bib_scan_tap')) . ''; + echo '
'; echo ''; } diff --git a/php/inc_settings.php b/php/inc_settings.php index b164064..d08efc1 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.695'); +define('_VERSION_CODE', '4.72.696'); define('_DATE_CODE', '2026-06-22'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); diff --git a/sql/MSIN-inscriptions-mobile-qr-i18n.sql b/sql/MSIN-inscriptions-mobile-qr-i18n.sql index 5d48e83..10f0f07 100644 --- a/sql/MSIN-inscriptions-mobile-qr-i18n.sql +++ b/sql/MSIN-inscriptions-mobile-qr-i18n.sql @@ -11,6 +11,8 @@ WHERE info_prg = 'compte.php' 'inscr_mobile_qr_camera_error', 'inscr_mobile_bib_scan_btn', 'inscr_mobile_bib_scan_hint', + 'inscr_mobile_bib_scan_ready', + 'inscr_mobile_bib_scan_tap', 'inscr_mobile_bib_scan_scanning', 'inscr_mobile_bib_scan_loading', 'inscr_mobile_bib_scan_found', @@ -32,8 +34,12 @@ INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_ ('inscr_mobile_qr_camera_error', 'en', 'Unable to access the camera.', '', 'compte.php', '', 0, 1, '', '', '', NOW()), ('inscr_mobile_bib_scan_btn', 'fr', 'Scanner le numero', '', 'compte.php', '', 0, 1, '', '', '', NOW()), ('inscr_mobile_bib_scan_btn', 'en', 'Scan number', '', 'compte.php', '', 0, 1, '', '', '', NOW()), -('inscr_mobile_bib_scan_hint', 'fr', 'Cadrez un seul numero au centre et maintenez stable.', '', 'compte.php', '', 0, 1, '', '', '', NOW()), -('inscr_mobile_bib_scan_hint', 'en', 'Frame a single number in the centre and hold steady.', '', 'compte.php', '', 0, 1, '', '', '', NOW()), +('inscr_mobile_bib_scan_hint', 'fr', 'Ouvrez la camera, cadrez le numero, puis touchez l''image pour lire.', '', 'compte.php', '', 0, 1, '', '', '', NOW()), +('inscr_mobile_bib_scan_hint', 'en', 'Open the camera, frame the number, then tap the image to read.', '', 'compte.php', '', 0, 1, '', '', '', NOW()), +('inscr_mobile_bib_scan_ready', 'fr', 'Cadrez le numero, puis touchez l''image', '', 'compte.php', '', 0, 1, '', '', '', NOW()), +('inscr_mobile_bib_scan_ready', 'en', 'Frame the number, then tap the image', '', 'compte.php', '', 0, 1, '', '', '', NOW()), +('inscr_mobile_bib_scan_tap', 'fr', 'Toucher pour lire', '', 'compte.php', '', 0, 1, '', '', '', NOW()), +('inscr_mobile_bib_scan_tap', 'en', 'Tap to read', '', 'compte.php', '', 0, 1, '', '', '', NOW()), ('inscr_mobile_bib_scan_scanning', 'fr', 'Lecture en cours...', '', 'compte.php', '', 0, 1, '', '', '', NOW()), ('inscr_mobile_bib_scan_scanning', 'en', 'Reading...', '', 'compte.php', '', 0, 1, '', '', '', NOW()), ('inscr_mobile_bib_scan_loading', 'fr', 'Initialisation (1re fois)...', '', 'compte.php', '', 0, 1, '', '', '', NOW()),