QR-Scanner: Kamera-Fallbacks und Foto-Upload; weitere Aenderungen

This commit is contained in:
spaethf committed 2026-09-29 18:08:01 +02:00
1 parent 7a0406eaa2
commit e2aef67099
13 files changed
+965 -58

No files matched your search

+25
View File
@@ -49,6 +49,31 @@
} catch (e) { /* ignore */ }
};
// ---------------------------------------------------------
// File download from base64 (used for PDF exports)
// ---------------------------------------------------------
window.appInterop.downloadFile = function (fileName, base64, mimeType) {
try {
const bin = atob(base64);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
const blob = new Blob([bytes], { type: mimeType || 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName || 'download';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
return true;
} catch (e) {
console.warn('[appInterop] downloadFile failed', e);
return false;
}
};
// ---------------------------------------------------------
// beforeunload: warn user when draft flag is set
// ---------------------------------------------------------
+59 -16
View File
@@ -9,33 +9,43 @@ window.qrScanner = (function () {
if (window.Html5Qrcode) return;
await new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = 'lib/html5-qrcode/html5-qrcode.min.js';
s.src = new URL('lib/html5-qrcode/html5-qrcode.min.js', document.baseURI).href;
s.onload = resolve;
s.onerror = () => reject(new Error('QR-Bibliothek konnte nicht geladen werden.'));
document.head.appendChild(s);
});
}
function createScanner(elementId) {
return new Html5Qrcode(elementId, {
formatsToSupport: [Html5QrcodeSupportedFormats.QR_CODE],
experimentalFeatures: { useBarCodeDetectorIfSupported: true },
verbose: false
});
}
function pickBackCamera(cams) {
const back = cams.find(c => /back|rear|environment|rück|hinten/i.test(c.label || ''));
return (back || cams[cams.length - 1]).id;
}
async function start(elementId, dotNetRef) {
await stop();
await ensureLib();
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error('Kamera wird von diesem Browser nicht unterstützt (HTTPS erforderlich).');
if (!window.isSecureContext || !navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error('Live-Kamera nicht verfügbar (HTTPS erforderlich). Bitte „Foto aufnehmen“ verwenden.');
}
scanner = new Html5Qrcode(elementId, {
formatsToSupport: [Html5QrcodeSupportedFormats.QR_CODE],
verbose: false
});
scanner = createScanner(elementId);
const config = {
fps: 10,
qrbox: (w, h) => {
const size = Math.max(180, Math.min(w, h) * 0.7);
return { width: size, height: size };
const size = Math.floor(Math.max(150, Math.min(w, h) * 0.7));
return { width: Math.min(size, w), height: Math.min(size, h) };
},
aspectRatio: 1.0
disableFlip: false
};
const onSuccess = (text) => {
@@ -46,13 +56,46 @@ window.qrScanner = (function () {
dotNetRef.invokeMethodAsync('OnQrScanned', text);
};
const attempts = [
() => scanner.start({ facingMode: { exact: 'environment' } }, config, onSuccess, () => { }),
() => scanner.start({ facingMode: 'environment' }, config, onSuccess, () => { }),
async () => {
const cams = await Html5Qrcode.getCameras();
if (!cams || cams.length === 0) throw new Error('Keine Kamera gefunden.');
await scanner.start(pickBackCamera(cams), config, onSuccess, () => { });
},
() => scanner.start({ facingMode: 'user' }, config, onSuccess, () => { })
];
let lastError = null;
for (const attempt of attempts) {
try {
await attempt();
return;
} catch (e) {
lastError = e;
if (e && (e.name === 'NotAllowedError' || /NotAllowed|Permission/i.test(String(e)))) break;
try { if (scanner.isScanning) await scanner.stop(); } catch { }
}
}
throw lastError instanceof Error ? lastError : new Error(String(lastError || 'Kamera konnte nicht gestartet werden.'));
}
// Decode a QR code from an image file (photo). Works on any device, also without HTTPS.
async function scanFile(elementId, inputId) {
await ensureLib();
const input = document.getElementById(inputId);
const file = input && input.files && input.files[0];
if (!file) return null;
await stop();
const fileScanner = createScanner(elementId);
try {
await scanner.start({ facingMode: 'environment' }, config, onSuccess, () => { });
return await fileScanner.scanFile(file, false);
} catch (e) {
// Fallback: any camera
const cams = await Html5Qrcode.getCameras();
if (!cams || cams.length === 0) throw new Error('Keine Kamera gefunden.');
await scanner.start(cams[0].id, config, onSuccess, () => { });
throw new Error('Kein QR-Code im Bild erkannt.');
} finally {
try { fileScanner.clear(); } catch { }
input.value = '';
}
}
@@ -84,5 +127,5 @@ window.qrScanner = (function () {
} catch (e) { /* audio is best-effort */ }
}
return { start, stop, playSound };
return { start, stop, scanFile, playSound };
})();