diff --git a/frontend/js/main.js b/frontend/js/main.js
index 02d42c2..32d2b54 100644
--- a/frontend/js/main.js
+++ b/frontend/js/main.js
@@ -1864,7 +1864,12 @@ function showChznScanModal(onSaved) {
Скан «Честный знак»
+
+ 🔍
+
Запуск камеры...
@@ -1920,9 +1925,13 @@ function showChznScanModal(onSaved) {
function grabROI() {
let vw = video.videoWidth, vh = video.videoHeight;
if (!vw || !vh) return null;
- let side = Math.min(vw, vh) * 0.72;
+ // The square container shows the video center-cropped (object-fit:cover), so
+ // 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 sx = (vw - side) / 2, sy = (vh - side) / 2;
- let out = 540;
+ let out = Math.min(side, 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);
@@ -1940,6 +1949,41 @@ function showChznScanModal(onSaved) {
if (scanning) setTimeout(tick, 100);
}
+ // Wire zoom slider / torch / continuous focus from the track capabilities.
+ // Zoom is the real answer to "the code is too small at focus distance" — the
+ // user enlarges the code optically instead of moving the phone dangerously close.
+ let torchOn = false;
+ function setupCameraControls() {
+ let track = stream && stream.getVideoTracks && stream.getVideoTracks()[0];
+ if (!track || !track.getCapabilities) return;
+ let caps = {};
+ try { caps = track.getCapabilities() || {}; } catch {}
+ try {
+ if (caps.focusMode && caps.focusMode.includes("continuous")) {
+ track.applyConstraints({ advanced: [{ focusMode: "continuous" }] }).catch(() => {});
+ }
+ } catch {}
+ 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.oninput = () => { track.applyConstraints({ advanced: [{ zoom: parseFloat(zi.value) }] }).catch(() => {}); };
+ zw.style.display = "flex";
+ }
+ if (caps.torch) {
+ let tb = ov.querySelector("#cz-torch");
+ tb.style.display = "block";
+ tb.onclick = () => {
+ torchOn = !torchOn;
+ track.applyConstraints({ advanced: [{ torch: torchOn }] }).catch(() => {});
+ tb.style.background = torchOn ? "rgba(255,214,10,.35)" : "";
+ };
+ }
+ }
+
async function startCamera() {
try {
statusEl.textContent = "Загрузка декодера...";
@@ -1949,11 +1993,14 @@ function showChznScanModal(onSaved) {
return;
}
try {
- stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: "environment" } } });
+ stream = await navigator.mediaDevices.getUserMedia({
+ video: { facingMode: { ideal: "environment" }, width: { ideal: 1920 }, height: { ideal: 1080 } },
+ });
video.srcObject = stream;
await video.play();
+ setupCameraControls();
scanning = true;
- statusEl.textContent = "Наведите на код Честного знака...";
+ statusEl.textContent = "Наведите на код (используйте зум, если код мелкий)…";
tick();
} catch (e) {
statusEl.textContent = "Камера недоступна (" + (e.name || e.message) + "). Выберите фото или введите код.";
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index f5ca4c9..1d96388 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/nicegui/tauri-v2/refs/heads/main/tooling/cli/schema.json",
"productName": "BalDav",
- "version": "0.1.423",
+ "version": "0.1.426",
"identifier": "dev.baldav.app",
"build": {
"frontendDist": "../frontend"
@@ -9,7 +9,7 @@
"app": {
"withGlobalTauri": true,
"security": {
- "csp": "default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' https://4aiman.top https://*.tile.openstreetmap.org https://*.tile.thunderforest.com https://api.open-meteo.com https://wttr.in http://192.168.1.1:88; img-src 'self' blob: data: https://*.tile.openstreetmap.org https://*.tile.thunderforest.com"
+ "csp": "default-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'self' 'wasm-unsafe-eval' 'unsafe-eval' 'unsafe-inline'; connect-src 'self' https://4aiman.top https://*.tile.openstreetmap.org https://*.tile.thunderforest.com https://api.open-meteo.com https://wttr.in http://192.168.1.1:88; img-src 'self' blob: data: https://*.tile.openstreetmap.org https://*.tile.thunderforest.com"
},
"windows": [
{