Skip to content

Commit

Permalink
Use jsqr for now as zbar-wasm seems to be too unstable
Browse files Browse the repository at this point in the history
  • Loading branch information
adzialocha committed Apr 19, 2024
1 parent 222a679 commit b8b74e9
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 74 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- Remove deprecated translations #3756
- Refactor chat store into React context #3725
- Improve security: restrict file protocol #3769
- Refactor QR code reader, use `zbar-wasm` for scanning #3762
- Refactor QR code reader #3762

### Fixed
- fix chat audit dialog was going out of viewport on smaller screens #3736
Expand Down
5 changes: 0 additions & 5 deletions bin/build-frontend-ts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ function config(options) {
define: {
'process.env.NODE_ENV': isProduction ? '"production"' : '"development"',
},
// We want the inlined version of the `zbar-wasm` package as the .wasm paths
// are unfortunately hardcoded in this package. We can force esbuild to load
// the inlined version through setting a condition which will make esbuild
// prefer an package export named after it
conditions: ['zbar-inlined'],
plugins,
external: ["*.jpg", "*.png", "*.webp", "*.svg"]
}
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@
"@emoji-mart/data": "1.1.2",
"@emoji-mart/react": "1.1.1",
"@mapbox/geojson-extent": "^1.0.0",
"@undecaf/zbar-wasm": "^0.10.1",
"application-config": "^1.0.1",
"classnames": "^2.3.2",
"debounce": "^1.2.0",
Expand All @@ -92,6 +91,7 @@
"error-stack-parser": "^2.0.7",
"filesize": "^10.1.0",
"immutable": "^4.0.0",
"jsqr": "^1.4.0",
"mapbox-gl": "^1.12.0",
"mime-types": "^2.1.31",
"moment": "^2.29.2",
Expand Down
94 changes: 33 additions & 61 deletions src/renderer/components/QrReader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,7 @@ import React, {
useRef,
useState,
} from 'react'
import {
ZBarConfigType,
ZBarScanner,
ZBarSymbol,
ZBarSymbolType,
getDefaultScanner,
scanImageData,
} from '@undecaf/zbar-wasm'
import scanQrCode from 'jsqr'
import classNames from 'classnames'
import { Spinner } from '@blueprintjs/core'

Expand Down Expand Up @@ -103,45 +96,13 @@ export default function QrReader({ onError, onScan }: Props) {

const [ready, setReady] = useState(false)
const [error, setError] = useState(false)
const [scanner, setScanner] = useState<ZBarScanner | undefined>(undefined)
const [videoDevices, setVideoDevices] = useState<MediaDeviceInfo[]>([])
const [deviceId, setDeviceId] = useState<string | undefined>(undefined)
const [dimensions, setDimensions] = useState<ImageDimensions>({
width: 640,
height: 480,
})

// Create a QR code scanner object from the "zbar" library.
//
// We use it to detect QR codes in image data. The library offers many other
// detection methods as well, we configure it here to only look
// for "2D QR-Codes".
useEffect(() => {
const createScanner = async () => {
const qrCodeScanner = await getDefaultScanner()

// First disable all code types
Object.keys(ZBarSymbolType).forEach(key => {
qrCodeScanner.setConfig(
key as unknown as ZBarSymbolType,
ZBarConfigType.ZBAR_CFG_ENABLE,
0
)
})

// .. and enable only QrCode scanning
qrCodeScanner.setConfig(
ZBarSymbolType.ZBAR_QRCODE,
ZBarConfigType.ZBAR_CFG_ENABLE,
1
)

setScanner(qrCodeScanner)
}

createScanner()
}, [])

// Get all current video devices available to the user.
useEffect(() => {
const getAllCameras = async () => {
Expand All @@ -161,22 +122,19 @@ export default function QrReader({ onError, onScan }: Props) {
getAllCameras()
}, [])

// General handler for scanning results coming from the "zbar" library.
// General handler for scanning results coming from the "jsqr" library.
//
// Since there can be multiple results we return all them. Additionally
// we have checks in place to make sure we're not firing any callbacks
// when this React component has already been unmounted.
const handleScanResults = useCallback(
(results: ZBarSymbol[]) => {
// Additionally we have checks in place to make sure we're not firing any
// callbacks when this React component has already been unmounted.
const handleScanResult = useCallback(
result => {
let unmounted = false

if (unmounted) {
return
}

results.forEach(result => {
onScan(result.decode())
})
onScan(result.data)

return () => {
unmounted = true
Expand Down Expand Up @@ -206,9 +164,13 @@ export default function QrReader({ onError, onScan }: Props) {
const base64 = await runtime.readClipboardImage()
if (base64) {
const imageData = await base64ToImageData(base64)
const results = await scanImageData(imageData, scanner)
if (results.length > 0) {
handleScanResults(results)
const result = scanQrCode(
imageData.data,
imageData.width,
imageData.height
)
if (result) {
handleScanResult(result)
return
} else {
throw new Error('no data in clipboard image')
Expand All @@ -227,7 +189,7 @@ export default function QrReader({ onError, onScan }: Props) {
text: `${tx('qrscan_failed')}: ${error}`,
})
}
}, [handleScanResults, onScan, scanner, tx, userFeedback])
}, [handleScanResult, onScan, tx, userFeedback])

// Read data from an external image file.
//
Expand All @@ -251,10 +213,14 @@ export default function QrReader({ onError, onScan }: Props) {
// Convert file to correct image data and scan it
const base64 = await fileToBase64(file)
const imageData = await base64ToImageData(base64)
const results = await scanImageData(imageData, scanner)
const result = scanQrCode(
imageData.data,
imageData.width,
imageData.height
)

if (results.length > 0) {
handleScanResults(results)
if (result) {
handleScanResult(result)
} else {
userFeedback({
type: 'error',
Expand All @@ -271,7 +237,7 @@ export default function QrReader({ onError, onScan }: Props) {
inputRef.current.value = ''
}
},
[handleError, handleScanResults, scanner, tx, userFeedback]
[handleError, handleScanResult, tx, userFeedback]
)

// Show a context menu with different video input options to the user.
Expand Down Expand Up @@ -393,7 +359,7 @@ export default function QrReader({ onError, onScan }: Props) {
}
}, [deviceId])

// Frequently scan image data for QR code with "zbar" library.
// Frequently scan image data for QR code with "jsqr" library.
//
// We achieve this by extracting the image data from the video element using
// an intermediary canvas context.
Expand Down Expand Up @@ -426,8 +392,14 @@ export default function QrReader({ onError, onScan }: Props) {

const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
try {
const results = await scanImageData(imageData, scanner)
handleScanResults(results)
const result = scanQrCode(
imageData.data,
imageData.width,
imageData.height
)
if (result) {
handleScanResult(result)
}
} catch (error: any) {
handleError(error)
}
Expand All @@ -437,7 +409,7 @@ export default function QrReader({ onError, onScan }: Props) {
return () => {
window.clearInterval(interval)
}
}, [handleError, handleScanResults, onScan, scanner])
}, [handleError, handleScanResult, onScan])

return (
<div className={styles.qrReader}>
Expand Down

0 comments on commit b8b74e9

Please sign in to comment.