feat: Implement hybrid barcode scanner with real-time camera support and image upload fallback
This commit is contained in:
+161
-12
@@ -67,8 +67,22 @@
|
||||
|
||||
<!-- Scanner Panel -->
|
||||
<div id="qrContainer" class="qr-container" style="display: none;">
|
||||
<div id="qr-reader" style="width: auto; height: 300px;"></div>
|
||||
<span id="qr-result" style="display: none;"></span>
|
||||
<div class="scanner-header">
|
||||
<h3>Barcode/QR-Code Scanner</h3>
|
||||
<div class="scanner-controls">
|
||||
<button type="button" id="cameraToggle" class="scanner-button" title="Kamera wechseln">📷 Kamera</button>
|
||||
<label for="uploadScanImage" class="scanner-button" title="Bild hochladen zum Dekodieren">📤 Upload</label>
|
||||
<input type="file" id="uploadScanImage" accept="image/*" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="scanner-video-wrapper">
|
||||
<video id="qr-reader" playsinline style="width: 100%; max-width: 500px; border-radius: 8px;"></video>
|
||||
<canvas id="scanner-canvas" style="display: none;"></canvas>
|
||||
</div>
|
||||
<div id="scanMessage" class="scanner-message" style="margin-top: 10px; text-align: center; font-weight: bold; color: #666;"></div>
|
||||
<p style="font-size: 0.9em; color: #999; margin-top: 10px; text-align: center;">
|
||||
💡 Hinweis: Scan-Ergebnis wird automatisch in das Feld oben übernommen. USB-Scanner werden als Tastatureingabe erkannt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Table Header (only in table mode) -->
|
||||
@@ -206,7 +220,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/html5-qrcode/minified/html5-qrcode.min.js"></script>
|
||||
<script src="https://unpkg.com/@zxing/library@latest/umd/index.min.js"></script>
|
||||
<script src="/static/js/scanner.js"></script>
|
||||
<script>
|
||||
// View mode persistence
|
||||
const LIBRARY_VIEW_MODE_KEY = 'inventarLibraryViewMode';
|
||||
@@ -631,8 +646,9 @@ document.getElementById('favoriteToggle').addEventListener('click', function() {
|
||||
// TODO: Implement favorites filtering
|
||||
});
|
||||
|
||||
// Scanner toggle
|
||||
// Scanner instance (hybrid: real-time + upload + keyboard)
|
||||
let scanner = null;
|
||||
|
||||
document.getElementById('scannerBtn').addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
const container = document.getElementById('qrContainer');
|
||||
@@ -640,7 +656,10 @@ document.getElementById('scannerBtn').addEventListener('click', function(e) {
|
||||
|
||||
if (isOpen) {
|
||||
container.style.display = 'none';
|
||||
if (scanner) scanner.clear();
|
||||
if (scanner) {
|
||||
scanner.stop();
|
||||
scanner = null;
|
||||
}
|
||||
this.classList.remove('open');
|
||||
this.setAttribute('aria-expanded', 'false');
|
||||
} else {
|
||||
@@ -649,17 +668,66 @@ document.getElementById('scannerBtn').addEventListener('click', function(e) {
|
||||
this.setAttribute('aria-expanded', 'true');
|
||||
|
||||
if (!scanner) {
|
||||
scanner = new Html5Qrcode('qr-reader');
|
||||
scanner.start(
|
||||
{ facingMode: "environment" },
|
||||
{ fps: 10, qrbox: 250 },
|
||||
onScanSuccess,
|
||||
onScanError
|
||||
);
|
||||
initScanner();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function initScanner() {
|
||||
scanner = new HybridScanner({
|
||||
videoId: 'qr-reader',
|
||||
canvasId: 'scanner-canvas',
|
||||
formats: ['QR_CODE', 'CODE_128', 'EAN_13', 'ISBN', 'CODE_39', 'UPC_A', 'EAN_8'],
|
||||
fps: 10,
|
||||
facingMode: 'environment',
|
||||
onSuccess: function(decodedText) {
|
||||
const studentId = decodedText.trim();
|
||||
document.getElementById('studentIdInput').value = studentId;
|
||||
document.getElementById('scanMessage').textContent = '✓ Erkannt: ' + studentId;
|
||||
document.getElementById('scanMessage').style.color = '#4CAF50';
|
||||
},
|
||||
onError: function(error) {
|
||||
document.getElementById('scanMessage').textContent = '✗ ' + error;
|
||||
document.getElementById('scanMessage').style.color = '#f44336';
|
||||
}
|
||||
});
|
||||
|
||||
scanner.start();
|
||||
}
|
||||
|
||||
// Camera toggle
|
||||
document.getElementById('cameraToggle')?.addEventListener('click', function() {
|
||||
if (scanner) {
|
||||
scanner.toggleCamera();
|
||||
this.textContent = scanner.options.facingMode === 'user' ? '📷 Rückkamera' : '🤳 Frontkamera';
|
||||
}
|
||||
});
|
||||
|
||||
// Image upload fallback
|
||||
document.getElementById('uploadScanImage')?.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const messageEl = document.getElementById('scanMessage');
|
||||
messageEl.textContent = 'Wird dekodiert...';
|
||||
messageEl.style.color = '#2196F3';
|
||||
|
||||
HybridScanner.uploadImageForDecoding(
|
||||
file,
|
||||
(decodedText) => {
|
||||
document.getElementById('studentIdInput').value = decodedText;
|
||||
messageEl.textContent = '✓ Erkannt (Upload): ' + decodedText;
|
||||
messageEl.style.color = '#4CAF50';
|
||||
e.target.value = ''; // Reset file input
|
||||
},
|
||||
(error) => {
|
||||
messageEl.textContent = '✗ ' + error;
|
||||
messageEl.style.color = '#f44336';
|
||||
e.target.value = '';
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function onScanSuccess(decodedText, decodedResult) {
|
||||
const studentId = decodedText.trim();
|
||||
document.getElementById('studentIdInput').value = studentId;
|
||||
@@ -737,5 +805,86 @@ function onScanError(error) {
|
||||
display: block; /* keep rows stacked but allow horizontal scroll */
|
||||
}
|
||||
}
|
||||
|
||||
/* Hybrid Scanner Styles */
|
||||
.qr-container {
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.scanner-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.scanner-header h3 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.scanner-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.scanner-button {
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.scanner-button:hover {
|
||||
background: #1976D2;
|
||||
}
|
||||
|
||||
.scanner-button:active {
|
||||
background: #1565C0;
|
||||
}
|
||||
|
||||
.scanner-video-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
#qr-reader {
|
||||
border: 2px solid #2196F3;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.scanner-message {
|
||||
min-height: 20px;
|
||||
font-size: 0.95em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.scanner-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.scanner-controls {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.scanner-button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -459,7 +459,10 @@
|
||||
Hinweis: Im Schnellmodus zuerst den Schülerausweis scannen, danach den Buch-/Mediencode.
|
||||
</div>
|
||||
<div id="scanReaderWrap" class="library-scan-reader-wrap">
|
||||
<div id="libraryQrReader" class="library-scan-reader"></div>
|
||||
<div id="libraryQrReader" class="library-scan-reader">
|
||||
<video id="libraryQrReaderVideo" playsinline style="width: 100%; border-radius: 8px; display: none;"></video>
|
||||
<canvas id="libraryQrReaderCanvas" style="display: none;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="filterPanel" class="library-filter-panel">
|
||||
@@ -547,7 +550,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/html5-qrcode/minified/html5-qrcode.min.js"></script>
|
||||
<script src="https://unpkg.com/@zxing/library@latest/umd/index.min.js"></script>
|
||||
<script src="/static/js/scanner.js"></script>
|
||||
<script>
|
||||
// State
|
||||
let libraryItems = [];
|
||||
@@ -1002,54 +1006,8 @@
|
||||
}
|
||||
|
||||
async function ensureScannerLibraryLoaded() {
|
||||
if (typeof Html5QrcodeScanner !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const sources = [
|
||||
'https://cdn.jsdelivr.net/npm/html5-qrcode/minified/html5-qrcode.min.js'
|
||||
];
|
||||
|
||||
for (const src of sources) {
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const existing = document.querySelector(`script[data-scanner-src="${src}"]`);
|
||||
if (existing) {
|
||||
const onLoad = () => resolve();
|
||||
const onError = () => reject(new Error('Script load failed'));
|
||||
existing.addEventListener('load', onLoad, { once: true });
|
||||
existing.addEventListener('error', onError, { once: true });
|
||||
setTimeout(() => {
|
||||
existing.removeEventListener('load', onLoad);
|
||||
existing.removeEventListener('error', onError);
|
||||
if (typeof Html5QrcodeScanner !== 'undefined') {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error('Script not available'));
|
||||
}
|
||||
}, 1200);
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.dataset.scannerSrc = src;
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error('Script load failed'));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
if (typeof Html5QrcodeScanner !== 'undefined') {
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Scanner library load failed from', src, err);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// HybridScanner is loaded globally via script tag
|
||||
return typeof HybridScanner !== 'undefined';
|
||||
}
|
||||
|
||||
async function startScanner() {
|
||||
@@ -1066,37 +1024,17 @@
|
||||
readerWrap.style.display = 'block';
|
||||
|
||||
try {
|
||||
const formats = [];
|
||||
if (typeof Html5QrcodeSupportedFormats !== 'undefined') {
|
||||
formats.push(
|
||||
Html5QrcodeSupportedFormats.QR_CODE,
|
||||
Html5QrcodeSupportedFormats.EAN_13,
|
||||
Html5QrcodeSupportedFormats.EAN_8,
|
||||
Html5QrcodeSupportedFormats.CODE_128,
|
||||
Html5QrcodeSupportedFormats.CODE_39,
|
||||
Html5QrcodeSupportedFormats.UPC_A,
|
||||
Html5QrcodeSupportedFormats.UPC_E,
|
||||
Html5QrcodeSupportedFormats.ITF,
|
||||
Html5QrcodeSupportedFormats.CODABAR
|
||||
);
|
||||
}
|
||||
|
||||
const scannerConfig = {
|
||||
scannerInstance = scannerInstance || new HybridScanner({
|
||||
videoId: 'libraryQrReaderVideo',
|
||||
canvasId: 'libraryQrReaderCanvas',
|
||||
formats: ['QR_CODE', 'EAN_13', 'EAN_8', 'CODE_128', 'CODE_39', 'UPC_A', 'UPC_E', 'ITF', 'CODABAR'],
|
||||
fps: 10,
|
||||
rememberLastUsedCamera: true,
|
||||
aspectRatio: 1.333334
|
||||
};
|
||||
if (formats.length > 0) {
|
||||
scannerConfig.formatsToSupport = formats;
|
||||
}
|
||||
facingMode: 'environment',
|
||||
onSuccess: handleScanSuccess,
|
||||
onError: handleScanError
|
||||
});
|
||||
|
||||
scannerInstance = scannerInstance || new Html5QrcodeScanner(
|
||||
'libraryQrReader',
|
||||
scannerConfig,
|
||||
false
|
||||
);
|
||||
|
||||
scannerInstance.render(handleScanSuccess, handleScanError);
|
||||
scannerInstance.start();
|
||||
scannerRunning = true;
|
||||
toggleBtn.textContent = 'Scanner stoppen';
|
||||
setScanStatus('Scanner aktiv. Jetzt Code scannen.', 'warn');
|
||||
@@ -1113,7 +1051,7 @@
|
||||
const readerWrap = document.getElementById('scanReaderWrap');
|
||||
const toggleBtn = document.getElementById('toggleScannerBtn');
|
||||
try {
|
||||
await scannerInstance.clear();
|
||||
scannerInstance.stop();
|
||||
} catch (err) {
|
||||
console.error('Scanner stop failed:', err);
|
||||
}
|
||||
|
||||
+32
-20
@@ -413,7 +413,10 @@
|
||||
</div>
|
||||
<div class="qr-container">
|
||||
<button id="scanButton" class="scan-button" aria-controls="qr-reader" aria-expanded="false">Barcode scannen</button>
|
||||
<div id="qr-reader"></div>
|
||||
<div id="qr-reader" style="display: none;">
|
||||
<video id="qr-reader-video" playsinline style="width: 100%; max-width: 500px; border-radius: 8px; display: none;"></video>
|
||||
<canvas id="qr-reader-canvas" style="display: none;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div id="table-view-header" class="table-view-header" aria-hidden="true">
|
||||
<span>Name</span>
|
||||
@@ -526,7 +529,8 @@
|
||||
window.isDebug = false; // Set to true only for development environment
|
||||
</script>
|
||||
|
||||
<script src="https://unpkg.com/html5-qrcode@2.0.9/dist/html5-qrcode.min.js"></script>
|
||||
<script src="https://unpkg.com/@zxing/library@latest/umd/index.min.js"></script>
|
||||
<script src="/static/js/scanner.js"></script>
|
||||
<script>
|
||||
// Global state
|
||||
const highlightItemId = (window.serverVars && window.serverVars.highlightItemId && window.serverVars.highlightItemId !== 'null')
|
||||
@@ -539,7 +543,7 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const scanButton = document.getElementById('scanButton');
|
||||
const qrReader = document.getElementById('qr-reader');
|
||||
let html5QrcodeScanner = null;
|
||||
let hybridScanner = null;
|
||||
|
||||
if (!scanButton || !qrReader) return;
|
||||
|
||||
@@ -553,28 +557,36 @@
|
||||
if (qrReader.style.display !== 'block') {
|
||||
qrReader.style.display = 'block';
|
||||
|
||||
html5QrcodeScanner = new Html5QrcodeScanner(
|
||||
'qr-reader', { fps: 10, qrbox: 250 }
|
||||
);
|
||||
|
||||
html5QrcodeScanner.render((decodedText) => {
|
||||
html5QrcodeScanner.clear();
|
||||
qrReader.style.display = 'none';
|
||||
|
||||
// Put scanned code into the search box and trigger search
|
||||
const searchInput = document.getElementById('code-search');
|
||||
if (searchInput) {
|
||||
searchInput.value = decodedText;
|
||||
searchByCode();
|
||||
}
|
||||
hybridScanner = new HybridScanner({
|
||||
videoId: 'qr-reader-video',
|
||||
canvasId: 'qr-reader-canvas',
|
||||
formats: ['QR_CODE', 'CODE_128', 'EAN_13', 'EAN_8', 'CODE_39'],
|
||||
fps: 10,
|
||||
facingMode: 'environment',
|
||||
onSuccess: function(decodedText) {
|
||||
hybridScanner.stop();
|
||||
qrReader.style.display = 'none';
|
||||
|
||||
// Put scanned code into the search box and trigger search
|
||||
const searchInput = document.getElementById('code-search');
|
||||
if (searchInput) {
|
||||
searchInput.value = decodedText;
|
||||
searchByCode();
|
||||
}
|
||||
|
||||
setScannerUi(false);
|
||||
setScannerUi(false);
|
||||
},
|
||||
onError: function(error) {
|
||||
console.error('Scanner error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
hybridScanner.start();
|
||||
setScannerUi(true);
|
||||
} else {
|
||||
if (html5QrcodeScanner) {
|
||||
html5QrcodeScanner.clear();
|
||||
if (hybridScanner) {
|
||||
hybridScanner.stop();
|
||||
hybridScanner = null;
|
||||
}
|
||||
qrReader.style.display = 'none';
|
||||
setScannerUi(false);
|
||||
|
||||
@@ -876,7 +876,10 @@
|
||||
<button type="button" id="scan-code4-btn" class="fetch-isbn-button">Barcode scannen</button>
|
||||
</div>
|
||||
<small style="display:block; color:#666;">Einzelcode manuell setzen oder per Scanner erfassen.</small>
|
||||
<div id="code4-scanner" style="width:100%; max-width:520px; display:none; margin-top:10px;"></div>
|
||||
<div id="code4-scanner" style="width:100%; max-width:520px; display:none; margin-top:10px;">
|
||||
<video id="code4-scanner-video" playsinline style="width: 100%; border-radius: 8px; display: none;"></video>
|
||||
<canvas id="code4-scanner-canvas" style="display: none;"></canvas>
|
||||
</div>
|
||||
<small id="code4-scan-status" style="display:block; color:#666; margin-top:6px;"></small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -907,7 +910,10 @@
|
||||
<button type="button" id="scan-isbn-btn" class="fetch-isbn-button">Barcode scannen</button>
|
||||
<button type="button" class="fetch-isbn-button" onclick="fetchBookInfo('upload')">Bild abrufen</button>
|
||||
</div>
|
||||
<div id="isbn-scanner" style="width:100%; max-width:520px; display:none; margin-top:10px;"></div>
|
||||
<div id="isbn-scanner" style="width:100%; max-width:520px; display:none; margin-top:10px;">
|
||||
<video id="isbn-scanner-video" playsinline style="width: 100%; border-radius: 8px; display: none;"></video>
|
||||
<canvas id="isbn-scanner-canvas" style="display: none;"></canvas>
|
||||
</div>
|
||||
<small id="isbn-scan-status" style="display:block; color:#666; margin-top:6px;">Scannen oder manuell eingeben. Gültige ISBNs helfen beim Abruf von Buchdaten, andere Codes werden trotzdem akzeptiert.</small>
|
||||
<div id="book-info-container" class="book-info-container"></div>
|
||||
</div>
|
||||
@@ -971,7 +977,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="https://unpkg.com/html5-qrcode@2.0.9/dist/html5-qrcode.min.js"></script>
|
||||
<script src="https://unpkg.com/@zxing/library@latest/umd/index.min.js"></script>
|
||||
<script src="/static/js/scanner.js"></script>
|
||||
<script>
|
||||
const libraryModuleEnabled = {{ 'true' if library_module_enabled else 'false' }};
|
||||
|
||||
@@ -1050,16 +1057,19 @@
|
||||
|
||||
// Stop ISBN scanner if currently running to avoid camera conflicts.
|
||||
if (isbnScannerRunning && isbnScannerInstance) {
|
||||
isbnScannerInstance.clear().catch(() => {});
|
||||
isbnScannerInstance.stop();
|
||||
isbnScannerInstance = null;
|
||||
const isbnScannerBox = document.getElementById('isbn-scanner');
|
||||
const isbnScanButton = document.getElementById('scan-isbn-btn');
|
||||
if (isbnScannerBox) isbnScannerBox.style.display = 'none';
|
||||
if (isbnScanButton) isbnScanButton.textContent = 'ISBN scannen';
|
||||
isbnScannerRunning = false;
|
||||
setIsbnScanStatus('Scanner gestoppt.');
|
||||
}
|
||||
|
||||
if (code4ScannerRunning && code4ScannerInstance) {
|
||||
code4ScannerInstance.clear().catch(() => {});
|
||||
code4ScannerInstance.stop();
|
||||
code4ScannerInstance = null;
|
||||
scannerBox.style.display = 'none';
|
||||
scanButton.textContent = 'Barcode scannen';
|
||||
code4ScannerRunning = false;
|
||||
@@ -1069,29 +1079,36 @@
|
||||
|
||||
scannerBox.style.display = 'block';
|
||||
scanButton.textContent = 'Scanner stoppen';
|
||||
code4ScannerInstance = new Html5QrcodeScanner('code4-scanner', {
|
||||
fps: 10,
|
||||
qrbox: 250,
|
||||
rememberLastUsedCamera: true
|
||||
});
|
||||
code4ScannerRunning = true;
|
||||
setCode4ScanStatus('Scanner läuft. Der Scan wird im Basis-Codefeld übernommen.');
|
||||
|
||||
code4ScannerInstance.render((decodedText) => {
|
||||
const scannedCode = String(decodedText || '').trim();
|
||||
if (!scannedCode) return;
|
||||
code4ScannerInstance = new HybridScanner({
|
||||
videoId: 'code4-scanner-video',
|
||||
canvasId: 'code4-scanner-canvas',
|
||||
formats: ['CODE_128', 'CODE_39', 'QR_CODE'],
|
||||
fps: 10,
|
||||
facingMode: 'environment',
|
||||
onSuccess: function(decodedText) {
|
||||
const scannedCode = String(decodedText || '').trim();
|
||||
if (!scannedCode) return;
|
||||
|
||||
const now = Date.now();
|
||||
if (scannedCode === code4LastScanned && (now - code4LastScannedAt) < 1500) {
|
||||
return;
|
||||
const now = Date.now();
|
||||
if (scannedCode === code4LastScanned && (now - code4LastScannedAt) < 1500) {
|
||||
return;
|
||||
}
|
||||
code4LastScanned = scannedCode;
|
||||
code4LastScannedAt = now;
|
||||
|
||||
codeField.value = scannedCode;
|
||||
validateCodeField(codeField);
|
||||
setCode4ScanStatus(`Code_4 gesetzt: ${scannedCode}`);
|
||||
},
|
||||
onError: function(error) {
|
||||
setCode4ScanStatus(`Fehler: ${error}`, true);
|
||||
}
|
||||
code4LastScanned = scannedCode;
|
||||
code4LastScannedAt = now;
|
||||
});
|
||||
|
||||
codeField.value = scannedCode;
|
||||
validateCodeField(codeField);
|
||||
setCode4ScanStatus(`Code_4 gesetzt: ${scannedCode}`);
|
||||
}, () => {});
|
||||
code4ScannerInstance.start();
|
||||
}
|
||||
|
||||
function startIsbnScanner() {
|
||||
@@ -1106,7 +1123,8 @@
|
||||
|
||||
// Stop Code_4 scanner if currently running to avoid camera conflicts.
|
||||
if (code4ScannerRunning && code4ScannerInstance) {
|
||||
code4ScannerInstance.clear().catch(() => {});
|
||||
code4ScannerInstance.stop();
|
||||
code4ScannerInstance = null;
|
||||
const codeScannerBox = document.getElementById('code4-scanner');
|
||||
const codeScanButton = document.getElementById('scan-code4-btn');
|
||||
if (codeScannerBox) codeScannerBox.style.display = 'none';
|
||||
@@ -1116,7 +1134,8 @@
|
||||
}
|
||||
|
||||
if (isbnScannerRunning && isbnScannerInstance) {
|
||||
isbnScannerInstance.clear().catch(() => {});
|
||||
isbnScannerInstance.stop();
|
||||
isbnScannerInstance = null;
|
||||
scannerBox.style.display = 'none';
|
||||
scanButton.textContent = 'ISBN scannen';
|
||||
isbnScannerRunning = false;
|
||||
@@ -1126,15 +1145,16 @@
|
||||
|
||||
scannerBox.style.display = 'block';
|
||||
scanButton.textContent = 'Scanner stoppen';
|
||||
isbnScannerInstance = new Html5QrcodeScanner('isbn-scanner', {
|
||||
fps: 10,
|
||||
qrbox: 250,
|
||||
rememberLastUsedCamera: true
|
||||
});
|
||||
isbnScannerRunning = true;
|
||||
setIsbnScanStatus('Scanner läuft. Bitte ISBN-Code erfassen.');
|
||||
|
||||
isbnScannerInstance.render((decodedText) => {
|
||||
isbnScannerInstance = new HybridScanner({
|
||||
videoId: 'isbn-scanner-video',
|
||||
canvasId: 'isbn-scanner-canvas',
|
||||
formats: ['ISBN', 'EAN_13', 'EAN_8', 'QR_CODE'],
|
||||
fps: 10,
|
||||
facingMode: 'environment',
|
||||
onSuccess: function(decodedText) {
|
||||
const scannedCode = String(decodedText || '').trim();
|
||||
if (!scannedCode) return;
|
||||
|
||||
@@ -1147,7 +1167,11 @@
|
||||
setIsbnScanStatus('Kein gültiges ISBN-Format erkannt, der gescannte Wert bleibt aber im Feld.', true);
|
||||
}
|
||||
|
||||
isbnScannerInstance.clear().catch(() => {});
|
||||
// Stop scanner after successful scan
|
||||
if (isbnScannerInstance) {
|
||||
isbnScannerInstance.stop();
|
||||
isbnScannerInstance = null;
|
||||
}
|
||||
scannerBox.style.display = 'none';
|
||||
scanButton.textContent = 'ISBN scannen';
|
||||
isbnScannerRunning = false;
|
||||
@@ -1156,7 +1180,13 @@
|
||||
// Automatically load book metadata after a valid ISBN scan.
|
||||
fetchBookInfo('upload');
|
||||
}
|
||||
}, () => {});
|
||||
},
|
||||
onError: function(error) {
|
||||
setIsbnScanStatus(`Fehler: ${error}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
isbnScannerInstance.start();
|
||||
}
|
||||
|
||||
// Load predefined filter values for dropdowns
|
||||
|
||||
Reference in New Issue
Block a user