diff --git a/src/containers/BarcodeScanner/Barcode.js b/src/containers/BarcodeScanner/Barcode.js new file mode 100644 index 0000000..631b168 --- /dev/null +++ b/src/containers/BarcodeScanner/Barcode.js @@ -0,0 +1,322 @@ +import React, {useEffect, useState} from 'react'; +import {Paper, IconButton} from "@material-ui/core"; +import { Close as CloseIcon } from '@material-ui/icons' +import useStyles from "./styles"; + +const Barcode = ({ isScannerOpen, handleClose }) => { + const [decodecBarcode, setDecodedBarcode] = useState(''); + const classes = useStyles() + + const onClose = () => { + stop() + handleClose() + } + + let localMediaStream = null; + const dimensions = { + height: 0, + width: 0, + start: 0, + end: 0 + } + + const elements = { + video: null, + canvas: null, + ctx: null, + canvasg: null, + ctxg: null + } + + const upc = { + '0': [3, 2, 1, 1], + '1': [2, 2, 2, 1], + '2': [2, 1, 2, 2], + '3': [1, 4, 1, 1], + '4': [1, 1, 3, 2], + '5': [1, 2, 3, 1], + '6': [1, 1, 1, 4], + '7': [1, 3, 1, 2], + '8': [1, 2, 1, 3], + '9': [3, 1, 1, 2] + }; + + const check = { + 'oooooo': '0', + 'ooeoee': '1', + 'ooeeoe': '2', + 'ooeeeo': '3', + 'oeooee': '4', + 'oeeooe': '5', + 'oeeeoo': '6', + 'oeoeoe': '7', + 'oeoeeo': '8', + 'oeeoeo': '9' + } + + const config = { + strokeColor: '#f00', + start: 0.1, + end: 0.9, + threshold: 160, + quality: 0.9, + delay: 100, + video: '#barcodevideo', + canvas: '#barcodecanvas', + canvasg: '#barcodecanvasg' + } + + const play = () => { + dimensions.height = elements.video.videoHeight; + dimensions.width = elements.video.videoWidth; + + dimensions.start = dimensions.width * config.start; + dimensions.end = dimensions.width * config.end; + + elements.canvas.width = dimensions.width; + elements.canvas.height = dimensions.height; + elements.canvasg.width = dimensions.width; + elements.canvasg.height = dimensions.height; + + drawLine(elements.ctxg); + + setInterval(() => { + const snapshot = takeSnapshot(elements.ctx, elements.video) + processSnapshot(snapshot) + }, config.delay); + } + + const stop = () => { + elements.video.pause(); + localMediaStream.getTracks()[0].stop(); + } + + const init = () => { + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; + + elements.video = document.querySelector(config.video); + elements.canvas = document.querySelector(config.canvas); + elements.ctx = elements.canvas.getContext('2d'); + elements.canvasg = document.querySelector(config.canvasg); + elements.ctxg = elements.canvasg.getContext('2d'); + + if (navigator.getUserMedia) { + navigator.getUserMedia({audio: false, video: true}, (stream) => { + elements.video.srcObject = stream; + localMediaStream = stream; + }, (error) => { + console.log(error); + }); + } + + elements.video.addEventListener('canplay', play, false) + } + + const takeSnapshot = (canvasElement, videoElement) => { + canvasElement.drawImage(videoElement, 0, 0, dimensions.width, dimensions.height); + return canvasElement.getImageData(dimensions.start, dimensions.height * 0.5, dimensions.end - dimensions.start, 1).data; + } + + const processSnapshot = (snapshot) => { + + const bars = []; + const pixels = []; + let pixelBars = []; + + // convert to grayscale + for (let i = 0; i < snapshot.length; i += 4) { + pixels.push(Math.round(snapshot[i] * 0.2126 + snapshot[i + 1] * 0.7152 + snapshot[ i + 2] * 0.0722)); + } + + // normalize and convert to binary + const minPixelValue = Math.min(...pixels); + const maxPixelValue = Math.max(...pixels); + + const binary = pixels.reduce((arr, val) => { + const binaryValue = Math.round((val - minPixelValue) / (maxPixelValue - minPixelValue) * 255) > config.threshold ? 1 : 0 + return [...arr, binaryValue] + }, []); + + // determine bar widths + let current = binary[0]; + let count = 0; + + for (let i = 0; i < binary.length; i++) { + if (binary[i] === current) { + count++; + } else { + pixelBars.push(count); + count = 1; + current = binary[i] + } + } + pixelBars.push(count); + + // quality check + if (pixelBars.length < (3 + 24 + 5 + 24 + 3 + 1)) { + return; + } + + // find starting sequence + const minFactor = 0.5; + const maxFactor = 1.5; + let startIndex = 0; + + for (let i = 3; i < pixelBars.length; i++) { + const refLength = (pixelBars[i] + pixelBars[i - 1] + pixelBars[i - 2]) / 3; + if ( + (pixelBars[i] > (minFactor * refLength) || pixelBars[i] < (maxFactor * refLength)) + && (pixelBars[i-1] > (minFactor * refLength) || pixelBars[i-1] < (maxFactor * refLength)) + && (pixelBars[i-2] > (minFactor * refLength) || pixelBars[i-2] < (maxFactor * refLength)) + && (pixelBars[i-3] > 3 * refLength) + ) { + startIndex = i - 2; + break; + } + } + + + console.log("startIndex: " + startIndex ); + // return if no starting sequence found + if (startIndex === 0) { + return; + } + + // discard leading and trailing patterns + pixelBars = pixelBars.slice(startIndex, startIndex + 3 + 24 + 5 + 24 + 3); + + console.log("pixelBars: " + pixelBars ); + + // calculate relative widths + const ref = (pixelBars[0] + pixelBars[1] + pixelBars[2]) / 3; + + for (let i = 0; i < pixelBars.length; i++) { + bars.push(Math.round(pixelBars[i] / ref * 100) / 100); + } + + // analyze pattern + analyzePattern(bars); + } + + const analyzePattern = (bars) => { + + console.clear(); + console.log("analyzing"); + // determine parity first digit and reverse sequence if necessary + const first = normalize(bars.slice(3, 3 + 4), 7); + if (!isOdd(Math.round(first[1] + first[3]))) { + bars = bars.reverse(); + } + + // split into digits + const digits = [ + normalize(bars.slice(3, 3 + 4), 7), + normalize(bars.slice(7, 7 + 4), 7), + normalize(bars.slice(11, 11 + 4), 7), + normalize(bars.slice(15, 15 + 4), 7), + normalize(bars.slice(19, 19 + 4), 7), + normalize(bars.slice(23, 23 + 4), 7), + normalize(bars.slice(32, 32 + 4), 7), + normalize(bars.slice(36, 36 + 4), 7), + normalize(bars.slice(40, 40 + 4), 7), + normalize(bars.slice(44, 44 + 4), 7), + normalize(bars.slice(48, 48 + 4), 7), + normalize(bars.slice(52, 52 + 4), 7) + ] + + console.log("digits: " + digits); + + // determine parity and reverse if necessary + const parities = []; + + for (let i = 0; i < 6; i++) { + if (parity(digits[i])) { + parities.push('o'); + } else { + parities.push('e'); + digits[i] = digits[i].reverse(); + } + } + + // identify digits + const result = []; + let quality = 0; + + for (let i = 0; i < digits.length; i++) { + + let distance = 9; + let bestKey = ''; + + for (let key in upc) { + if (maxDistance(digits[i], upc[key]) < distance) { + distance = maxDistance(digits[i], upc[key]); + bestKey = key; + } + } + + result.push(bestKey); + if (distance > quality) { + quality = distance; + } + + } + console.log("result: " + result); + // check digit + const checkDigit = check[parities.join('')]; + + console.log("quality: " + quality); + // output + if(quality < config.quality) { + const barcode = checkDigit + result.join('') + setDecodedBarcode(barcode) + onClose() + } + + } + + const normalize = (input, total) => { + const sum = input.reduce((acc, val) => acc + val, 0); + return input.reduce((acc, val) => [...acc, val / sum * total], []); + } + + const isOdd = (num) => num % 2; + + const maxDistance = (a, b) => + a.reduce((max, value, index) => { + const current = Math.abs(value - b[index]) + return current > max ? current : max + }, 0) + + const parity = (digit) => isOdd(Math.round(digit[1] + digit[3])) + + const drawLine = (canvasContext) => { + canvasContext.strokeStyle = config.strokeColor; + canvasContext.lineWidth = 3; + canvasContext.beginPath(); + canvasContext.moveTo(dimensions.start, dimensions.height * 0.5); + canvasContext.lineTo(dimensions.end, dimensions.height * 0.5); + canvasContext.stroke(); + } + + useEffect(() => { + init() + return () => elements.video.removeEventListener('canplay', play, false) + }, []) + + return ( + +
+ + + +
+
+
+ +
+ ); +} + +export default Barcode; diff --git a/src/containers/BarcodeScanner/index.js b/src/containers/BarcodeScanner/index.js index 34e9894..3088c35 100644 --- a/src/containers/BarcodeScanner/index.js +++ b/src/containers/BarcodeScanner/index.js @@ -1,293 +1,28 @@ -import React, { useState, useEffect } from 'react'; -import {IconButton, Grid} from '@material-ui/core' +import React, {useState} from 'react'; +import {IconButton} from "@material-ui/core"; import {CropFree as CropFreeIcon} from '@material-ui/icons' -import './styles.css' +import Barcode from './Barcode' const BarcodeScanner = () => { - const [decodecBarcode, setDecodedBarcode] = useState(''); + const [isScannerOpen, setIsScannerOpen] = useState(false) - const dimensions = { - height: 0, - width: 0, - start: 0, - end: 0 + const handleOpen = () => { + setIsScannerOpen(true) } - const elements = { - video: null, - canvas: null, - ctx: null, - canvasg: null, - ctxg: null + const handleClose = () => { + setIsScannerOpen(false) } - - const upc = { - '0': [3, 2, 1, 1], - '1': [2, 2, 2, 1], - '2': [2, 1, 2, 2], - '3': [1, 4, 1, 1], - '4': [1, 1, 3, 2], - '5': [1, 2, 3, 1], - '6': [1, 1, 1, 4], - '7': [1, 3, 1, 2], - '8': [1, 2, 1, 3], - '9': [3, 1, 1, 2] - }; - - const check = { - 'oooooo': '0', - 'ooeoee': '1', - 'ooeeoe': '2', - 'ooeeeo': '3', - 'oeooee': '4', - 'oeeooe': '5', - 'oeeeoo': '6', - 'oeoeoe': '7', - 'oeoeeo': '8', - 'oeeoeo': '9' - } - - const config = { - strokeColor: '#f00', - start: 0.1, - end: 0.9, - threshold: 160, - quality: 0.45, - delay: 100, - video: '#barcodevideo', - canvas: '#barcodecanvas', - canvasg: '#barcodecanvasg' - } - - const init = () => { - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; - - elements.video = document.querySelector(config.video); - elements.canvas = document.querySelector(config.canvas); - elements.ctx = elements.canvas.getContext('2d'); - elements.canvasg = document.querySelector(config.canvasg); - elements.ctxg = elements.canvasg.getContext('2d'); - - if (navigator.getUserMedia) { - navigator.getUserMedia({audio: false, video: true}, function(stream) { - elements.video.srcObject = stream; - }, function(error) { - console.log(error); - }); - } - - elements.video.addEventListener('canplay', () => { - - dimensions.height = elements.video.videoHeight; - dimensions.width = elements.video.videoWidth; - - dimensions.start = dimensions.width * config.start; - dimensions.end = dimensions.width * config.end; - - elements.canvas.width = dimensions.width; - elements.canvas.height = dimensions.height; - elements.canvasg.width = dimensions.width; - elements.canvasg.height = dimensions.height; - - drawLine(elements.ctxg); - - setInterval(() => { - const snapshot = takeSnapshot(elements.ctx, elements.video) - processSnapshot(snapshot) - }, config.delay); - }, false); - } - - const takeSnapshot = (canvasElement, videoElement) => { - canvasElement.drawImage(videoElement, 0, 0, dimensions.width, dimensions.height); - return canvasElement.getImageData(dimensions.start, dimensions.height * 0.5, dimensions.end - dimensions.start, 1).data; - } - - const processSnapshot = (snapshot) => { - - const bars = []; - const pixels = []; - let pixelBars = []; - - // convert to grayscale - for (let i = 0; i < snapshot.length; i += 4) { - pixels.push(Math.round(snapshot[i] * 0.2126 + snapshot[i + 1] * 0.7152 + snapshot[ i + 2] * 0.0722)); - } - - // normalize and convert to binary - const minPixelValue = Math.min(...pixels); - const maxPixelValue = Math.max(...pixels); - - const binary = pixels.reduce((arr, val) => { - const binaryValue = Math.round((val - minPixelValue) / (maxPixelValue - minPixelValue) * 255) > config.threshold ? 1 : 0 - return [...arr, binaryValue] - }, []); - - // determine bar widths - let current = binary[0]; - let count = 0; - - for (let i = 0; i < binary.length; i++) { - if (binary[i] === current) { - count++; - } else { - pixelBars.push(count); - count = 1; - current = binary[i] - } - } - pixelBars.push(count); - - // quality check - if (pixelBars.length < (3 + 24 + 5 + 24 + 3 + 1)) { - return; - } - - // find starting sequence - const minFactor = 0.5; - const maxFactor = 1.5; - let startIndex = 0; - - for (let i = 3; i < pixelBars.length; i++) { - const refLength = (pixelBars[i] + pixelBars[i - 1] + pixelBars[i - 2]) / 3; - if ( - (pixelBars[i] > (minFactor * refLength) || pixelBars[i] < (maxFactor * refLength)) - && (pixelBars[i-1] > (minFactor * refLength) || pixelBars[i-1] < (maxFactor * refLength)) - && (pixelBars[i-2] > (minFactor * refLength) || pixelBars[i-2] < (maxFactor * refLength)) - && (pixelBars[i-3] > 3 * refLength) - ) { - startIndex = i - 2; - break; - } - } - - // return if no starting sequence found - if (startIndex === 0) { - return; - } - - // discard leading and trailing patterns - pixelBars = pixelBars.slice(startIndex, startIndex + 3 + 24 + 5 + 24 + 3); - - // calculate relative widths - const ref = (pixelBars[0] + pixelBars[1] + pixelBars[2]) / 3; - - for (let i = 0; i < pixelBars.length; i++) { - bars.push(Math.round(pixelBars[i] / ref * 100) / 100); - } - - // analyze pattern - analyzePattern(bars); - } - - const analyzePattern = (bars) => { - // determine parity first digit and reverse sequence if necessary - const first = normalize(bars.slice(3, 3 + 4), 7); - if (!isOdd(Math.round(first[1] + first[3]))) { - bars = bars.reverse(); - } - - // split into digits - const digits = [ - normalize(bars.slice(3, 3 + 4), 7), - normalize(bars.slice(7, 7 + 4), 7), - normalize(bars.slice(11, 11 + 4), 7), - normalize(bars.slice(15, 15 + 4), 7), - normalize(bars.slice(19, 19 + 4), 7), - normalize(bars.slice(23, 23 + 4), 7), - normalize(bars.slice(32, 32 + 4), 7), - normalize(bars.slice(36, 36 + 4), 7), - normalize(bars.slice(40, 40 + 4), 7), - normalize(bars.slice(44, 44 + 4), 7), - normalize(bars.slice(48, 48 + 4), 7), - normalize(bars.slice(52, 52 + 4), 7) - ] - - // determine parity and reverse if necessary - const parities = []; - - for (let i = 0; i < 6; i++) { - if (parity(digits[i])) { - parities.push('o'); - } else { - parities.push('e'); - digits[i] = digits[i].reverse(); - } - } - - // identify digits - const result = []; - let quality = 0; - - for (let i = 0; i < digits.length; i++) { - - let distance = 9; - let bestKey = ''; - - for (let key in upc) { - if (maxDistance(digits[i], upc[key]) < distance) { - distance = maxDistance(digits[i], upc[key]); - bestKey = key; - } - } - - result.push(bestKey); - if (distance > quality) { - quality = distance; - } - - } - // check digit - const checkDigit = check[parities.join('')]; - - // output - if(quality < config.quality) { - const barcode = checkDigit + result.join('') - setDecodedBarcode(barcode) - } - - } - - const normalize = (input, total) => { - const sum = input.reduce((acc, val) => acc + val, 0); - return input.reduce((acc, val) => [...acc, val / sum * total], []); - } - - const isOdd = (num) => num % 2; - - const maxDistance = (a, b) => - a.reduce((max, value, index) => { - const current = Math.abs(value - b[index]) - return current > max ? current : max - }, 0) - - const parity = (digit) => isOdd(Math.round(digit[1] + digit[3])) - - const drawLine = (canvasContext) => { - canvasContext.strokeStyle = config.strokeColor; - canvasContext.lineWidth = 3; - canvasContext.beginPath(); - canvasContext.moveTo(dimensions.start, dimensions.height * 0.5); - canvasContext.lineTo(dimensions.end, dimensions.height * 0.5); - canvasContext.stroke(); - } - - useEffect(() => { - init() - }, []) - return ( -
-
- - {decodecBarcode === "5449000136350"} - {decodecBarcode} + + + + {isScannerOpen ? ( + + ) : null}
); } - export default BarcodeScanner; diff --git a/src/containers/BarcodeScanner/styles.css b/src/containers/BarcodeScanner/styles.css deleted file mode 100644 index cbf60f7..0000000 --- a/src/containers/BarcodeScanner/styles.css +++ /dev/null @@ -1,22 +0,0 @@ -#barcodevideo, #barcodecanvas, #barcodecanvasg { - height: 400px; -} - -#barcodecanvasg { - position: absolute; - top: 0px; - left: 0px; -} - -#result { - font-family: verdana; - font-size: 1.5em; -} - -#barcode { - position: relative; -} - -#barcodecanvas { - display: none; -} diff --git a/src/containers/BarcodeScanner/styles.js b/src/containers/BarcodeScanner/styles.js new file mode 100644 index 0000000..f17fadb --- /dev/null +++ b/src/containers/BarcodeScanner/styles.js @@ -0,0 +1,37 @@ +import {makeStyles} from "@material-ui/core/styles"; + +const useStyles = makeStyles((theme) => ({ + container: { + zIndex: theme.zIndex.modal, + width: `100%`, + height: `100%`, + position: `absolute`, + left: 0, + top: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + inner: { + position: `relative`, + }, + close: { + position: `absolute`, + top: 20, + right: 20, + }, + video: { + height: 400, + }, + canvas: { + display: "none", + }, + canvasg: { + position: "absolute", + top: 0, + left: 0, + height: 400, + }, +})) + +export default useStyles diff --git a/src/pages/Login/index.js b/src/pages/Login/index.js index a332150..1d45425 100644 --- a/src/pages/Login/index.js +++ b/src/pages/Login/index.js @@ -1,108 +1,100 @@ -// import React from 'react'; -// import { useInjectReducer, useInjectSaga } from 'redux-injectors'; -// import { useDispatch } from 'react-redux'; -// import { -// Container, -// Grid, -// Button, -// Typography, -// FormControlLabel, -// Checkbox, -// Link, -// } from '@material-ui/core' -// import {routes} from "utils"; -// import InputField from 'components/InputField' -// import { Formik, Form } from 'formik'; -// import { loginAction } from './actions' -// import useStyles from './styles' -// import validationSchema from './FormModel/validationSchema' -// import formInitialValues from './FormModel/formInitialValues' -// import loginFormModel from './FormModel/loginFormModel' -// import saga from "./saga"; -// -// const { -// formId, -// formField: { -// email, -// password -// } -// } = loginFormModel; -// -// const key = 'loginPage' -// -// const Login = () => { -// useInjectSaga({ key, saga }); -// const classes = useStyles() -// const dispatch = useDispatch(); -// -// const handleSubmit = (values, actions) => { -// dispatch(loginAction(values)) -// } -// -// return ( -// -//
-// -// Login to Account -// -// -// {({ isSubmitting }) => ( -//
-// -// -// } -// label="Remember me" -// /> -// -// -// )} -//
-// -// -// -// Don't have an account? Sign Up -// -// -// -//
-//
-// ); -// }; -// -// export default Login; +import React from 'react'; +import { useInjectReducer, useInjectSaga } from 'redux-injectors'; +import { useDispatch } from 'react-redux'; +import { + Container, + Grid, + Button, + Typography, + FormControlLabel, + Checkbox, + Link, +} from '@material-ui/core' +import {routes} from "utils"; +import InputField from 'components/InputField' +import { Formik, Form } from 'formik'; +import { loginAction } from './actions' +import useStyles from './styles' +import validationSchema from './FormModel/validationSchema' +import formInitialValues from './FormModel/formInitialValues' +import loginFormModel from './FormModel/loginFormModel' +import saga from "./saga"; -import BarcodeScanner from "../../containers/BarcodeScanner"; -import {Container} from "@material-ui/core"; -import React from "react"; +const { + formId, + formField: { + email, + password + } +} = loginFormModel; -export default () => ( - -) +const key = 'loginPage' + +const Login = () => { + useInjectSaga({ key, saga }); + const classes = useStyles() + const dispatch = useDispatch(); + + const handleSubmit = (values, actions) => { + dispatch(loginAction(values)) + } + + return ( + +
+ + Login to Account + + + {({ isSubmitting }) => ( +
+ + + } + label="Remember me" + /> + + + )} +
+ + + + Don't have an account? Sign Up + + + +
+
+ ); +}; + +export default Login;