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 '