|
|
|
@@ -135,21 +135,6 @@
|
|
|
|
|
.library-scan-status.warn { color: #9a6700; }
|
|
|
|
|
.library-scan-status.error { color: #b42318; }
|
|
|
|
|
|
|
|
|
|
.library-scan-reader-wrap {
|
|
|
|
|
display: none;
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
max-width: 460px;
|
|
|
|
|
background: var(--ui-surface);
|
|
|
|
|
border: 1px solid #d9dde4;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.library-scan-reader {
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-height: 280px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Filters */
|
|
|
|
|
.library-filter-toggle-btn {
|
|
|
|
|
padding: 10px 16px;
|
|
|
|
@@ -431,6 +416,40 @@
|
|
|
|
|
|
|
|
|
|
.library-scan-reader-wrap { max-width: 100%; }
|
|
|
|
|
}
|
|
|
|
|
.library-scan-reader-wrap {
|
|
|
|
|
display: none;
|
|
|
|
|
margin-top: 15px;
|
|
|
|
|
max-width: 640px;
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
margin-right: auto;
|
|
|
|
|
background: #000;
|
|
|
|
|
border: 1px solid #d9dde4;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.library-scan-reader {
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-height: 280px;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.library-scan-reader video {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: auto;
|
|
|
|
|
display: block;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.library-scan-reader canvas.drawing,
|
|
|
|
|
.library-scan-reader canvas.drawingBuffer {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
<div class="library-table-container" id="libraryTableContainer" data-can-edit="{{ 1 if current_permissions.actions.get('can_edit', False) else 0 }}">
|
|
|
|
@@ -457,7 +476,6 @@
|
|
|
|
|
🔍 Filter
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="library-scan-controls">
|
|
|
|
|
<select id="scanModeSelect" aria-label="Scan-Modus">
|
|
|
|
|
<option value="card_only">Nur Ausweis erfassen</option>
|
|
|
|
@@ -475,6 +493,10 @@
|
|
|
|
|
</label>
|
|
|
|
|
<button id="manualActionBtn" class="button" type="button" style="margin-left:6px; background:#4f46e5; color:white;">Code verarbeiten</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="library-scan-reader-wrap" id="scanReaderWrap" style="display: none; margin-top: 15px;">
|
|
|
|
|
<div class="library-scan-reader" style="width: 100%; max-width: 640px; margin: 0 auto; overflow: hidden; border-radius: 8px; border: 2px solid #ccc;">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="filterPanel" class="library-filter-panel">
|
|
|
|
|
<div class="filter-row">
|
|
|
|
|
<div class="filter-item">
|
|
|
|
@@ -805,74 +827,92 @@
|
|
|
|
|
deleteLibraryItem(itemId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =========================================================================
|
|
|
|
|
// 3. CORE SCANNER ROUTING ENGINE (QUAGGA2)
|
|
|
|
|
// =========================================================================
|
|
|
|
|
function startScanner(targetCallback) {
|
|
|
|
|
const readerWrap = document.getElementById('scanReaderWrap');
|
|
|
|
|
async function startScanner(callback = null) {
|
|
|
|
|
if (scannerRunning) {
|
|
|
|
|
console.warn("Scanner is already running. Stopping it first...");
|
|
|
|
|
await stopScanner();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
activeScannerCallback = callback;
|
|
|
|
|
|
|
|
|
|
const scannerWrap = document.querySelector('.library-scan-reader-wrap');
|
|
|
|
|
const toggleBtn = document.getElementById('toggleScannerBtn');
|
|
|
|
|
|
|
|
|
|
activeScannerCallback = targetCallback;
|
|
|
|
|
// 1. CRITICAL FIX: Make the container visible BEFORE Quagga initializes
|
|
|
|
|
// Quagga cannot calculate video dimensions if display is 'none'
|
|
|
|
|
if (scannerWrap) {
|
|
|
|
|
scannerWrap.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (readerWrap) readerWrap.style.display = 'block';
|
|
|
|
|
setScanStatus('Initializing camera...', 'warn');
|
|
|
|
|
|
|
|
|
|
const scannerContainer = document.querySelector('.library-scan-reader-wrap');
|
|
|
|
|
scannerContainer.style.display = 'block';
|
|
|
|
|
setScanStatus('Starte Kamera...', 'warn');
|
|
|
|
|
|
|
|
|
|
Quagga.init({
|
|
|
|
|
inputStream: {
|
|
|
|
|
name: "Live",
|
|
|
|
|
type: "LiveStream",
|
|
|
|
|
target: document.querySelector('.library-scan-reader'),
|
|
|
|
|
constraints: {
|
|
|
|
|
width: 640,
|
|
|
|
|
height: 480,
|
|
|
|
|
facingMode: "environment"
|
|
|
|
|
// 2. Initialize Quagga with a slight delay to allow the DOM to render the block
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
Quagga.init({
|
|
|
|
|
inputStream: {
|
|
|
|
|
name: "Live",
|
|
|
|
|
type: "LiveStream",
|
|
|
|
|
// This MUST match the inner div where the video should appear
|
|
|
|
|
target: document.querySelector('.library-scan-reader'),
|
|
|
|
|
constraints: {
|
|
|
|
|
width: 640,
|
|
|
|
|
height: 480,
|
|
|
|
|
facingMode: "environment" // Prefer back camera on mobile
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
decoder: {
|
|
|
|
|
readers: [
|
|
|
|
|
"code_128_reader",
|
|
|
|
|
"ean_reader",
|
|
|
|
|
"code_39_reader",
|
|
|
|
|
"upc_reader",
|
|
|
|
|
"codabar_reader",
|
|
|
|
|
"i2of5_reader"
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}, function(err) {
|
|
|
|
|
if (err) {
|
|
|
|
|
console.error('Scanner start failed:', err);
|
|
|
|
|
if (readerWrap) readerWrap.style.display = 'none';
|
|
|
|
|
const detail = (err && (err.message || err.name)) ? ` (${err.message || err.name})` : '';
|
|
|
|
|
setScanStatus(`Scanner konnte nicht gestartet werden${detail}`, 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
locator: {
|
|
|
|
|
patchSize: "medium",
|
|
|
|
|
halfSample: true
|
|
|
|
|
},
|
|
|
|
|
decoder: {
|
|
|
|
|
// Keep only the barcode types you actually use to improve performance
|
|
|
|
|
readers: ["code_128_reader", "ean_reader", "code_39_reader"]
|
|
|
|
|
},
|
|
|
|
|
locate: true
|
|
|
|
|
}, function(err) {
|
|
|
|
|
if (err) {
|
|
|
|
|
// This will finally log the actual error (e.g., NotAllowedError) if permissions fail
|
|
|
|
|
console.error("Quagga initialization failed:", err);
|
|
|
|
|
setScanStatus('Kamera-Fehler: ' + (err.name || err), 'error');
|
|
|
|
|
|
|
|
|
|
Quagga.start();
|
|
|
|
|
scannerRunning = true;
|
|
|
|
|
if (scannerWrap) scannerWrap.style.display = 'none';
|
|
|
|
|
scannerRunning = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!targetCallback && toggleBtn) {
|
|
|
|
|
toggleBtn.textContent = 'Scanner stoppen';
|
|
|
|
|
}
|
|
|
|
|
setScanStatus('Scanner aktiv. Jetzt Code scannen.', 'warn');
|
|
|
|
|
});
|
|
|
|
|
Quagga.start();
|
|
|
|
|
scannerRunning = true;
|
|
|
|
|
setScanStatus('Scanner bereit.', 'ok');
|
|
|
|
|
|
|
|
|
|
if (toggleBtn) {
|
|
|
|
|
toggleBtn.textContent = 'Scanner stoppen';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}, 50);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stopScanner() {
|
|
|
|
|
async function stopScanner() {
|
|
|
|
|
if (!scannerRunning) return;
|
|
|
|
|
|
|
|
|
|
const readerWrap = document.getElementById('scanReaderWrap');
|
|
|
|
|
const toggleBtn = document.getElementById('toggleScannerBtn');
|
|
|
|
|
try {
|
|
|
|
|
Quagga.stop();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn("Error stopping Quagga (it may not have been running):", e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Quagga.stop();
|
|
|
|
|
scannerRunning = false;
|
|
|
|
|
activeScannerCallback = null;
|
|
|
|
|
|
|
|
|
|
if (readerWrap) readerWrap.style.display = 'none';
|
|
|
|
|
if (toggleBtn) toggleBtn.textContent = 'Scanner starten';
|
|
|
|
|
setScanStatus('Scanner gestoppt.', 'warn');
|
|
|
|
|
// Hide the container to free up UI space
|
|
|
|
|
const scannerWrap = document.querySelector('.library-scan-reader-wrap');
|
|
|
|
|
if (scannerWrap) {
|
|
|
|
|
scannerWrap.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const toggleBtn = document.getElementById('toggleScannerBtn');
|
|
|
|
|
if (toggleBtn) {
|
|
|
|
|
toggleBtn.textContent = 'Kamera Scanner'; // Reset button text
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function isStudentCardBarcode(code) {
|
|
|
|
|