diff --git a/static/index.html b/static/index.html
index c42b23d..1593505 100644
--- a/static/index.html
+++ b/static/index.html
@@ -2826,8 +2826,9 @@
// BARCODE MODE
// ═══════════════════════════════════════════════════════════════════════
function _barcodeHints() {
- // All formats — 1D (Code 128, EAN, UPC, etc.) + 2D (QR, DataMatrix,
- // PDF417, Aztec) for serial number stickers with 2D barcodes.
+ // Focused 1D formats for fast scanning on mobile.
+ // Removed TRY_HARDER (too slow on mobile) and 2D formats (QR, DataMatrix,
+ // PDF417, Aztec) which are rare for canteen machine barcodes.
const hints = new Map();
const fmts = [
ZXing.BarcodeFormat.CODE_128,
@@ -2836,31 +2837,41 @@
ZXing.BarcodeFormat.EAN_8,
ZXing.BarcodeFormat.UPC_A,
ZXing.BarcodeFormat.UPC_E,
- ZXing.BarcodeFormat.CODE_93,
ZXing.BarcodeFormat.ITF,
- ZXing.BarcodeFormat.CODABAR,
- ZXing.BarcodeFormat.QR_CODE,
- ZXing.BarcodeFormat.DATA_MATRIX,
- ZXing.BarcodeFormat.PDF_417,
- ZXing.BarcodeFormat.AZTEC,
];
hints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, fmts);
- hints.set(ZXing.DecodeHintType.TRY_HARDER, true);
return hints;
}
function _barcodeOptions() {
return {
- delayBetweenScanAttempts: 300, // scan every 300ms (was 50ms — too fast
- // for TRY_HARDER with 13 formats, caused
- // decoder thrashing and missed barcodes)
+ delayBetweenScanAttempts: 300,
delayBetweenScanSuccess: 2000, // 2s cooldown after a successful scan
};
}
+ let zxingLoadAttempted = false;
+
+ function checkZxingLoaded() {
+ if (typeof ZXing !== 'undefined') return true;
+ if (!zxingLoadAttempted) {
+ zxingLoadAttempted = true;
+ console.warn('ZXing library failed to load from CDN');
+ }
+ return false;
+ }
+
async function startScanning() {
if (addAssetMode !== 'barcode') return;
if (scanningActive) return;
+
+ // Check ZXing loaded from CDN
+ if (!checkZxingLoaded()) {
+ setScanStatus('Barcode library not loaded — enter ID manually below', 'error');
+ showFallbackCameraError('Barcode scanner library failed to load.
Enter the ID manually instead.');
+ return;
+ }
+
try {
setScanStatus('Starting camera...', 'working');
@@ -2887,7 +2898,6 @@
deviceId = rear ? rear.deviceId : devices[0].deviceId;
}
} catch (e) {
- // Fallback: pass null to let decodeFromVideoDevice pick default
console.warn('listVideoInputDevices failed, using default camera:', e);
}
@@ -2897,11 +2907,9 @@
setScanStatus('Point camera at a barcode', 'success');
- // Camera readiness check: if the video never starts streaming
- // (no media attached within 5s), treat as camera failure
+ // Camera readiness check — give it 10s (was 5s, too tight for some mobile browsers)
const cameraFailTimer = setTimeout(() => {
if (video.readyState === 0 && scanningActive) {
- console.warn('Camera stream never started — showing manual fallback');
scanningActive = false;
video.style.display = 'none';
if (codeReader) {
@@ -2909,15 +2917,9 @@
}
handleCameraError(new Error('No camera stream — connect a camera or enter ID manually'));
}
- }, 5000);
+ }, 10000);
- // decodeFromVideoDevice handles camera + scanning in one call.
- // The callback fires on every video frame (~30fps). ZXing passes
- // "No MultiFormat Readers..." as an error when no barcode is in view
- // — that's NORMAL, not a hardware failure. Only treat real device
- // errors (permission denied, camera not found, stream broken) as fatal.
codeReader.decodeFromVideoDevice(deviceId, video, (result, err) => {
- // Clear the timeout once we get any callback (stream started or error)
clearTimeout(cameraFailTimer);
if (result && scanningActive) {
const val = result.getText();
@@ -2925,7 +2927,6 @@
handleBarcode(val.trim());
}
} else if (err && scanningActive) {
- // Filter out "no barcode in frame" — that's normal, not a failure
const msg = (err.message || err || '').toLowerCase();
if (!msg.includes('multiformat') && !msg.includes('unable to detect')) {
console.error('ZXing camera error:', err);
@@ -3036,14 +3037,13 @@
try {
const asset = await api(`/api/assets/search?machine_id=${encodeURIComponent(machineId)}`);
- // Asset exists — go directly to its detail view
- showToast(`${esc(asset.name)} — showing details`, false);
+ // Asset exists — show result in-place with scan new button
+ showToast(`${esc(asset.name)} — found!`, false);
// Auto-checkin if GPS available (background, non-blocking)
if (AppState.gpsLat != null) {
doAutoCheckin(asset.id);
}
- switchTab('tabAssets');
- viewAsset(asset.id);
+ showScannedAsset(asset);
} catch (e) {
if (e.message === 'Asset not found' || e.message.includes('404')) {
showNewAssetForm(machineId);
@@ -3073,6 +3073,7 @@
${hasGps ? '✓ GPS captured' : '📍 GPS needed'}
+
`;
@@ -3082,6 +3083,46 @@
}
}
+ function resetBarcodeScanner() {
+ // Reset the scan result UI and restart barcode scanner
+ document.getElementById('scanResult').style.display = 'none';
+ document.getElementById('scanResult').innerHTML = '';
+ document.getElementById('checkinCard').style.display = 'none';
+ setScanStatus('Ready — scan another barcode', 'success');
+ stopScanning();
+ // Re-show the camera placeholder
+ const ph = document.getElementById('cameraPlaceholder');
+ if (ph) {
+ ph.style.display = 'block';
+ ph.innerHTML = '
' + esc(m.matched_on) + '' +
+ '