1361 lines
56 KiB
JavaScript
1361 lines
56 KiB
JavaScript
/**
|
|
* MSIN-4435 — Gestion inscriptions v2
|
|
* Chargé sur inc_tableau_inscriptions_gestion et mobile.php (entrée gestion).
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var cfg = window.MS1_V2_INSCR_GESTION || {};
|
|
var i18n = cfg.i18n || {};
|
|
|
|
function t(key) {
|
|
return (i18n[key] && String(i18n[key])) || '';
|
|
}
|
|
|
|
// MSIN-4401 — Session expiree en AJAX : recharger → ecran de login (pas un message mort).
|
|
function fxInscrRedirectIfSessionExpired(result) {
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
if (result.code === 'session' || result.message === 'session') {
|
|
window.location.reload();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function ms1LoaderInlineHtml() {
|
|
if (window.Ms1Loader) {
|
|
return Ms1Loader.inlineHtml(cfg.preloaderWait || undefined);
|
|
}
|
|
return '<div class="inscr-gestion-muted" style="padding:10px;">…</div>';
|
|
}
|
|
|
|
if (typeof jQuery !== 'undefined') {
|
|
jQuery(function ($) {
|
|
if ($('#frm_inscr_gestion_login').length && cfg.loginMessages) {
|
|
$('#frm_inscr_gestion_login').validate({
|
|
messages: {
|
|
txt_login: cfg.loginMessages.txt_login || '',
|
|
txt_password: cfg.loginMessages.txt_password || ''
|
|
},
|
|
submitHandler: function (form) {
|
|
if (window.Ms1Loader) {
|
|
Ms1Loader.show(cfg.preloaderWait || undefined);
|
|
} else {
|
|
$('#preloader').show();
|
|
}
|
|
form.submit();
|
|
}
|
|
});
|
|
}
|
|
|
|
var $body = $('body');
|
|
var $preloader = $('#preloader');
|
|
var $preloader_text = $('#preloader_text');
|
|
|
|
// MSIN-4448 — Remis inline liste (meme endpoint / confirmation que la fiche ; UI pastille).
|
|
(function () {
|
|
var remisOp = cfg.remisOperator || {};
|
|
var remisTable = (typeof window.btoa === 'function')
|
|
? window.btoa('resultats_participants')
|
|
: '';
|
|
|
|
function applyListRemisUi($btn, remisYes) {
|
|
$btn.toggleClass('inscr-gestion-remis--yes', remisYes);
|
|
$btn.toggleClass('inscr-gestion-remis--no', !remisYes);
|
|
$btn.attr('aria-pressed', remisYes ? 'true' : 'false');
|
|
$btn.data('valeur', remisYes ? 0 : 1);
|
|
if (remisYes) {
|
|
$btn.html('<i class="fa fa-check-circle" aria-hidden="true"></i>');
|
|
} else {
|
|
$btn.empty();
|
|
}
|
|
}
|
|
|
|
function doListRemis($btn) {
|
|
var intId = parseInt($btn.data('id'), 10) || 0;
|
|
var intValeur = parseInt($btn.data('valeur'), 10);
|
|
if (intId <= 0 || (intValeur !== 0 && intValeur !== 1)) {
|
|
return;
|
|
}
|
|
|
|
$preloader_text.html(cfg.preloaderWait || '');
|
|
$preloader.show();
|
|
|
|
$.post((cfg.domain || '') + '/superadm/enregistrer.php', {
|
|
action: 'remis',
|
|
table: remisTable,
|
|
field: 'no_bib_remis',
|
|
key: 'par_id',
|
|
valeur: intValeur,
|
|
id: intId,
|
|
par_id: remisOp.par_id || 0,
|
|
par: remisOp.par || ''
|
|
}, function (result) {
|
|
$preloader.fadeOut();
|
|
if (fxInscrRedirectIfSessionExpired(result)) {
|
|
return;
|
|
}
|
|
if (result && result.state === 'success') {
|
|
applyListRemisUi($btn, intValeur === 1);
|
|
} else {
|
|
window.alert((result && (result.message || result.state)) || t('errNetwork') || 'Error');
|
|
}
|
|
}, 'json').fail(function () {
|
|
$preloader.fadeOut();
|
|
window.alert(t('errNetwork') || 'Error');
|
|
});
|
|
}
|
|
|
|
$body.on('click', '.inscr-gestion-list-remis', function (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
var $btn = $(this);
|
|
var intValeur = parseInt($btn.data('valeur'), 10);
|
|
|
|
// Retrait (valeur 0) : confirmation — meme garde-fou que la fiche.
|
|
if (intValeur === 0) {
|
|
if (typeof window.bootbox === 'undefined') {
|
|
if (window.confirm(t('remisConfirmMsg'))) {
|
|
doListRemis($btn);
|
|
}
|
|
return;
|
|
}
|
|
window.bootbox.confirm({
|
|
title: t('remisConfirmTitle') || 'Confirmation',
|
|
message: t('remisConfirmMsg'),
|
|
buttons: {
|
|
confirm: {
|
|
label: t('remisConfirmOk') || 'OK',
|
|
className: 'btn btn-danger rounded-0'
|
|
},
|
|
cancel: {
|
|
label: t('remisConfirmCancel') || 'Cancel',
|
|
className: 'btn btn-secondary rounded-0'
|
|
}
|
|
},
|
|
callback: function (ok) {
|
|
if (ok === true) {
|
|
doListRemis($btn);
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
doListRemis($btn);
|
|
});
|
|
|
|
// Valider le dossard inline avec Entree (declenche le .btn_bib legacy).
|
|
$body.on('keydown', '.inscr-gestion-list-bib-input', function (event) {
|
|
if (event.which !== 13 && event.key !== 'Enter') {
|
|
return;
|
|
}
|
|
event.preventDefault();
|
|
$(this).closest('.inscr-gestion-list-bib-edit').find('.btn_bib').trigger('click');
|
|
});
|
|
})();
|
|
|
|
(function () {
|
|
var pendingStatutXhr = null;
|
|
var lastStatutParId = null;
|
|
|
|
$('.inscr-gestion-statut-select').each(function () {
|
|
$(this).data('prev', $(this).val());
|
|
});
|
|
|
|
$body.on('change', '.inscr-gestion-statut-select', function () {
|
|
var $select = $(this);
|
|
var $block = $select.closest('.inscr-gestion-field-block--statut');
|
|
var intParId = $select.data('par_id');
|
|
var strCode = $select.val();
|
|
var strPrev = $select.data('prev');
|
|
|
|
if (!intParId || !strCode) {
|
|
return;
|
|
}
|
|
|
|
if (pendingStatutXhr && lastStatutParId === intParId) {
|
|
pendingStatutXhr.abort();
|
|
}
|
|
lastStatutParId = intParId;
|
|
$block.removeClass('inscr-gestion-statut--saved inscr-gestion-statut--error');
|
|
|
|
$preloader_text.html(cfg.preloaderWait || '');
|
|
$preloader.show();
|
|
|
|
pendingStatutXhr = $.post((cfg.domain || '') + '/ajax_inscr_gestion.php', {
|
|
a: 'par_statut_course',
|
|
par_id: intParId,
|
|
par_statut_course: strCode,
|
|
lang: cfg.lang || 'fr'
|
|
}, function (result) {
|
|
pendingStatutXhr = null;
|
|
$preloader.fadeOut('slow');
|
|
if (fxInscrRedirectIfSessionExpired(result)) {
|
|
return;
|
|
}
|
|
if (result && result.state === 'success') {
|
|
$select.data('prev', strCode);
|
|
$block.addClass('inscr-gestion-statut--saved');
|
|
window.setTimeout(function () {
|
|
$block.removeClass('inscr-gestion-statut--saved');
|
|
}, 1200);
|
|
} else {
|
|
$select.val(strPrev);
|
|
$block.addClass('inscr-gestion-statut--error');
|
|
}
|
|
}, 'json').fail(function () {
|
|
pendingStatutXhr = null;
|
|
$preloader.fadeOut('slow');
|
|
$select.val(strPrev);
|
|
$block.addClass('inscr-gestion-statut--error');
|
|
});
|
|
});
|
|
})();
|
|
|
|
(function () {
|
|
var refundCancelHint = cfg.refundCancelHint || '';
|
|
|
|
function onRefundSuccess(form, json) {
|
|
var $form = $(form);
|
|
|
|
if (json && json.new_token) {
|
|
$form.find('input[name="refund_token"]').val(json.new_token);
|
|
}
|
|
|
|
var amount = parseFloat($form.find('input[name="refund_amount"]').val()) || 0;
|
|
var $max = $('#pp_ref_max');
|
|
var oldMax = parseFloat(String($max.text() || '0').replace(',', '.')) || 0;
|
|
var newMax = Math.max(0, oldMax - amount);
|
|
$max.text(newMax.toFixed(2));
|
|
|
|
var $amountInput = $('#pp_ref_amount');
|
|
$amountInput.attr('max', newMax.toFixed(2));
|
|
if (newMax <= 0) {
|
|
$amountInput.val('0.00').prop('disabled', true);
|
|
$form.find('button[type="submit"]').prop('disabled', true);
|
|
} else {
|
|
$amountInput.val(newMax.toFixed(2));
|
|
}
|
|
|
|
var $cancelBtn = $('#annuler_fiche');
|
|
if ($cancelBtn.length && $cancelBtn.is(':visible')) {
|
|
if (!$('#inscr-gestion-refund-cancel-hint').length) {
|
|
var $hint = $('<div id="inscr-gestion-refund-cancel-hint" style="margin-top:8px;padding:10px;border:1px solid #ffe1a6;background:#fff8e6;border-radius:8px;font-size:13px;display:flex;align-items:center;flex-wrap:wrap;gap:10px;"></div>');
|
|
$hint.append($('<span></span>').text(refundCancelHint));
|
|
$hint.append(
|
|
$('<button type="button" class="btn btn-danger btn-sm rounded-0"></button>')
|
|
.text($.trim($cancelBtn.text()))
|
|
.on('click', function () { $cancelBtn.trigger('click'); })
|
|
);
|
|
$hint.insertAfter($('#inscr-gestion-refund-panel'));
|
|
}
|
|
$cancelBtn.addClass('d-none');
|
|
}
|
|
}
|
|
|
|
$(document).on('click', '.btn_refund_toggle', function () {
|
|
var $btn = $(this);
|
|
var $panel = $($btn.attr('data-refund-target') || '#inscr-gestion-refund-panel');
|
|
if (!$panel.length) { return; }
|
|
|
|
if (!$panel.prop('hidden')) {
|
|
$panel.prop('hidden', true);
|
|
$btn.attr('aria-expanded', 'false');
|
|
return;
|
|
}
|
|
|
|
$panel.prop('hidden', false);
|
|
$btn.attr('aria-expanded', 'true');
|
|
|
|
function focusRefundPanel() {
|
|
if ($panel[0] && $panel[0].scrollIntoView) {
|
|
$panel[0].scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
var $amount = $panel.find('#pp_ref_amount');
|
|
if ($amount.length && !$amount.prop('disabled')) {
|
|
$amount.trigger('focus').trigger('select');
|
|
}
|
|
}
|
|
|
|
if ($panel.attr('data-loaded') === '1') { focusRefundPanel(); return; }
|
|
|
|
$panel.html(ms1LoaderInlineHtml());
|
|
$.post((cfg.domain || '') + '/ajax_inscr_gestion.php', {
|
|
a: 'refund_form',
|
|
no_panier: $btn.data('no_panier'),
|
|
eve_id: $btn.data('eve_id'),
|
|
lang: cfg.lang || 'fr'
|
|
}, function (result) {
|
|
if (fxInscrRedirectIfSessionExpired(result)) {
|
|
return;
|
|
}
|
|
if (result && result.state === 'ok' && result.html) {
|
|
$panel.html(result.html);
|
|
$panel.attr('data-loaded', '1');
|
|
focusRefundPanel();
|
|
} else {
|
|
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || t('errLoad')) + '</div>');
|
|
}
|
|
}, 'json').fail(function () {
|
|
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + t('errNetwork') + '</div>');
|
|
});
|
|
});
|
|
|
|
$(document).on('click', '.btn_transaction_toggle', function () {
|
|
var $btn = $(this);
|
|
var $panel = $($btn.attr('data-transaction-target') || '#inscr-gestion-transaction-panel');
|
|
if (!$panel.length) { return; }
|
|
|
|
var strShow = $btn.data('label-show');
|
|
var strHide = $btn.data('label-hide');
|
|
|
|
if (!$panel.prop('hidden')) {
|
|
$panel.prop('hidden', true);
|
|
$btn.attr('aria-expanded', 'false');
|
|
if (strShow) { $btn.text(strShow); }
|
|
return;
|
|
}
|
|
|
|
$panel.prop('hidden', false);
|
|
$btn.attr('aria-expanded', 'true');
|
|
if (strHide) { $btn.text(strHide); }
|
|
|
|
if ($panel.attr('data-loaded') === '1') { return; }
|
|
|
|
$panel.html(ms1LoaderInlineHtml());
|
|
$.post((cfg.domain || '') + '/ajax_inscr_gestion.php', {
|
|
a: 'transaction_resume',
|
|
no_panier: $btn.data('no_panier'),
|
|
lang: cfg.lang || 'fr'
|
|
}, function (result) {
|
|
if (fxInscrRedirectIfSessionExpired(result)) {
|
|
return;
|
|
}
|
|
if (result && result.state === 'ok' && result.html) {
|
|
$panel.html(result.html);
|
|
$panel.attr('data-loaded', '1');
|
|
} else {
|
|
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + ((result && result.message) || t('errLoad')) + '</div>');
|
|
}
|
|
}, 'json').fail(function () {
|
|
$panel.html('<div style="background:#feecec;border:1px solid #f2b8b5;padding:10px;border-radius:8px;">\u26a0\ufe0f ' + t('errNetwork') + '</div>');
|
|
});
|
|
});
|
|
|
|
$(document).on('submit', '#refund-form', function (e) {
|
|
e.preventDefault();
|
|
var form = this;
|
|
var url = form.getAttribute('data-refund-url');
|
|
var $msg = $('#refund-msg');
|
|
|
|
function showMsg(text, ok) {
|
|
$msg.css({
|
|
display: 'block',
|
|
background: ok ? '#e9f8ef' : '#feecec',
|
|
border: ok ? '1px solid #b8e3c5' : '1px solid #f2b8b5'
|
|
}).text(text);
|
|
}
|
|
|
|
showMsg('\u23f3 Traitement en cours...', false);
|
|
if (window.Ms1Loader) {
|
|
Ms1Loader.show(cfg.preloaderWait || undefined);
|
|
}
|
|
|
|
fetch(url, { method: 'POST', body: new FormData(form) })
|
|
.then(function (res) {
|
|
if (!res.ok) { throw new Error('network'); }
|
|
return res.json();
|
|
})
|
|
.then(function (json) {
|
|
if (window.Ms1Loader) {
|
|
Ms1Loader.hide();
|
|
}
|
|
if (json && json.success) {
|
|
showMsg('\u2705 ' + (json.message || t('refundOk')), true);
|
|
onRefundSuccess(form, json);
|
|
} else {
|
|
showMsg('\u26a0\ufe0f ' + ((json && json.message) || t('errRefund')), false);
|
|
}
|
|
})
|
|
.catch(function () {
|
|
if (window.Ms1Loader) {
|
|
Ms1Loader.hide();
|
|
}
|
|
showMsg('\u26a0\ufe0f ' + t('errNetwork'), false);
|
|
});
|
|
});
|
|
})();
|
|
});
|
|
}
|
|
(function () {
|
|
var qrPanel = document.getElementById('inscr-gestion-qr-panel');
|
|
if (!qrPanel) {
|
|
return;
|
|
}
|
|
|
|
var qrReaderEl = document.getElementById('inscr-gestion-qr-reader');
|
|
var qrFeedback = document.getElementById('inscr-gestion-qr-feedback');
|
|
var qrCamera = null;
|
|
var qrStarting = false;
|
|
var lastQrRaw = '';
|
|
var qrResolvePending = false;
|
|
var qrLibPromise = null;
|
|
|
|
function loadHtml5Qrcode() {
|
|
if (window.Html5Qrcode) {
|
|
return Promise.resolve();
|
|
}
|
|
if (qrLibPromise) {
|
|
return qrLibPromise;
|
|
}
|
|
qrLibPromise = new Promise(function (resolve, reject) {
|
|
var script = document.createElement('script');
|
|
script.src = 'https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js';
|
|
script.onload = function () { resolve(); };
|
|
script.onerror = function () {
|
|
qrLibPromise = null;
|
|
reject(new Error('load'));
|
|
};
|
|
document.head.appendChild(script);
|
|
});
|
|
return qrLibPromise;
|
|
}
|
|
|
|
function showQrFeedback(type, message) {
|
|
if (!qrFeedback) {
|
|
return;
|
|
}
|
|
qrFeedback.className = 'alert mb-2 alert-' + type;
|
|
qrFeedback.textContent = message;
|
|
qrFeedback.classList.remove('d-none');
|
|
}
|
|
|
|
function hideQrFeedback() {
|
|
if (!qrFeedback) {
|
|
return;
|
|
}
|
|
qrFeedback.classList.add('d-none');
|
|
qrFeedback.textContent = '';
|
|
}
|
|
|
|
function stopQrScanner() {
|
|
if (!qrCamera) {
|
|
return Promise.resolve();
|
|
}
|
|
var instance = qrCamera;
|
|
qrCamera = null;
|
|
return instance.stop().catch(function () {
|
|
return null;
|
|
}).then(function () {
|
|
try {
|
|
instance.clear();
|
|
} catch (e) {
|
|
/* ignore */
|
|
}
|
|
});
|
|
}
|
|
|
|
function focusQrCameraView() {
|
|
var wrap = document.getElementById('inscr-gestion-qr-reader-wrap');
|
|
if (!wrap) {
|
|
return;
|
|
}
|
|
// MSIN-4401 — ramener la "carte" caméra au centre dès l'ouverture (mobile + desktop).
|
|
try {
|
|
wrap.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'});
|
|
} catch (e) {
|
|
wrap.scrollIntoView(true);
|
|
}
|
|
}
|
|
|
|
function startQrScanner() {
|
|
if (qrStarting || qrCamera || !qrReaderEl || !qrPanel.open) {
|
|
return;
|
|
}
|
|
qrStarting = true;
|
|
hideQrFeedback();
|
|
lastQrRaw = '';
|
|
focusQrCameraView();
|
|
|
|
loadHtml5Qrcode().then(function () {
|
|
if (!qrPanel.open) {
|
|
qrStarting = false;
|
|
return;
|
|
}
|
|
var config = window.Html5QrcodeSupportedFormats
|
|
? {formatsToSupport: [Html5QrcodeSupportedFormats.QR_CODE]}
|
|
: undefined;
|
|
qrCamera = new Html5Qrcode('inscr-gestion-qr-reader', config);
|
|
var qrboxSize = function (viewfinderWidth, viewfinderHeight) {
|
|
var edge = Math.floor(Math.min(viewfinderWidth, viewfinderHeight) * 0.72);
|
|
edge = Math.max(180, Math.min(edge, 320));
|
|
return {width: edge, height: edge};
|
|
};
|
|
return qrCamera.start(
|
|
{facingMode: 'environment'},
|
|
{fps: 8, qrbox: qrboxSize, aspectRatio: 1},
|
|
onQrDecoded
|
|
);
|
|
}).catch(function () {
|
|
showQrFeedback('danger', qrPanel.getAttribute('data-qr-msg-camera') || '');
|
|
}).then(function () {
|
|
qrStarting = false;
|
|
if (qrPanel.open) {
|
|
window.setTimeout(focusQrCameraView, 80);
|
|
}
|
|
});
|
|
}
|
|
|
|
function onQrDecoded(decodedText) {
|
|
if (!decodedText || decodedText === lastQrRaw || qrResolvePending) {
|
|
return;
|
|
}
|
|
lastQrRaw = decodedText;
|
|
qrResolvePending = true;
|
|
|
|
var resolveUrl = qrPanel.getAttribute('data-qr-resolve-url');
|
|
var eveId = qrPanel.getAttribute('data-qr-eve-id');
|
|
var lang = qrPanel.getAttribute('data-qr-lang') || 'fr';
|
|
if (!resolveUrl || !eveId) {
|
|
qrResolvePending = false;
|
|
return;
|
|
}
|
|
|
|
var body = new URLSearchParams();
|
|
body.set('qr', decodedText);
|
|
body.set('promoteur_eve_id', eveId);
|
|
body.set('lang', lang);
|
|
|
|
fetch(resolveUrl, {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
|
|
body: body.toString(),
|
|
credentials: 'same-origin'
|
|
}).then(function (response) {
|
|
return response.json();
|
|
}).then(function (data) {
|
|
qrResolvePending = false;
|
|
if (fxInscrRedirectIfSessionExpired(data)) {
|
|
return;
|
|
}
|
|
if (!data || !data.state) {
|
|
showQrFeedback('danger', qrPanel.getAttribute('data-qr-msg-invalid') || '');
|
|
window.setTimeout(function () { lastQrRaw = ''; }, 2000);
|
|
return;
|
|
}
|
|
if (data.state === 'success' && data.pec_id) {
|
|
stopQrScanner().then(function () {
|
|
var url = new URL(window.location.href);
|
|
url.searchParams.set('pec_id', String(data.pec_id));
|
|
url.searchParams.set('from_qr', '1');
|
|
window.location.href = url.toString();
|
|
});
|
|
return;
|
|
}
|
|
if (data.state === 'wrong_event') {
|
|
var wrongTpl = qrPanel.getAttribute('data-qr-msg-wrong') || '';
|
|
showQrFeedback('warning', data.message || wrongTpl.replace('%s', data.eve_nom || ''));
|
|
window.setTimeout(function () { lastQrRaw = ''; }, 2500);
|
|
return;
|
|
}
|
|
showQrFeedback('danger', data.message || qrPanel.getAttribute('data-qr-msg-invalid') || '');
|
|
window.setTimeout(function () { lastQrRaw = ''; }, 2000);
|
|
}).catch(function () {
|
|
qrResolvePending = false;
|
|
showQrFeedback('danger', qrPanel.getAttribute('data-qr-msg-invalid') || '');
|
|
window.setTimeout(function () { lastQrRaw = ''; }, 2000);
|
|
});
|
|
}
|
|
|
|
qrPanel.addEventListener('toggle', function () {
|
|
if (qrPanel.open) {
|
|
startQrScanner();
|
|
} else {
|
|
stopQrScanner();
|
|
hideQrFeedback();
|
|
lastQrRaw = '';
|
|
qrResolvePending = false;
|
|
}
|
|
});
|
|
})();
|
|
|
|
|
|
(function () {
|
|
var BIB_MAX_LEN = 6;
|
|
var OCR_MAX_WIDTH = 640;
|
|
var tesseractPromise = null;
|
|
var globalOcrWorker = null;
|
|
var globalOcrWorkerPromise = null;
|
|
var activeScan = null;
|
|
|
|
function loadTesseract() {
|
|
if (window.Tesseract) {
|
|
return Promise.resolve();
|
|
}
|
|
if (tesseractPromise) {
|
|
return tesseractPromise;
|
|
}
|
|
tesseractPromise = new Promise(function (resolve, reject) {
|
|
var script = document.createElement('script');
|
|
script.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js';
|
|
script.onload = function () { resolve(); };
|
|
script.onerror = function () {
|
|
tesseractPromise = null;
|
|
reject(new Error('load'));
|
|
};
|
|
document.head.appendChild(script);
|
|
});
|
|
return tesseractPromise;
|
|
}
|
|
|
|
function getGlobalOcrWorker() {
|
|
if (globalOcrWorker) {
|
|
return Promise.resolve(globalOcrWorker);
|
|
}
|
|
if (globalOcrWorkerPromise) {
|
|
return globalOcrWorkerPromise;
|
|
}
|
|
globalOcrWorkerPromise = loadTesseract().then(function () {
|
|
return Tesseract.createWorker('eng');
|
|
}).then(function (worker) {
|
|
globalOcrWorker = worker;
|
|
// Chiffres seulement + bloc (dossard type "1020", pas une ligne unique fragile).
|
|
var params = {tessedit_char_whitelist: '0123456789'};
|
|
if (window.Tesseract && Tesseract.PSM && Tesseract.PSM.SINGLE_BLOCK) {
|
|
params.tessedit_pageseg_mode = Tesseract.PSM.SINGLE_BLOCK;
|
|
}
|
|
return worker.setParameters(params).then(function () {
|
|
return globalOcrWorker;
|
|
});
|
|
}).catch(function (err) {
|
|
globalOcrWorkerPromise = null;
|
|
throw err;
|
|
});
|
|
return globalOcrWorkerPromise;
|
|
}
|
|
|
|
function preloadOcrWorker() {
|
|
return getGlobalOcrWorker().catch(function () {
|
|
/* ignore preload errors */
|
|
});
|
|
}
|
|
|
|
// MSIN-4401 — Extraire UN numero de dossard :
|
|
// - manuscrit "5 2 1" → coller les chiffres isoles → 521
|
|
// - dossard "1 KM" + "1020" → garder le plus long groupe → 1020 (pas 11020)
|
|
function extractBibNumber(raw) {
|
|
var matches = String(raw || '').match(/\d+/g);
|
|
if (!matches || !matches.length) {
|
|
return '';
|
|
}
|
|
var i;
|
|
var allSingles = true;
|
|
for (i = 0; i < matches.length; i++) {
|
|
if (matches[i].length !== 1) {
|
|
allSingles = false;
|
|
break;
|
|
}
|
|
}
|
|
if (allSingles) {
|
|
var joined = matches.join('');
|
|
return (joined.length >= 1 && joined.length <= BIB_MAX_LEN) ? joined : '';
|
|
}
|
|
var best = '';
|
|
for (i = 0; i < matches.length; i++) {
|
|
if (matches[i].length <= BIB_MAX_LEN && matches[i].length > best.length) {
|
|
best = matches[i];
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// Portion du flux source visible dans le cadre (object-fit:cover).
|
|
function getCoverVisibleRect(srcW, srcH, displayEl) {
|
|
var vw = srcW;
|
|
var vh = srcH;
|
|
var dw = displayEl.clientWidth || 1;
|
|
var dh = displayEl.clientHeight || 1;
|
|
var videoRatio = vw / vh;
|
|
var displayRatio = dw / dh;
|
|
var renderedW;
|
|
var renderedH;
|
|
var offsetX;
|
|
var offsetY;
|
|
if (videoRatio > displayRatio) {
|
|
renderedH = vh;
|
|
renderedW = vh * displayRatio;
|
|
offsetX = (vw - renderedW) / 2;
|
|
offsetY = 0;
|
|
} else {
|
|
renderedW = vw;
|
|
renderedH = vw / displayRatio;
|
|
offsetX = 0;
|
|
offsetY = (vh - renderedH) / 2;
|
|
}
|
|
return {
|
|
offsetX: offsetX,
|
|
offsetY: offsetY,
|
|
renderedW: renderedW,
|
|
renderedH: renderedH
|
|
};
|
|
}
|
|
|
|
// MSIN-4401 — Crop = cadre blanc mesure a l'ecran (pas de ratios JS/CSS divergents).
|
|
// Relatif au display, puis mappe en pixels source via object-fit:cover.
|
|
function getTargetCropRect(srcW, srcH, displayEl, targetEl) {
|
|
var dw = displayEl && displayEl.clientWidth ? displayEl.clientWidth : 1;
|
|
var dh = displayEl && displayEl.clientHeight ? displayEl.clientHeight : 1;
|
|
var relLeft = 0.225;
|
|
var relTop = 0.35;
|
|
var relW = 0.55;
|
|
var relH = 0.3;
|
|
if (targetEl && displayEl && targetEl.getBoundingClientRect && displayEl.getBoundingClientRect) {
|
|
var tr = targetEl.getBoundingClientRect();
|
|
var dr = displayEl.getBoundingClientRect();
|
|
if (dr.width > 0 && dr.height > 0 && tr.width > 0 && tr.height > 0) {
|
|
relLeft = (tr.left - dr.left) / dr.width;
|
|
relTop = (tr.top - dr.top) / dr.height;
|
|
relW = tr.width / dr.width;
|
|
relH = tr.height / dr.height;
|
|
}
|
|
}
|
|
if (relW < 0.05) { relW = 0.05; }
|
|
if (relH < 0.05) { relH = 0.05; }
|
|
if (relLeft < 0) { relLeft = 0; }
|
|
if (relTop < 0) { relTop = 0; }
|
|
if (relLeft + relW > 1) { relW = 1 - relLeft; }
|
|
if (relTop + relH > 1) { relH = 1 - relTop; }
|
|
|
|
var vis = getCoverVisibleRect(srcW, srcH, displayEl || {clientWidth: 1, clientHeight: 1});
|
|
var cropW = Math.max(1, Math.round(vis.renderedW * relW));
|
|
var cropH = Math.max(1, Math.round(vis.renderedH * relH));
|
|
var sx = Math.round(vis.offsetX + vis.renderedW * relLeft);
|
|
var sy = Math.round(vis.offsetY + vis.renderedH * relTop);
|
|
if (sx < 0) { sx = 0; }
|
|
if (sy < 0) { sy = 0; }
|
|
if (sx + cropW > srcW) { cropW = Math.max(1, srcW - sx); }
|
|
if (sy + cropH > srcH) { cropH = Math.max(1, srcH - sy); }
|
|
return {sx: sx, sy: sy, cropW: cropW, cropH: cropH};
|
|
}
|
|
|
|
// Renforce legerement le contraste (trop fort = hasard sur telephone).
|
|
function preprocessOcrCanvas(srcCanvas) {
|
|
var w = srcCanvas.width;
|
|
var h = srcCanvas.height;
|
|
var scale = (w < 320) ? 2 : 1;
|
|
var out = document.createElement('canvas');
|
|
out.width = Math.max(1, Math.round(w * scale));
|
|
out.height = Math.max(1, Math.round(h * scale));
|
|
var ctx = out.getContext('2d');
|
|
ctx.imageSmoothingEnabled = true;
|
|
ctx.drawImage(srcCanvas, 0, 0, out.width, out.height);
|
|
var img = ctx.getImageData(0, 0, out.width, out.height);
|
|
var d = img.data;
|
|
var j;
|
|
for (j = 0; j < d.length; j += 4) {
|
|
var gray = (0.299 * d[j] + 0.587 * d[j + 1] + 0.114 * d[j + 2]);
|
|
gray = (gray - 128) * 1.25 + 128;
|
|
if (gray < 0) { gray = 0; }
|
|
if (gray > 255) { gray = 255; }
|
|
d[j] = d[j + 1] = d[j + 2] = gray;
|
|
}
|
|
ctx.putImageData(img, 0, 0);
|
|
return out;
|
|
}
|
|
|
|
// Crop exactement la zone du cadre blanc (mesure DOM).
|
|
// blnCloud = JPEG brut pour Vision (pas de preprocess Tesseract).
|
|
function cropTargetFromSource(source, srcW, srcH, displayEl, targetEl, blnCloud) {
|
|
if (!source || !srcW || !srcH) {
|
|
return null;
|
|
}
|
|
var rect = getTargetCropRect(srcW, srcH, displayEl, targetEl);
|
|
var canvas = document.createElement('canvas');
|
|
var outW = rect.cropW;
|
|
var outH = rect.cropH;
|
|
var intMaxW = blnCloud ? 1280 : OCR_MAX_WIDTH;
|
|
if (rect.cropW > intMaxW) {
|
|
var scaleDown = intMaxW / rect.cropW;
|
|
outW = intMaxW;
|
|
outH = Math.max(1, Math.round(rect.cropH * scaleDown));
|
|
}
|
|
canvas.width = outW;
|
|
canvas.height = outH;
|
|
canvas.getContext('2d').drawImage(
|
|
source,
|
|
rect.sx, rect.sy, rect.cropW, rect.cropH,
|
|
0, 0, outW, outH
|
|
);
|
|
return blnCloud ? canvas : preprocessOcrCanvas(canvas);
|
|
}
|
|
|
|
// MSIN-4401 — Toujours depuis la video affichee (WYSIWYG). ImageCapture.grabFrame
|
|
// a une autre FOV/ratio que l'apercu → decalage cadre vs OCR (ex. lit 26 au lieu de 17).
|
|
function grabOcrCanvas(session, blnCloud) {
|
|
var videoEl = session.video;
|
|
var displayEl = session.captureEl;
|
|
var targetEl = session.targetEl
|
|
|| (displayEl ? displayEl.querySelector('.inscr-gestion-bib-scan-target') : null);
|
|
if (!videoEl || !videoEl.videoWidth) {
|
|
return Promise.resolve(null);
|
|
}
|
|
return Promise.resolve(cropTargetFromSource(
|
|
videoEl,
|
|
videoEl.videoWidth,
|
|
videoEl.videoHeight,
|
|
displayEl,
|
|
targetEl,
|
|
!!blnCloud
|
|
));
|
|
}
|
|
|
|
function canvasToJpegBlob(canvas, quality) {
|
|
return new Promise(function (resolve, reject) {
|
|
if (!canvas) {
|
|
reject(new Error('canvas'));
|
|
return;
|
|
}
|
|
if (!canvas.toBlob) {
|
|
try {
|
|
var dataUrl = canvas.toDataURL('image/jpeg', quality || 0.9);
|
|
var parts = dataUrl.split(',');
|
|
var bin = atob(parts[1] || '');
|
|
var arr = new Uint8Array(bin.length);
|
|
var i;
|
|
for (i = 0; i < bin.length; i++) {
|
|
arr[i] = bin.charCodeAt(i);
|
|
}
|
|
resolve(new Blob([arr], {type: 'image/jpeg'}));
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
return;
|
|
}
|
|
canvas.toBlob(function (blob) {
|
|
if (blob) {
|
|
resolve(blob);
|
|
} else {
|
|
reject(new Error('blob'));
|
|
}
|
|
}, 'image/jpeg', quality || 0.9);
|
|
});
|
|
}
|
|
|
|
function bibOcrErrorMessage(result) {
|
|
var code = result && result.message ? String(result.message) : '';
|
|
if (code === 'ocr_not_configured') {
|
|
return t('bibOcrNotConfigured');
|
|
}
|
|
if (code === 'vision_api' || code === 'vision_http' || code === 'vision_curl') {
|
|
var detail = (result && result.detail) ? String(result.detail) : '';
|
|
return t('bibOcrVisionPrefix') + (detail || code);
|
|
}
|
|
if (code === 'image_invalid') {
|
|
return t('bibOcrImageInvalid');
|
|
}
|
|
var base = t('bibOcrUnreadable');
|
|
if (result && result.seen) {
|
|
base += ' (' + result.seen + ')';
|
|
} else if (code && code !== 'no_number') {
|
|
base += ' [' + code + ']';
|
|
}
|
|
return base;
|
|
}
|
|
|
|
function BibScanSession(block) {
|
|
this.block = block;
|
|
this.panel = block.querySelector('.inscr-gestion-bib-scan-panel');
|
|
this.toggleBtn = block.querySelector('.inscr-gestion-bib-scan-toggle');
|
|
this.statusEl = this.panel ? this.panel.querySelector('.inscr-gestion-bib-scan-status') : null;
|
|
this.captureEl = this.panel ? this.panel.querySelector('.inscr-gestion-bib-scan-capture') : null;
|
|
this.targetEl = this.captureEl ? this.captureEl.querySelector('.inscr-gestion-bib-scan-target') : null;
|
|
this.videoWrap = this.captureEl ? this.captureEl.querySelector('.inscr-gestion-bib-scan-video') : null;
|
|
this.captureLabel = this.captureEl ? this.captureEl.querySelector('.inscr-gestion-bib-scan-capture-label') : null;
|
|
this.inputId = this.panel ? this.panel.getAttribute('data-bib-input-id') : '';
|
|
this.stream = null;
|
|
this.video = null;
|
|
this.ocrBusy = false;
|
|
this.workerReady = false;
|
|
this.onCaptureClick = null;
|
|
}
|
|
|
|
BibScanSession.prototype.getInput = function () {
|
|
return this.inputId ? document.getElementById(this.inputId) : null;
|
|
};
|
|
|
|
BibScanSession.prototype.setStatus = function (text, active) {
|
|
if (!this.statusEl) {
|
|
return;
|
|
}
|
|
this.statusEl.textContent = text || '';
|
|
this.statusEl.classList.toggle('is-active', !!active);
|
|
};
|
|
|
|
BibScanSession.prototype.setCaptureReady = function (ready) {
|
|
if (this.captureEl) {
|
|
this.captureEl.classList.toggle('is-ready', !!ready);
|
|
this.captureEl.setAttribute('aria-disabled', ready ? 'false' : 'true');
|
|
}
|
|
};
|
|
|
|
// Detecte une clef i18n brute (label absent de la table info) quel que
|
|
// soit le prefixe (inscr_gestion_, ancien inscr_mobile_, etc.) : une vraie
|
|
// traduction contient des espaces/accents, jamais ce motif "cle_technique".
|
|
function isRawI18nKey(str) {
|
|
return typeof str === 'string' && /^[a-z][a-z0-9_]*$/i.test(str) && str.indexOf('_') !== -1;
|
|
}
|
|
|
|
var SCAN_MSG_I18N = {
|
|
'data-msg-scanning': 'bibScanScanning',
|
|
'data-msg-loading': 'bibScanLoading',
|
|
'data-msg-ready': 'bibScanReady',
|
|
'data-msg-found': 'bibScanFound',
|
|
'data-msg-found-search': 'bibScanFoundSearch',
|
|
'data-msg-invalid': 'bibScanInvalid',
|
|
'data-msg-camera': 'bibScanCamera'
|
|
};
|
|
|
|
// Attribut data-* si traduit, sinon libelle injecte (cfg.i18n) — jamais une clef brute.
|
|
function scanMsg(panel, attr) {
|
|
var value = panel ? panel.getAttribute(attr) : '';
|
|
if (value && !isRawI18nKey(value)) {
|
|
return value;
|
|
}
|
|
var i18nKey = SCAN_MSG_I18N[attr];
|
|
return i18nKey ? t(i18nKey) : '';
|
|
}
|
|
|
|
BibScanSession.prototype.showReady = function () {
|
|
this.setStatus(scanMsg(this.panel, '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.ocrBusy || activeScan !== self) {
|
|
return;
|
|
}
|
|
if (!self.video) {
|
|
self.setStatus(scanMsg(self.panel, 'data-msg-loading'), true);
|
|
return;
|
|
}
|
|
if (!cfg.bibOcrCloud && !self.workerReady) {
|
|
self.setStatus(scanMsg(self.panel, 'data-msg-loading'), true);
|
|
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;
|
|
}
|
|
if (this.video) {
|
|
this.video.srcObject = null;
|
|
this.video = null;
|
|
}
|
|
if (this.videoWrap) {
|
|
this.videoWrap.innerHTML = '';
|
|
}
|
|
this.ocrBusy = false;
|
|
this.workerReady = false;
|
|
this.setCaptureReady(false);
|
|
this.setStatus('');
|
|
if (this.panel) {
|
|
this.panel.hidden = true;
|
|
}
|
|
if (this.toggleBtn) {
|
|
this.toggleBtn.setAttribute('aria-expanded', 'false');
|
|
this.toggleBtn.classList.remove('is-active');
|
|
}
|
|
if (activeScan === this) {
|
|
activeScan = null;
|
|
}
|
|
};
|
|
|
|
BibScanSession.prototype.isSearchContext = function () {
|
|
var form = this.block ? this.block.closest('form.inscr-gestion-form') : null;
|
|
return !!(form && !form.closest('.inscr-gestion-fiche'));
|
|
};
|
|
|
|
BibScanSession.prototype.submitSearchForm = function (form) {
|
|
if (!form) {
|
|
return;
|
|
}
|
|
var submitBtn = form.querySelector('button[type="submit"][name="btn_recherche"], input[type="submit"][name="btn_recherche"]');
|
|
if (submitBtn) {
|
|
submitBtn.click();
|
|
return;
|
|
}
|
|
var hidden = document.createElement('input');
|
|
hidden.type = 'hidden';
|
|
hidden.name = 'btn_recherche';
|
|
hidden.value = '1';
|
|
form.appendChild(hidden);
|
|
form.submit();
|
|
};
|
|
|
|
BibScanSession.prototype.applyNumber = function (strNum) {
|
|
var input = this.getInput();
|
|
if (!input) {
|
|
return;
|
|
}
|
|
input.value = strNum;
|
|
input.classList.add('inscr-gestion-bib--proposed');
|
|
var blnSearch = this.isSearchContext();
|
|
if (blnSearch) {
|
|
var tplSearch = scanMsg(this.panel, 'data-msg-found-search');
|
|
this.setStatus(tplSearch.replace('%s', strNum), true);
|
|
this.stop();
|
|
var form = this.block.closest('form.inscr-gestion-form');
|
|
window.setTimeout(function () {
|
|
BibScanSession.prototype.submitSearchForm(form);
|
|
}, 350);
|
|
return;
|
|
}
|
|
input.focus();
|
|
var tpl = scanMsg(this.panel, 'data-msg-found');
|
|
this.setStatus(tpl.replace('%s', strNum), false);
|
|
this.stop();
|
|
};
|
|
|
|
BibScanSession.prototype.runOcr = function () {
|
|
var self = this;
|
|
if (self.ocrBusy || !self.video || activeScan !== self) {
|
|
return;
|
|
}
|
|
if (!cfg.bibOcrCloud && !self.workerReady) {
|
|
return;
|
|
}
|
|
self.ocrBusy = true;
|
|
self.setCaptureReady(false);
|
|
if (self.captureLabel) {
|
|
self.captureLabel.hidden = true;
|
|
}
|
|
self.setStatus(scanMsg(self.panel, 'data-msg-scanning'), true);
|
|
|
|
// MSIN-4401 — Chemin produit : photo → Google Vision (serveur). Plus fiable que Tesseract mobile.
|
|
if (cfg.bibOcrCloud) {
|
|
grabOcrCanvas(self, true).then(function (canvas) {
|
|
if (!canvas) {
|
|
throw new Error('capture');
|
|
}
|
|
return canvasToJpegBlob(canvas, 0.9).then(function (blob) {
|
|
var fd = new FormData();
|
|
fd.append('a', 'bib_ocr');
|
|
fd.append('lang', cfg.lang || 'fr');
|
|
fd.append('image', blob, 'bib.jpg');
|
|
return fetch((cfg.domain || '') + '/ajax_inscr_gestion.php', {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
body: fd
|
|
});
|
|
});
|
|
}).then(function (res) {
|
|
return res.json().then(function (result) {
|
|
result = result || {};
|
|
result._http = res.status;
|
|
return result;
|
|
}, function () {
|
|
return {state: 'error', message: 'vision_parse', detail: 'HTTP ' + res.status};
|
|
});
|
|
}).then(function (result) {
|
|
if (fxInscrRedirectIfSessionExpired(result)) {
|
|
return;
|
|
}
|
|
if (result && result.state === 'ok' && result.number) {
|
|
self.applyNumber(String(result.number));
|
|
return;
|
|
}
|
|
self.setStatus(bibOcrErrorMessage(result), false);
|
|
// Laisse l'erreur visible (pas de flash) — disparait au prochain tap / ready.
|
|
}).catch(function () {
|
|
self.setStatus(t('bibOcrNetwork'), false);
|
|
}).then(function () {
|
|
self.ocrBusy = false;
|
|
self.setCaptureReady(true);
|
|
if (self.captureLabel) {
|
|
self.captureLabel.hidden = false;
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
grabOcrCanvas(self).then(function (canvas) {
|
|
if (!canvas) {
|
|
throw new Error('capture');
|
|
}
|
|
return getGlobalOcrWorker().then(function (worker) {
|
|
return worker.recognize(canvas);
|
|
});
|
|
}).then(function (result) {
|
|
var rawText = result && result.data ? String(result.data.text || '') : '';
|
|
var strNum = extractBibNumber(rawText);
|
|
if (strNum) {
|
|
self.applyNumber(strNum);
|
|
return;
|
|
}
|
|
var seen = String(rawText || '').replace(/\D/g, '');
|
|
var base = scanMsg(self.panel, 'data-msg-invalid');
|
|
if (seen) {
|
|
base += ' (' + seen + ')';
|
|
}
|
|
self.setStatus(base, false);
|
|
self.ocrBusy = false;
|
|
self.setCaptureReady(true);
|
|
if (self.captureLabel) {
|
|
self.captureLabel.hidden = false;
|
|
}
|
|
}).catch(function () {
|
|
self.setStatus(scanMsg(self.panel, 'data-msg-invalid'), false);
|
|
self.ocrBusy = false;
|
|
self.setCaptureReady(true);
|
|
if (self.captureLabel) {
|
|
self.captureLabel.hidden = false;
|
|
}
|
|
});
|
|
};
|
|
|
|
BibScanSession.prototype.scrollCaptureIntoView = function () {
|
|
var el = this.captureEl || this.panel;
|
|
if (!el || !el.scrollIntoView) {
|
|
return;
|
|
}
|
|
try {
|
|
el.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'});
|
|
} catch (eScroll) {
|
|
el.scrollIntoView(true);
|
|
}
|
|
};
|
|
|
|
BibScanSession.prototype.start = function () {
|
|
var self = this;
|
|
if (!self.panel || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
self.setStatus(scanMsg(self.panel, 'data-msg-camera'), false);
|
|
return;
|
|
}
|
|
if (activeScan && activeScan !== self) {
|
|
activeScan.stop();
|
|
}
|
|
activeScan = self;
|
|
self.panel.hidden = false;
|
|
if (self.toggleBtn) {
|
|
self.toggleBtn.setAttribute('aria-expanded', 'true');
|
|
self.toggleBtn.classList.add('is-active');
|
|
}
|
|
// MSIN-4401 — Centrer le viseur dans l'ecran (evite la moitie hors viewport).
|
|
self.scrollCaptureIntoView();
|
|
window.setTimeout(function () {
|
|
if (activeScan === self) {
|
|
self.scrollCaptureIntoView();
|
|
}
|
|
}, 120);
|
|
self.setStatus(scanMsg(self.panel, 'data-msg-loading'), true);
|
|
|
|
var cameraPromise = navigator.mediaDevices.getUserMedia({
|
|
video: {
|
|
facingMode: {ideal: 'environment'},
|
|
width: {ideal: 1920},
|
|
height: {ideal: 1080}
|
|
},
|
|
audio: false
|
|
});
|
|
var readyPromise = cfg.bibOcrCloud
|
|
? cameraPromise.then(function (stream) { return [stream, null]; })
|
|
: Promise.all([cameraPromise, getGlobalOcrWorker()]);
|
|
|
|
readyPromise.then(function (results) {
|
|
if (activeScan !== self) {
|
|
results[0].getTracks().forEach(function (track) { track.stop(); });
|
|
return;
|
|
}
|
|
self.workerReady = cfg.bibOcrCloud ? true : !!results[1];
|
|
self.stream = results[0];
|
|
self.video = document.createElement('video');
|
|
self.video.setAttribute('playsinline', 'true');
|
|
self.video.setAttribute('autoplay', 'true');
|
|
self.video.muted = true;
|
|
self.video.srcObject = self.stream;
|
|
self.videoWrap.innerHTML = '';
|
|
self.videoWrap.appendChild(self.video);
|
|
return self.video.play();
|
|
}).then(function () {
|
|
if (activeScan !== self || !self.workerReady) {
|
|
return;
|
|
}
|
|
self.bindCapture();
|
|
self.showReady();
|
|
// Apres affichage video : recentrer (layout final).
|
|
self.scrollCaptureIntoView();
|
|
}).catch(function () {
|
|
self.setStatus(scanMsg(self.panel, 'data-msg-camera'), false);
|
|
self.stop();
|
|
});
|
|
};
|
|
|
|
if (document.querySelector('.inscr-gestion-bib-scan-toggle') && !cfg.bibOcrCloud) {
|
|
preloadOcrWorker();
|
|
}
|
|
|
|
document.querySelectorAll('.inscr-gestion-field-block[data-inscr-field="bib"]').forEach(function (block) {
|
|
var toggleBtn = block.querySelector('.inscr-gestion-bib-scan-toggle');
|
|
var panel = block.querySelector('.inscr-gestion-bib-scan-panel');
|
|
if (!toggleBtn || !panel) {
|
|
return;
|
|
}
|
|
var session = new BibScanSession(block);
|
|
toggleBtn.addEventListener('click', function () {
|
|
if (activeScan === session && !panel.hidden) {
|
|
session.stop();
|
|
return;
|
|
}
|
|
session.start();
|
|
});
|
|
var input = session.getInput();
|
|
if (input) {
|
|
input.addEventListener('input', function () {
|
|
input.classList.remove('inscr-gestion-bib--proposed');
|
|
});
|
|
}
|
|
});
|
|
|
|
var ficheFromQr = document.querySelector('.inscr-gestion-fiche[data-from-qr="1"]');
|
|
if (ficheFromQr) {
|
|
var editableSection = ficheFromQr.querySelector('.inscr-gestion-fiche-section--editable');
|
|
if (editableSection) {
|
|
window.setTimeout(function () {
|
|
editableSection.scrollIntoView({behavior: 'smooth', block: 'start'});
|
|
}, 80);
|
|
}
|
|
}
|
|
})();
|
|
|
|
|
|
(function () {
|
|
var moduleInscr = document.getElementById('module-inscr-gestion');
|
|
if (!moduleInscr) {
|
|
return;
|
|
}
|
|
|
|
var params = new URLSearchParams(window.location.search);
|
|
if (params.get('pec_id')) {
|
|
moduleInscr.scrollIntoView({block: 'start'});
|
|
} else if (params.get('pg') && parseInt(params.get('pg'), 10) > 1) {
|
|
var listAnchor = document.getElementById('inscr-gestion-list');
|
|
if (listAnchor) {
|
|
listAnchor.scrollIntoView({block: 'start'});
|
|
}
|
|
}
|
|
moduleInscr.addEventListener('click', function (e) {
|
|
var btnToggle = e.target.closest('.epr-block-toggle');
|
|
if (!btnToggle || !moduleInscr.contains(btnToggle)) {
|
|
return;
|
|
}
|
|
var block = btnToggle.closest('.epr-block');
|
|
if (!block) {
|
|
return;
|
|
}
|
|
var collapsed = block.classList.toggle('is-collapsed');
|
|
btnToggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
|
var label = collapsed
|
|
? (btnToggle.getAttribute('data-label-expand') || '')
|
|
: (btnToggle.getAttribute('data-label-collapse') || '');
|
|
if (label) {
|
|
btnToggle.setAttribute('aria-label', label);
|
|
btnToggle.setAttribute('title', label);
|
|
}
|
|
var icon = btnToggle.querySelector('.fa');
|
|
if (icon) {
|
|
icon.classList.toggle('fa-chevron-up', !collapsed);
|
|
icon.classList.toggle('fa-chevron-down', collapsed);
|
|
}
|
|
});
|
|
|
|
var inscrLoaderMsg = cfg.preloaderWait || '';
|
|
|
|
moduleInscr.addEventListener('click', function (e) {
|
|
var factureLink = e.target.closest('a[href*="facture.php"]');
|
|
if (!factureLink || !moduleInscr.contains(factureLink)) {
|
|
return;
|
|
}
|
|
if (window.Ms1Loader) {
|
|
Ms1Loader.show(inscrLoaderMsg);
|
|
window.setTimeout(function () {
|
|
Ms1Loader.hide();
|
|
}, 3000);
|
|
}
|
|
});
|
|
|
|
moduleInscr.addEventListener('submit', function (e) {
|
|
var form = e.target;
|
|
if (!form || !moduleInscr.contains(form)) {
|
|
return;
|
|
}
|
|
if (!form.classList.contains('inscr-gestion-form')) {
|
|
return;
|
|
}
|
|
if (window.Ms1Loader) {
|
|
Ms1Loader.show(inscrLoaderMsg);
|
|
}
|
|
});
|
|
|
|
// MSIN-4401 — Libellé du multi-select statut (Tous / N sélectionnés).
|
|
(function () {
|
|
function updateStatutMultiSummary(details) {
|
|
if (!details) {
|
|
return;
|
|
}
|
|
var textEl = details.querySelector('.inscr-gestion-statut-multi__summary-text');
|
|
if (!textEl) {
|
|
return;
|
|
}
|
|
var checked = details.querySelectorAll('input[name="sel_rech_statut[]"]:checked');
|
|
var n = checked.length;
|
|
var labelAll = details.getAttribute('data-label-all') || '';
|
|
var labelN = details.getAttribute('data-label-n') || '%d';
|
|
textEl.textContent = (n === 0) ? labelAll : labelN.replace('%d', String(n));
|
|
}
|
|
|
|
moduleInscr.addEventListener('change', function (e) {
|
|
var input = e.target;
|
|
if (!input || input.name !== 'sel_rech_statut[]') {
|
|
return;
|
|
}
|
|
updateStatutMultiSummary(input.closest('.inscr-gestion-statut-multi'));
|
|
});
|
|
})();
|
|
})();
|
|
|
|
})();
|