diff --git a/app.js b/app.js index 9975a8c..de5b1ae 100644 --- a/app.js +++ b/app.js @@ -58,6 +58,11 @@ const googleRestoreBtn = document.getElementById("googleRestoreBtn"); const quickSearchInput = document.getElementById("quickSearchInput"); const quickSearchResults = document.getElementById("quickSearchResults"); const loanedFilterBtn = document.getElementById("loanedFilterBtn"); +const scannerStatus = document.getElementById("scannerStatus"); +const scannerStartBtn = document.getElementById("scannerStartBtn"); +const scannerStopBtn = document.getElementById("scannerStopBtn"); +const scannerVideo = document.getElementById("scannerVideo"); +const scannerLastCode = document.getElementById("scannerLastCode"); const gamesList = document.getElementById("gamesList"); const gameCardTemplate = document.getElementById("gameCardTemplate"); let editingGameId = null; @@ -65,6 +70,12 @@ let pendingRestoreMode = "merge"; let quickSearchTerm = ""; let googleStatus = { configured: false, connected: false, email: "" }; let showLoanedOnly = false; +let scannerDetector = null; +let scannerStream = null; +let scannerRunning = false; +let scannerLoopId = null; +let scannerLastCodeValue = ""; +let scannerLastCodeAt = 0; toolsToggleBtn.addEventListener("click", () => { gamesDrawer.classList.remove("open"); @@ -106,6 +117,9 @@ document.addEventListener("keydown", (event) => { if (event.key === "Escape") { toolsDrawer.classList.remove("open"); gamesDrawer.classList.remove("open"); + if (scannerRunning) { + stopScanner("Camera arretee."); + } } }); @@ -161,6 +175,18 @@ loanedFilterBtn.addEventListener("click", () => { renderGames(); }); +if (scannerStartBtn) { + scannerStartBtn.addEventListener("click", async () => { + await startScanner(); + }); +} + +if (scannerStopBtn) { + scannerStopBtn.addEventListener("click", () => { + stopScanner("Camera arretee."); + }); +} + platformForm.addEventListener("submit", async (event) => { event.preventDefault(); @@ -894,6 +920,131 @@ function resetEditMode() { cancelEditBtn.classList.add("hidden"); } +function updateScannerStatus(message) { + if (scannerStatus) { + scannerStatus.textContent = message; + } +} + +function scannerSupported() { + return typeof window !== "undefined" && "BarcodeDetector" in window && navigator.mediaDevices; +} + +async function startScanner() { + if (!scannerVideo || !scannerStartBtn || !scannerStopBtn) { + return; + } + + if (!scannerSupported()) { + updateScannerStatus("Scan non supporte sur ce navigateur. Utilise Chrome mobile recente."); + return; + } + + if (scannerRunning) { + return; + } + + try { + scannerDetector = new window.BarcodeDetector({ + formats: ["ean_13", "ean_8", "upc_a", "upc_e", "code_128", "code_39", "qr_code"], + }); + scannerStream = await navigator.mediaDevices.getUserMedia({ + video: { + facingMode: { ideal: "environment" }, + }, + audio: false, + }); + + scannerVideo.srcObject = scannerStream; + await scannerVideo.play(); + scannerRunning = true; + scannerVideo.classList.remove("hidden"); + scannerStartBtn.classList.add("hidden"); + scannerStopBtn.classList.remove("hidden"); + updateScannerStatus("Scan en cours... vise le code-barres de la boite."); + scanLoop(); + } catch (error) { + console.error(error); + updateScannerStatus("Impossible d'acceder a la camera. Verifie les permissions."); + stopScanner(); + } +} + +function stopScanner(message) { + if (scannerLoopId) { + cancelAnimationFrame(scannerLoopId); + scannerLoopId = null; + } + + if (scannerVideo) { + scannerVideo.pause(); + scannerVideo.srcObject = null; + scannerVideo.classList.add("hidden"); + } + + if (scannerStream) { + for (const track of scannerStream.getTracks()) { + track.stop(); + } + scannerStream = null; + } + + scannerRunning = false; + if (scannerStartBtn) { + scannerStartBtn.classList.remove("hidden"); + } + if (scannerStopBtn) { + scannerStopBtn.classList.add("hidden"); + } + updateScannerStatus(message || "Camera inactive."); +} + +async function scanLoop() { + if (!scannerRunning || !scannerDetector || !scannerVideo) { + return; + } + + try { + const barcodes = await scannerDetector.detect(scannerVideo); + if (barcodes.length > 0) { + const rawValue = normalizeText(barcodes[0].rawValue); + if (rawValue) { + const now = Date.now(); + if (rawValue !== scannerLastCodeValue || now - scannerLastCodeAt > 1800) { + scannerLastCodeValue = rawValue; + scannerLastCodeAt = now; + applyScannedCode(rawValue); + stopScanner(`Code detecte: ${rawValue}`); + return; + } + } + } + } catch (error) { + console.error(error); + } + + scannerLoopId = requestAnimationFrame(() => { + scanLoop(); + }); +} + +function applyScannedCode(codeValue) { + if (scannerLastCode) { + scannerLastCode.textContent = `Dernier code detecte: ${codeValue}`; + scannerLastCode.classList.remove("hidden"); + } + + if (quickSearchInput) { + quickSearchInput.value = codeValue; + quickSearchTerm = codeValue; + renderSearchResults(); + } + + if (titleInput && !normalizeText(titleInput.value)) { + titleInput.value = codeValue; + } +} + function normalizeText(value) { if (value == null) { return ""; @@ -1059,11 +1210,20 @@ async function bootstrap() { await hydrateFromApi(); normalizeState(); render(); + if (scannerSupported()) { + updateScannerStatus("Camera inactive. Appuie sur Demarrer scan."); + } else { + updateScannerStatus("Scan non supporte sur ce navigateur."); + } handleGoogleCallbackResult(); } bootstrap(); +window.addEventListener("beforeunload", () => { + stopScanner(); +}); + function handleGoogleCallbackResult() { const url = new URL(window.location.href); const googleParam = url.searchParams.get("google"); diff --git a/index.html b/index.html index 22dd050..072d54c 100644 --- a/index.html +++ b/index.html @@ -123,6 +123,18 @@
+Camera inactive.
+