Tested and working, added ability to use vite

This commit is contained in:
Craig
2026-08-02 18:46:23 +01:00
parent fc30b28921
commit 42c4deca75
12 changed files with 143 additions and 25 deletions
+14
View File
@@ -12,6 +12,7 @@
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@vitejs/plugin-basic-ssl": "^2.3.0",
"svelte": "^5.56.4",
"vite": "^7.3.6",
"vitest": "^4.1.10"
@@ -953,6 +954,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/@vitejs/plugin-basic-ssl": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz",
"integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"peerDependencies": {
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/@vitest/expect": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
+2
View File
@@ -5,12 +5,14 @@
"type": "module",
"scripts": {
"dev": "vite",
"dev:host": "vite --host",
"build": "vite build",
"preview": "vite preview",
"test": "vitest run"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@vitejs/plugin-basic-ssl": "^2.3.0",
"svelte": "^5.56.4",
"vite": "^7.3.6",
"vitest": "^4.1.10"
+13 -2
View File
@@ -107,37 +107,48 @@
}
async function handleBarcode(barcode) {
console.log('[App] handleBarcode:', barcode)
resetScanFlow()
scanBarcode = barcode
scanLoading = true
// Step 1: Check local DB by barcode
try {
console.log('[App] searching local DB for barcode:', barcode)
const results = await api.searchFoodsByBarcode(barcode)
console.log('[App] local search results:', results?.length ?? 0)
if (results && results.length > 0) {
localFood = results[0]
scanLogQuantity = defaultQuantity(localFood)
scanLoading = false
scanPhase = 'localFound'
console.log('[App] local food found:', localFood.name)
return
}
console.log('[App] not found locally, trying OpenFoodFacts…')
} catch (e) {
console.warn('[App] local search failed:', e)
// Local lookup failed — try OFF anyway
}
// Step 2: Not found locally → try OFF
try {
console.log('[App] fetching OFF product:', barcode)
offFood = await api.offProduct(barcode)
scanLoading = false
scanPhase = 'offFound'
console.log('[App] OFF product found:', offFood?.product_name ?? offFood?.name)
} catch (e) {
console.warn('[App] OFF lookup failed:', e.message)
// OFF miss (404) or network error
scanLoading = false
if (e.message?.includes('404') || e.message?.includes('not found')) {
scanPhase = 'notFound'
console.log('[App] OFF product not found (404)')
} else {
scanError = e.message
scanPhase = 'error'
console.error('[App] OFF lookup error:', e.message)
}
}
}
@@ -572,8 +583,8 @@
padding: 0 0.3rem;
}
.macro-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-actions {
@@ -19,6 +19,7 @@
let stopHandle = null
function handleDetected(barcode) {
console.log('[BarcodeScanner] 🎯 barcode detected:', barcode)
// Stop scanning once we have a barcode
if (stopHandle) {
stopHandle.stop()
@@ -28,6 +29,7 @@
}
function handleError(err) {
console.error('[BarcodeScanner] camera error:', err)
loading = false
if (isPermissionDeniedError(err)) {
cameraUnavailable = true
@@ -45,6 +47,7 @@
e.preventDefault()
const code = manualBarcode.trim()
if (!code) return
console.log('[BarcodeScanner] manual barcode submitted:', code)
handleDetected(code)
}
+2 -2
View File
@@ -538,8 +538,8 @@
}
.macro-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
+45 -11
View File
@@ -8,8 +8,13 @@
*
* NOTE: getUserMedia requires a secure context — HTTPS via the Caddy
* reverse proxy must be in place before phone testing (spec §5).
*
* DEBUGGING: Open browser DevTools (F12) → Console to see scanner logs.
* Set localStorage.debugScanner = 'true' for verbose per-frame logging.
*/
const DEBUG = typeof globalThis !== 'undefined' && globalThis.localStorage?.getItem('debugScanner') === 'true'
// zxing-wasm is the fallback decoder; imported lazily so Chromium users
// on the native path never pay the WASM download cost.
// The module exposes readBarcodesFromImageData for ImageData input.
@@ -82,57 +87,80 @@ export function startScanner(videoEl, { onDetect, onError }) {
// Extract the current video frame into our canvas
const vw = videoEl.videoWidth
const vh = videoEl.videoHeight
if (vw === 0 || vh === 0) return // not playing yet
if (vw === 0 || vh === 0) {
if (DEBUG) console.warn('[scanner] video dimensions are 0 — not playing yet?')
return
}
canvas.width = vw
canvas.height = vh
ctx.drawImage(videoEl, 0, 0, vw, vh)
if (DEBUG) console.log('[scanner] frame extracted', vw, '×', vh)
let barcode = null
if (detector) {
// Native BarcodeDetector path
try {
const detections = await detector.detect(canvas)
if (DEBUG) console.log('[scanner] BarcodeDetector returned', detections.length, 'results')
if (detections.length > 0 && !stopped) {
barcode = detections[0].rawValue
if (DEBUG) console.log('[scanner] 🎯 detected via BarcodeDetector:', barcode)
}
} catch {
// Native detector can throw on some frames; ignore and try next
} catch (e) {
console.warn('[scanner] BarcodeDetector error on frame:', e)
}
} else {
// zxing-wasm fallback — load once
if (!zxingReader) {
const mod = await import('zxing-wasm')
// zxing-wasm v3 exports readBarcodesFromImageData
zxingReader = mod.readBarcodesFromImageData
try {
if (DEBUG) console.log('[scanner] loading zxing-wasm…')
const mod = await import('zxing-wasm')
// zxing-wasm v3 exports readBarcodesFromImageData
zxingReader = mod.readBarcodesFromImageData
if (DEBUG) console.log('[scanner] zxing-wasm loaded')
} catch (e) {
console.error('[scanner] failed to load zxing-wasm:', e)
return
}
}
try {
const imageData = ctx.getImageData(0, 0, vw, vh)
const results = await zxingReader(imageData, {
formats: BARCODE_FORMATS,
})
if (DEBUG) console.log('[scanner] zxing returned', results.length, 'results')
if (results.length > 0 && !stopped) {
barcode = results[0].text
if (DEBUG) console.log('[scanner] 🎯 detected via zxing:', barcode)
}
} catch {
// zxing decode errors on non-barcode frames — ignore
} catch (e) {
console.warn('[scanner] zxing decode error on frame:', e)
}
}
if (barcode && !stopped) {
onDetect(barcode)
}
} catch {
// Frame extraction can fail if video isn't ready yet — ignore
} catch (e) {
console.warn('[scanner] frame extraction error:', e)
}
}
// ── Start camera ──────────────────────────────────────────────────────
;(async () => {
try {
// Request a minimum resolution for better barcode detection.
// Desktop webcams default to very low res (e.g. 320×240) which makes
// fine barcode lines hard to read. 640×480 is a good baseline.
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' },
video: {
facingMode: 'environment',
width: { min: 640, ideal: 1280 },
height: { min: 480, ideal: 720 },
},
audio: false,
})
if (stopped) {
@@ -143,9 +171,15 @@ export function startScanner(videoEl, { onDetect, onError }) {
videoEl.srcObject = stream
await videoEl.play()
if (DEBUG) {
console.log('[scanner] camera started, dimensions:', videoEl.videoWidth, '×', videoEl.videoHeight)
console.log('[scanner] decoder:', detector ? 'native BarcodeDetector' : 'zxing-wasm')
}
// Start decode loop at ~4 fps
timer = setInterval(decodeFrame, DECODE_INTERVAL_MS)
} catch (err) {
console.error('[scanner] getUserMedia failed:', err)
if (!stopped) {
onError(err instanceof Error ? err : new Error(String(err)))
}
+2 -1
View File
@@ -1,9 +1,10 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import basicSsl from '@vitejs/plugin-basic-ssl'
// https://vite.dev/config/
export default defineConfig({
plugins: [svelte()],
plugins: [svelte(), basicSsl()],
server: {
// Dev proxy so the frontend can call /api without CORS in production-style setups
proxy: {