Browse Source

Gestion multi caméra dans le scan

Ajout de fontawesome pour les icônes
Ajout d'un bouton de déconnexion sur la page principale
Ajout de la redirection après login dans settings.py
Ajout de la gestion des erreurs 404 et 500 avec des pages personnalisées
Ajout de DataTables pour la gestion des tableaux dans course_detail.html
master
scayac 3 months ago
parent
commit
b04157ff7a
  1. 24
      courses/templatetags/temps_format.py
  2. 5
      crossapp/settings.py
  3. 1
      requirements.txt
  4. 4
      scan/views.py
  5. 1
      static/bootstrap/dataTables.bootstrap4.min.css
  6. 4
      static/bootstrap/dataTables.bootstrap4.min.js
  7. 6
      static/jquery/datatables.fr.js
  8. 4
      static/jquery/jquery.dataTables.min.js
  9. BIN
      static/person-running-solid-full.png
  10. 8
      templates/404.html
  11. 8
      templates/500.html
  12. 5
      templates/base.html
  13. 59
      templates/course_detail.html
  14. 4
      templates/dossards.html
  15. 39
      templates/main.html
  16. 2
      templates/registration/login.html
  17. 87
      templates/scan.html

24
courses/templatetags/temps_format.py

@ -0,0 +1,24 @@
from django import template
register = template.Library()
import datetime
def seconds_to_hms(value):
try:
# Si value est un datetime, on convertit en secondes
if isinstance(value, datetime.timedelta):
total_seconds = int(value.total_seconds())
elif isinstance(value, datetime.datetime):
# Si value est un datetime, on prend l'heure/min/sec
total_seconds = value.hour * 3600 + value.minute * 60 + value.second
else:
total_seconds = int(value)
h = total_seconds // 3600
m = (total_seconds % 3600) // 60
s = total_seconds % 60
return f"{h:02}h{m:02}m{s:02}s"
except (ValueError, TypeError, AttributeError):
return "--:--:--"
register.filter('seconds_to_hms', seconds_to_hms)

5
crossapp/settings.py

@ -54,6 +54,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'fontawesomefree',
'channels', 'channels',
'courses', 'courses',
'coureurs', 'coureurs',
@ -144,7 +145,11 @@ USE_TZ = True
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static'] STATICFILES_DIRS = [BASE_DIR / 'static']
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Redirection après login
LOGIN_REDIRECT_URL = '/'

1
requirements.txt

@ -1,6 +1,7 @@
Django>=5.2.6 Django>=5.2.6
python-dotenv python-dotenv
channels channels
fontawesomefree==6.6.0
reportlab reportlab
qrcode qrcode
pandas pandas

4
scan/views.py

@ -6,6 +6,8 @@ from django.contrib.auth.decorators import login_required
from courses.models import Course, Arrivee from courses.models import Course, Arrivee
from coureurs.models import Coureur from coureurs.models import Coureur
from django.utils import timezone from django.utils import timezone
from courses.templatetags.temps_format import seconds_to_hms
from .forms import ScanForm from .forms import ScanForm
@login_required @login_required
@ -40,7 +42,7 @@ def scan_view(request):
'nom': coureur.nom, 'nom': coureur.nom,
'classe': coureur.classe, 'classe': coureur.classe,
'rang': rang, 'rang': rang,
'temps': str(temps) 'temps': str(seconds_to_hms(temps))
} }
# Broadcast websocket # Broadcast websocket
channel_layer = get_channel_layer() channel_layer = get_channel_layer()

1
static/bootstrap/dataTables.bootstrap4.min.css vendored

File diff suppressed because one or more lines are too long

4
static/bootstrap/dataTables.bootstrap4.min.js vendored

@ -0,0 +1,4 @@
/*! DataTables Bootstrap 4 integration
* ©2011-2017 SpryMedia Ltd - datatables.net/license
*/
!function(t){var n,o;"function"==typeof define&&define.amd?define(["jquery","datatables.net"],function(e){return t(e,window,document)}):"object"==typeof exports?(n=require("jquery"),o=function(e,a){a.fn.dataTable||require("datatables.net")(e,a)},"undefined"==typeof window?module.exports=function(e,a){return e=e||window,a=a||n(e),o(e,a),t(a,0,e.document)}:(o(window,n),module.exports=t(n,window,window.document))):t(jQuery,window,document)}(function(x,e,n,o){"use strict";var r=x.fn.dataTable;return x.extend(!0,r.defaults,{dom:"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",renderer:"bootstrap"}),x.extend(r.ext.classes,{sWrapper:"dataTables_wrapper dt-bootstrap4",sFilterInput:"form-control form-control-sm",sLengthSelect:"custom-select custom-select-sm form-control form-control-sm",sProcessing:"dataTables_processing card",sPageButton:"paginate_button page-item"}),r.ext.renderer.pageButton.bootstrap=function(i,e,d,a,l,c){function u(e,a){for(var t,n,o=function(e){e.preventDefault(),x(e.currentTarget).hasClass("disabled")||m.page()==e.data.action||m.page(e.data.action).draw("page")},r=0,s=a.length;r<s;r++)if(t=a[r],Array.isArray(t))u(e,t);else{switch(f=p="",t){case"ellipsis":p="&#x2026;",f="disabled";break;case"first":p=g.sFirst,f=t+(0<l?"":" disabled");break;case"previous":p=g.sPrevious,f=t+(0<l?"":" disabled");break;case"next":p=g.sNext,f=t+(l<c-1?"":" disabled");break;case"last":p=g.sLast,f=t+(l<c-1?"":" disabled");break;default:p=t+1,f=l===t?"active":""}p&&(n=-1!==f.indexOf("disabled"),n=x("<li>",{class:b.sPageButton+" "+f,id:0===d&&"string"==typeof t?i.sTableId+"_"+t:null}).append(x("<a>",{href:n?null:"#","aria-controls":i.sTableId,"aria-disabled":n?"true":null,"aria-label":w[t],role:"link","aria-current":"active"===f?"page":null,"data-dt-idx":t,tabindex:n?-1:i.iTabIndex,class:"page-link"}).html(p)).appendTo(e),i.oApi._fnBindAction(n,{action:t},o))}}var p,f,t,m=new r.Api(i),b=i.oClasses,g=i.oLanguage.oPaginate,w=i.oLanguage.oAria.paginate||{};try{t=x(e).find(n.activeElement).data("dt-idx")}catch(e){}u(x(e).empty().html('<ul class="pagination"/>').children("ul"),a),t!==o&&x(e).find("[data-dt-idx="+t+"]").trigger("focus")},r});

6
static/jquery/datatables.fr.js

@ -0,0 +1,6 @@
/*! DataTables French translation - v1.10.20 */
$.extend(true, $.fn.dataTable.defaults, {
language: {
url: "https://cdn.datatables.net/plug-ins/1.13.7/i18n/fr-FR.json"
}
});

4
static/jquery/jquery.dataTables.min.js vendored

File diff suppressed because one or more lines are too long

BIN
static/person-running-solid-full.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

8
templates/404.html

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-5 text-center">
<h1 class="display-4 text-danger">Erreur 404</h1>
<p class="lead">La page demandée n'existe pas ou a été déplacée.</p>
<a href="/" class="btn btn-primary">Retour à l'accueil</a>
</div>
{% endblock %}

8
templates/500.html

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-5 text-center">
<h1 class="display-4 text-danger">Erreur 500</h1>
<p class="lead">Une erreur interne est survenue. Veuillez réessayer plus tard.</p>
<a href="/" class="btn btn-primary">Retour à l'accueil</a>
</div>
{% endblock %}

5
templates/base.html

@ -5,8 +5,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>CrossApp</title> <title>CrossApp</title>
{% load static %} {% load static %}
<link rel="icon" type="image/png" href="{% static 'person-running-solid-full.png' %}">
<!-- SB Admin 2 Bootstrap CSS --> <!-- SB Admin 2 Bootstrap CSS -->
<link href="{% static 'sb-admin-2/sb-admin-2.min.css' %}" rel="stylesheet"> <link href="{% static 'sb-admin-2/sb-admin-2.min.css' %}" rel="stylesheet">
<!-- Font Awesome -->
<link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet">
<link href="{% static 'fontawesomefree/css/brands.css' %}" rel="stylesheet">
<link href="{% static 'fontawesomefree/css/solid.css' %}" rel="stylesheet">
<!-- Custom styles --> <!-- Custom styles -->
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
</head> </head>

59
templates/course_detail.html

@ -1,5 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
{% load static %}
{% load temps_format %}
<div class="container-fluid mt-4"> <div class="container-fluid mt-4">
<h1 class="h3 mb-4 text-gray-800">Course : {{ course.nom }} ({{ course.date }})</h1> <h1 class="h3 mb-4 text-gray-800">Course : {{ course.nom }} ({{ course.date }})</h1>
<div class="row"> <div class="row">
@ -12,9 +14,36 @@
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
{% if not is_started %} {% if not is_started %}
<button type="submit" name="start" class="btn btn-success">Départ</button> <button type="submit" name="start" class="btn btn-success">Départ
<i class="fa-solid fa-play"></i>
</button>
{% elif not is_finished %} {% elif not is_finished %}
<button type="submit" name="finish" class="btn btn-danger">Fin course</button> <button type="button" id="btnFinish" class="btn btn-danger">Fin course
<i class="fa-solid fa-stop"></i>
</button>
<!-- Modal confirmation fin de course -->
<div class="modal fade" id="finishModal" tabindex="-1" role="dialog" aria-labelledby="finishModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="finishModalLabel">Confirmer la fin de la course</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Êtes-vous sûr de vouloir terminer la course ? Cette action est irréversible.
</div>
<div class="modal-footer">
<form method="post" style="margin:0;">
{% csrf_token %}
<button type="submit" name="finish" class="btn btn-danger">Valider la fin</button>
</form>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
</div>
</div>
</div>
</div>
{% else %} {% else %}
<span class="badge badge-secondary">Course terminée</span> <span class="badge badge-secondary">Course terminée</span>
{% endif %} {% endif %}
@ -25,8 +54,12 @@
<div class="card-header py-3 d-flex justify-content-between align-items-center"> <div class="card-header py-3 d-flex justify-content-between align-items-center">
<h6 class="m-0 font-weight-bold text-primary">Arrivées</h6> <h6 class="m-0 font-weight-bold text-primary">Arrivées</h6>
<div> <div>
<a href="{% url 'export_csv' course.id %}" class="btn btn-outline-primary btn-sm mr-2">Export CSV</a> <a href="{% url 'export_csv' course.id %}" class="btn btn-success mb-2">
<a href="{% url 'export_pdf' course.id %}" class="btn btn-outline-danger btn-sm">Export PDF</a> <i class="fas fa-file-csv" title="Exporter en CSV"></i>
</a>
<a href="{% url 'export_pdf' course.id %}" class="btn btn-danger mb-2">
<i class="fas fa-file-pdf" title="Exporter en PDF"></i>
</a>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
@ -45,7 +78,7 @@
<td>{{ a.rang }}</td> <td>{{ a.rang }}</td>
<td>{{ a.coureur.nom }}</td> <td>{{ a.coureur.nom }}</td>
<td>{{ a.coureur.classe }}</td> <td>{{ a.coureur.classe }}</td>
<td>{{ a.temps }}</td> <td>{% if a.temps %}{{ a.temps|seconds_to_hms }}{% endif %}</td>
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="4">Aucun coureur arrivé.</td></tr> <tr><td colspan="4">Aucun coureur arrivé.</td></tr>
@ -59,6 +92,11 @@
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<!-- DataTables JS & CSS -->
<link rel="stylesheet" href="{% static 'bootstrap/dataTables.bootstrap4.min.css' %}">
<script src="{% static 'jquery/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'bootstrap/dataTables.bootstrap4.min.js' %}"></script>
<script src="{% static 'jquery/datatables.fr.js' %}"></script>
<script> <script>
const courseId = "{{ course.id }}"; const courseId = "{{ course.id }}";
const wsScheme = window.location.protocol === "https:" ? "wss" : "ws"; const wsScheme = window.location.protocol === "https:" ? "wss" : "ws";
@ -72,7 +110,18 @@ socket.onmessage = function(e) {
.then(html => { .then(html => {
const table = document.getElementById('arriveesTable').getElementsByTagName('tbody')[0]; const table = document.getElementById('arriveesTable').getElementsByTagName('tbody')[0];
table.innerHTML = html; table.innerHTML = html;
$('#arriveesTable').DataTable();
}); });
}; };
// Modal confirmation fin de course
document.getElementById('btnFinish').onclick = function() {
$('#finishModal').modal('show');
};
// Initialisation DataTables au chargement
$(document).ready(function() {
$('#arriveesTable').DataTable();
});
</script> </script>
{% endblock %} {% endblock %}

4
templates/dossards.html

@ -12,7 +12,9 @@
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<button type="submit" class="btn btn-success">Générer PDF</button> <button type="submit" class="btn btn-success">Générer PDF
<i class="fas fa-file-pdf" title="Générer PDF"></i>
</button>
</form> </form>
{% if error %} {% if error %}
<div class="alert alert-danger mt-3">{{ error }}</div> <div class="alert alert-danger mt-3">{{ error }}</div>

39
templates/main.html

@ -2,9 +2,9 @@
{% block content %} {% block content %}
<div class="container-fluid mt-4"> <div class="container-fluid mt-4">
<h1 class="h3 mb-4 text-gray-800">Bienvenue sur CrossApp</h1> <h1 class="h3 mb-4 text-gray-800">Bienvenue sur CrossApp</h1>
<div class="row align-items-stretch"> <div class="row">
<div class="col-lg-6 mb-4 d-flex"> <div class="col-12">
<div class="card shadow mb-4 h-100 w-100"> <div class="card shadow mb-4">
<div class="card-header py-3"> <div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Liste des courses</h6> <h6 class="m-0 font-weight-bold text-primary">Liste des courses</h6>
</div> </div>
@ -14,8 +14,12 @@
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">
{{ course.nom }} ({{ course.date }}) {{ course.nom }} ({{ course.date }})
<div> <div>
<a href="{% url 'course_detail' course.id %}" class="btn btn-primary btn-sm mr-2">Voir</a> <a href="{% url 'course_detail' course.id %}" class="btn btn-primary btn-sm mr-2">
<a href="{% url 'scan' %}?course_id={{ course.id }}" class="btn btn-info btn-sm">Scan</a> <i class="fas fa-eye" title="Détails de la course"></i>
</a>
<a href="{% url 'scan' %}?course_id={{ course.id }}" class="btn btn-info btn-sm">
<i class="fas fa-qrcode" title="Accès au mode scan"></i>
</a>
</div> </div>
</li> </li>
{% empty %} {% empty %}
@ -26,15 +30,28 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row align-items-stretch"> <div class="row">
<div class="col-lg-6 mb-4 d-flex"> <div class="col-12">
<div class="card shadow mb-4 h-100 w-100"> <div class="card shadow mb-4">
<div class="card-header py-3"> <div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Actions</h6> <h6 class="m-0 font-weight-bold text-primary">Actions</h6>
</div> </div>
<div class="card-body"> <div class="card-body d-flex justify-content-center gap-3">
<button class="btn btn-success mb-2" id="btnNewCourse">Créer une nouvelle course</button> <button class="btn btn-success mb-2" id="btnNewCourse">
<a href="{% url 'dossards' %}" class="btn btn-warning mb-2 ml-2">Générer les dossards</a> <i class="fas fa-plus"></i>
&nbsp;Créer une nouvelle course
</button>
<a href="{% url 'dossards' %}" class="btn btn-warning mb-2 ml-2">
<i class="fas fa-plus" title="Générer les dossards"></i>
&nbsp;Générer les dossards
</a>
<form action="{% url 'logout' %}" method="post" style="display:inline;">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'login' %}">
<button type="submit" class="btn btn-danger mb-2 ml-2">
<i class="fas fa-sign-out-alt"></i> Déconnexion
</button>
</form>
</div> </div>
</div> </div>
<div id="newCourseModal" class="modal" tabindex="-1" role="dialog" style="display:none;"> <div id="newCourseModal" class="modal" tabindex="-1" role="dialog" style="display:none;">

2
templates/registration/login.html

@ -5,7 +5,7 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="card shadow"> <div class="card shadow">
<div class="card-header bg-primary text-white"> <div class="card-header bg-primary text-white">
<h4 class="mb-0">Connexion</h4> <h4 class="mb-0">Connexion à CrossApp</h4>
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="post"> <form method="post">

87
templates/scan.html

@ -4,7 +4,7 @@
<div class="container-fluid mt-4"> <div class="container-fluid mt-4">
<h1 class="h3 mb-4 text-gray-800">Mode Scan : {% if course %}{{ course.nom }} ({{ course.date }}){% endif %}</h1> <h1 class="h3 mb-4 text-gray-800">Mode Scan : {% if course %}{{ course.nom }} ({{ course.date }}){% endif %}</h1>
<div class="row"> <div class="row">
<div class="col-lg-8 mx-auto"> <div class="col-12">
<div class="card shadow mb-4"> <div class="card shadow mb-4">
<div class="card-header py-3"> <div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Scanner un coureur</h6> <h6 class="m-0 font-weight-bold text-primary">Scanner un coureur</h6>
@ -21,9 +21,33 @@
<div class="card-header py-3"> <div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Actions</h6> <h6 class="m-0 font-weight-bold text-primary">Actions</h6>
</div> </div>
<div class="card-body d-flex justify-content-between"> <div class="card-body d-flex justify-content-center gap-3">
<a href="/" class="btn btn-secondary">Menu principal</a> <a href="/" class="btn btn-secondary" title="Accueil">
<button id="toggleBeep" type="button" class="btn btn-info">Désactiver bip scan</button> <i class="fas fa-home mx-auto"></i>
</a>&nbsp;
<button id="toggleBeep" type="button" class="btn btn-info " title="Bip scan" >
<i id="beepIcon" class="fas fa-volume-up mx-auto"></i>
</button>&nbsp;
<button id="showCameras" type="button" class="btn btn-warning " title="Changer de caméra" data-toggle="modal" data-target="#cameraModal">
<i class="fas fa-camera mx-auto"></i>
</button>
</div>
<!-- Modal pour la liste des caméras -->
<div class="modal fade" id="cameraModal" tabindex="-1" role="dialog" aria-labelledby="cameraModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="cameraModalLabel">Sélectionner une caméra</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<ul id="cameraList" class="list-group"></ul>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -44,7 +68,7 @@ function beep() {
} }
let lastScanned = ''; let lastScanned = '';
let html5QrCode; let html5Qrcode;
function getCookie(name) { function getCookie(name) {
var cookieValue = null; var cookieValue = null;
@ -62,19 +86,49 @@ function getCookie(name) {
return cookieValue; return cookieValue;
} }
let camerasAvailable = [];
let currentCameraId = null;
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
html5QrCode = new Html5Qrcode("reader"); html5Qrcode = new Html5Qrcode("reader");
Html5Qrcode.getCameras().then(cameras => { Html5Qrcode.getCameras().then(cameras => {
if (cameras && cameras.length) { camerasAvailable = cameras || [];
html5QrCode.start( if (camerasAvailable.length) {
cameras[0].id, currentCameraId = camerasAvailable[0].id;
html5Qrcode.start(
currentCameraId,
{ fps: 10, qrbox: 250 }, { fps: 10, qrbox: 250 },
onScanSuccess onScanSuccess
); );
} }
}); });
// Plus aucune référence à l’ancien form ou input qrcode // Remplir la liste des caméras à chaque ouverture du modal
$('#cameraModal').on('show.bs.modal', function () {
const cameraList = document.getElementById('cameraList');
cameraList.innerHTML = '';
camerasAvailable.forEach((cam, idx) => {
const li = document.createElement('li');
li.className = 'list-group-item list-group-item-action';
li.textContent = cam.label || `Caméra ${idx+1}`;
li.style.cursor = 'pointer';
li.onclick = function() {
if (currentCameraId !== cam.id) {
html5Qrcode.stop().then(() => {
currentCameraId = cam.id;
html5Qrcode.start(
currentCameraId,
{ fps: 10, qrbox: 250 },
onScanSuccess
);
});
}
// Fermer le modal
$('#cameraModal').modal('hide');
};
cameraList.appendChild(li);
});
});
}); });
function getCourseIdFromUrl() { function getCourseIdFromUrl() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
@ -111,10 +165,19 @@ function onScanSuccess(decodedText, decodedResult) {
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const beepBtn = document.getElementById('toggleBeep'); const beepBtn = document.getElementById('toggleBeep');
const beepIcon = document.getElementById('beepIcon');
let beepEnabled = true; let beepEnabled = true;
beepBtn.onclick = function() { beepBtn.onclick = function() {
beepEnabled = !beepEnabled; beepEnabled = !beepEnabled;
beepBtn.textContent = beepEnabled ? 'Désactiver bip scan' : 'Activer bip scan'; if (beepEnabled) {
beepIcon.classList.remove('fa-volume-mute');
beepIcon.classList.add('fa-volume-up');
beepBtn.title = 'Désactiver bip scan';
} else {
beepIcon.classList.remove('fa-volume-up');
beepIcon.classList.add('fa-volume-mute');
beepBtn.title = 'Activer bip scan';
}
}; };
window.beep = function() { window.beep = function() {
if (!beepEnabled) return; if (!beepEnabled) return;
@ -130,7 +193,7 @@ document.addEventListener('DOMContentLoaded', function() {
Html5Qrcode.getCameras().then(cameras => { Html5Qrcode.getCameras().then(cameras => {
if (cameras && cameras.length) { if (cameras && cameras.length) {
html5QrCode.start( html5Qrcode.start(
cameras[0].id, cameras[0].id,
{ fps: 10, qrbox: 250 }, { fps: 10, qrbox: 250 },
onScanSuccess onScanSuccess

Loading…
Cancel
Save