diff --git a/frontend/js/main.js b/frontend/js/main.js index 790f3fd..620e29f 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -1880,7 +1880,7 @@ function showChznScanModal(onSaved) { `; document.body.appendChild(ov); - let stream = null, scanning = false, busy = false; + let stream = null, scanning = false, busy = false, zoomFactor = 1; let base = winStack.length; let statusEl = ov.querySelector("#cz-status"); let video = ov.querySelector("#cz-video"); @@ -1902,6 +1902,22 @@ function showChznScanModal(onSaved) { _skipPops = count; if (count > 0) history.go(-count); } + // Close this modal AND wait for its history.go popstate(s) to flush before the + // caller opens the next modal. Opening it synchronously after close() races the + // async history.go (it would unwind the NEW modal's entry) and desyncs the back + // stack — that was the "swipe-back does nothing" bug on scan -> add-product. + function closeThen() { + let count = winStack.length - base; + for (let i = 0; i < count; i++) winStack.pop().close(); + if (count <= 0) return Promise.resolve(); + _skipPops = count; + return new Promise(res => { + let remaining = count; + let h = () => { if (--remaining <= 0) { window.removeEventListener("popstate", h); res(); } }; + window.addEventListener("popstate", h); + history.go(-count); + }); + } async function onDecoded(rawVal) { if (busy) return; @@ -1910,7 +1926,7 @@ function showChznScanModal(onSaved) { try { let prefill = await ChZ.resolve(rawVal); stopCamera(); - close(); + await closeThen(); showAddProductModal(onSaved, prefill); } catch (e) { busy = false; @@ -1929,9 +1945,9 @@ function showChznScanModal(onSaved) { // sample exactly that center square (WYSIWYG). zxing-wasm locates the code // anywhere within it — no need for the user to fill a tiny box; use zoom to // enlarge the code at a focusable distance instead. - let side = Math.min(vw, vh); + let side = Math.min(vw, vh) / zoomFactor; // digital zoom crops tighter to center let sx = (vw - side) / 2, sy = (vh - side) / 2; - let out = Math.min(side, 768); // enough detail, still fast + let out = Math.min(Math.min(vw, vh), 768); // enough detail, still fast canvas.width = out; canvas.height = out; cctx.drawImage(video, sx, sy, side, side, 0, 0, out, out); return cctx.getImageData(0, 0, out, out); @@ -1963,16 +1979,25 @@ function showChznScanModal(onSaved) { track.applyConstraints({ advanced: [{ focusMode: "continuous" }] }).catch(() => {}); } } catch {} + // Zoom slider: prefer hardware zoom; otherwise fall back to digital zoom + // (CSS-scale the preview + crop the decode ROI). Always shown so the user can + // enlarge a small code instead of moving the phone inside its focus range. + let zw = ov.querySelector("#cz-zoom-wrap"); + let zi = ov.querySelector("#cz-zoom"); if (caps.zoom && caps.zoom.max > caps.zoom.min) { - let zw = ov.querySelector("#cz-zoom-wrap"); - let zi = ov.querySelector("#cz-zoom"); zi.min = caps.zoom.min; zi.max = caps.zoom.max; zi.step = caps.zoom.step || (caps.zoom.max - caps.zoom.min) / 100 || 0.1; - let cur = ((track.getSettings && track.getSettings()) || {}).zoom; - zi.value = cur || caps.zoom.min; + zi.value = ((track.getSettings && track.getSettings()) || {}).zoom || caps.zoom.min; zi.oninput = () => { track.applyConstraints({ advanced: [{ zoom: parseFloat(zi.value) }] }).catch(() => {}); }; - zw.style.display = "flex"; + } else { + zi.min = 1; zi.max = 5; zi.step = 0.1; zi.value = 1; + zi.oninput = () => { + zoomFactor = parseFloat(zi.value) || 1; + video.style.transformOrigin = "center center"; + video.style.transform = "scale(" + zoomFactor + ")"; + }; } + zw.style.display = "flex"; if (caps.torch) { let tb = ov.querySelector("#cz-torch"); tb.style.display = "block";