You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

240 lines
6.8 KiB

// @flow
import React, { Component } from 'react'
import QrCode from 'qrcode-reader'
import logger from 'logger'
export default class QRCodeCameraPickerCanvas extends Component<
{
width: number,
height: number,
centerSize: number,
cameraBorderSize: number,
cameraBorderLength: number,
intervalCheck: number,
dpr: number,
onPick: string => void,
},
{
message: ?string,
},
> {
static defaultProps = {
width: 260,
height: 185,
centerSize: 110,
cameraBorderSize: 4,
cameraBorderLength: 35,
intervalCheck: 250,
dpr: window.devicePixelRatio || 1,
}
state = {
message: 'Please accept Camera permission',
}
componentDidMount() {
let getUserMedia
let sum = 0
const onkeyup = (e: *) => {
sum += e.which
if (sum === 439 && this.canvasSecond) {
this.canvasSecond.style.filter = 'hue-rotate(90deg)'
}
}
if (document) document.addEventListener('keyup', onkeyup)
this.unsubscribes.push(() => {
if (document) document.removeEventListener('keyup', onkeyup)
})
const { navigator } = window
if (navigator.mediaDevices) {
const mediaDevices = navigator.mediaDevices
getUserMedia = opts => mediaDevices.getUserMedia(opts)
} else {
const f = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia
if (f) {
getUserMedia = opts => new Promise((res, rej) => f.call(navigator, opts, res, rej))
}
}
if (!getUserMedia) {
this.setState({ message: 'Incompatible browser' }) // eslint-disable-line
} else {
const qr = new QrCode()
qr.callback = (err, value) => {
if (!err) {
this.props.onPick(value.result)
}
}
getUserMedia({
video: { facingMode: 'environment' },
})
.then(stream => {
if (this.unmounted) return
this.setState({ message: null })
let video = document.createElement('video')
video.setAttribute('playsinline', 'true')
video.setAttribute('autoplay', 'true')
video.srcObject = stream
video.load()
this.unsubscribes.push(() => {
if (video) {
video.pause()
video.srcObject = null
video = null
}
})
video.onloadedmetadata = () => {
if (this.unmounted || !video) return
try {
video.play()
} catch (e) {
logger.error(e)
}
let lastCheck = 0
let raf
const loop = (t: number) => {
raf = requestAnimationFrame(loop)
const { ctxMain, ctxSecond } = this
if (!ctxMain || !ctxSecond || !video) return
const {
centerSize,
cameraBorderSize,
cameraBorderLength,
dpr,
intervalCheck,
} = this.props
const cs = centerSize * dpr
const cbs = cameraBorderSize * dpr
const cbl = cameraBorderLength * dpr
const { width, height } = ctxMain.canvas
ctxMain.drawImage(video, 0, 0, width, height)
// draw second in the inner
const x = Math.floor((width - cs) / 2 - cbs)
const y = Math.floor((height - cs) / 2 - cbs)
const w = cs + cbs * 2
const h = cs + cbs * 2
ctxSecond.beginPath()
ctxSecond.rect(x, y, w, h)
ctxSecond.clip()
ctxSecond.drawImage(ctxMain.canvas, 0, 0)
// draw the camera borders
ctxSecond.strokeStyle = '#fff'
ctxSecond.lineWidth = cbs
ctxSecond.beginPath()
ctxSecond.moveTo(x + cbl, y)
ctxSecond.lineTo(x, y)
ctxSecond.lineTo(x, y + cbl)
ctxSecond.stroke()
ctxSecond.beginPath()
ctxSecond.moveTo(x + cbl, y + h)
ctxSecond.lineTo(x, y + h)
ctxSecond.lineTo(x, y + h - cbl)
ctxSecond.stroke()
ctxSecond.beginPath()
ctxSecond.moveTo(x + w - cbl, y + h)
ctxSecond.lineTo(x + w, y + h)
ctxSecond.lineTo(x + w, y + h - cbl)
ctxSecond.stroke()
ctxSecond.beginPath()
ctxSecond.moveTo(x + w - cbl, y)
ctxSecond.lineTo(x + w, y)
ctxSecond.lineTo(x + w, y + cbl)
ctxSecond.stroke()
if (t - lastCheck >= intervalCheck) {
lastCheck = t
qr.decode(ctxMain.getImageData(0, 0, width, height))
}
}
raf = requestAnimationFrame(loop)
this.unsubscribes.push(() => cancelAnimationFrame(raf))
}
})
.catch(e => {
if (this.unmounted) return
this.setState({
message: String(e.message || e),
})
})
}
}
componentWillUnmount() {
this.unmounted = true
this.unsubscribes.forEach(f => f())
}
canvasMain: ?HTMLCanvasElement
ctxMain: ?CanvasRenderingContext2D
canvasSecond: ?HTMLCanvasElement
ctxSecond: ?CanvasRenderingContext2D
unsubscribes: Array<() => void> = []
unmounted = false
_onMainRef = (canvasMain: ?HTMLCanvasElement) => {
if (canvasMain === this.canvasMain) return
this.canvasMain = canvasMain
if (canvasMain) {
this.ctxMain = canvasMain.getContext('2d')
}
}
_onSecondRef = (canvasSecond: ?HTMLCanvasElement) => {
if (canvasSecond === this.canvasSecond) return
this.canvasSecond = canvasSecond
if (canvasSecond) {
this.ctxSecond = canvasSecond.getContext('2d')
}
}
render() {
const { width, height, dpr } = this.props
const { message } = this.state
const style = {
width,
height,
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#eee',
color: '#666',
fontSize: `${(width / 30).toFixed(0)}px`,
overflow: 'hidden',
}
const mainStyle = {
width,
height,
position: 'absolute',
top: 0,
left: 0,
filter: 'brightness(80%) blur(6px)',
transform: 'scaleX(-1)',
}
const secondStyle = {
width,
height,
position: 'absolute',
top: 0,
left: 0,
transform: 'scaleX(-1)',
}
return message ? (
<div style={style}>
<p>{message}</p>
</div>
) : (
<div style={style}>
<canvas ref={this._onMainRef} style={mainStyle} width={dpr * width} height={dpr * height} />
<canvas
ref={this._onSecondRef}
style={secondStyle}
width={dpr * width}
height={dpr * height}
/>
</div>
)
}
}