From b043eb3a71b0d450fb296ac3c999b3ace84a986f Mon Sep 17 00:00:00 2001 From: Himanshu Gupta Date: Sun, 3 Mar 2024 13:36:47 +0530 Subject: [PATCH] fix(scanner): added scanner component --- .eslintrc.json | 6 +- README.md | 2 +- __app/component/Bluetooth/Bluetooth.js | 7 + __app/component/Clipboard/Clipboard.js | 7 + __app/component/PhoneBook/PhoneBook.js | 35 ++- __app/component/Scanner/Scanner.js | 302 ++++++++++++++++++++++++- __app/component/Share/Share.js | 43 ++-- __app/component/utils/utils.js | 16 ++ package.json | 1 + 9 files changed, 386 insertions(+), 33 deletions(-) create mode 100644 __app/component/Bluetooth/Bluetooth.js create mode 100644 __app/component/Clipboard/Clipboard.js create mode 100644 __app/component/utils/utils.js diff --git a/.eslintrc.json b/.eslintrc.json index 53bb9db..006574a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,7 +5,8 @@ "node": true }, "globals": { - "APP_ENV": true + "APP_ENV": true, + "globalThis": true }, "rules": { "import/no-extraneous-dependencies": ["off"], // Need to use external dep like webpack-dashboard @@ -14,7 +15,8 @@ "no-underscore-dangle": ["off"], "react/prop-types": ["off"], "jsx-a11y/control-has-associated-label": ["off"], - "react/forbid-prop-types": ["off"] + "react/forbid-prop-types": ["off"], + "object-curly-newline": ["off"] }, "parserOptions": { "ecmaVersion":"latest" diff --git a/README.md b/README.md index 0a7c231..9567e2e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ A React UI library for Advance Web Features that streamlines the development of ## Features - **Web/Mobile Support:** fe-pilot will be helpfull in Large/Mid/Small size of web based applications. -- **Ease of Use:** fe-pilot contains a set of components like `Scanner`, `PhoneBook`, `Voice Search`, `Share`, `Contact List`, `Live Location Tracking`, `Current Location` that are easy to plug and play. +- **Ease of Use:** fe-pilot contains a set of components like `Scanner`, `PhoneBook`, `Voice Search`, `Share`, `Live Location Tracking`, `Current Location` that are easy to plug and play. - **Scalable & Maintainable:** fe-pilot components are built on top of a React for better scalable, maintainable component. ## Installation diff --git a/__app/component/Bluetooth/Bluetooth.js b/__app/component/Bluetooth/Bluetooth.js new file mode 100644 index 0000000..a3b9809 --- /dev/null +++ b/__app/component/Bluetooth/Bluetooth.js @@ -0,0 +1,7 @@ +import React from 'react'; + +export default function bluetooth() { + return ( +
bluetooth
+ ); +} diff --git a/__app/component/Clipboard/Clipboard.js b/__app/component/Clipboard/Clipboard.js new file mode 100644 index 0000000..cfe57d1 --- /dev/null +++ b/__app/component/Clipboard/Clipboard.js @@ -0,0 +1,7 @@ +import React from 'react'; + +export default function Clipboard() { + return ( +
Clipboard
+ ); +} diff --git a/__app/component/PhoneBook/PhoneBook.js b/__app/component/PhoneBook/PhoneBook.js index ce686ff..b6e895c 100644 --- a/__app/component/PhoneBook/PhoneBook.js +++ b/__app/component/PhoneBook/PhoneBook.js @@ -3,6 +3,25 @@ import PropTypes from 'prop-types'; const isPhoneBookAPISupport = () => navigator.contacts && window.ContactsManager; +const handleError = ({ disbaleToast, msg, msgType, failureCb }) => { + console.log(msgType); + if (!disbaleToast && msg) console.log(msg); + failureCb({ + msgType, + msg, + }); +}; + +const handleSuccess = ({ disbaleToast, msg, msgType, data, successCb }) => { + console.log(msgType); + if (!disbaleToast && msg) console.log('Success:', msg); + successCb({ + msgType, + msg, + data, + }); +}; + function PhoneBook({ disbaleToast, successCb, @@ -21,15 +40,12 @@ function PhoneBook({ if (isPhoneBookAPISupport()) { try { const contacts = await navigator.contacts.select(contactProperty, opts); - if (!disbaleToast && successMsg) console.log('Success:', successMsg); - successCb({ msgType: 'SUCCESS', msg: successMsg, data: contacts }); + handleSuccess({ disbaleToast, msgType: 'SUCCESS', msg: successMsg, successCb, data: contacts }); } catch (error) { - if (!disbaleToast && failureMsg.generalMsg) console.log(failureMsg.generalMsg || error); - failureCb({ msgType: 'ERROR', msg: failureMsg.generalMsg || error }); + handleError({ disbaleToast, msgType: 'ERROR', msg: failureMsg.error || error, failureCb }); } } else { - if (!disbaleToast && failureMsg.unSupportedMsg) console.log(failureMsg.unSupportedMsg); - failureCb({ msgType: 'UN_SUPPORTED_FEATURE', msg: failureMsg.unSupportedMsg }); + handleError({ disbaleToast, msgType: 'UN_SUPPORTED_FEATURE', msg: failureMsg.unSupported, failureCb }); } }; @@ -61,11 +77,10 @@ PhoneBook.defaultProps = { failureCb: () => {}, successMsg: '', failureMsg: { - unSupportedMsg: '', - badRequestMsg: '', - generalMsg: '', + unSupported: '', + error: '', }, - showForever: false, + showForever: true, contactProperty: ['name', 'email', 'tel', 'address', 'icon'], isSelectMultiple: false, }; diff --git a/__app/component/Scanner/Scanner.js b/__app/component/Scanner/Scanner.js index a35be6f..bd3db0a 100644 --- a/__app/component/Scanner/Scanner.js +++ b/__app/component/Scanner/Scanner.js @@ -1,7 +1,299 @@ -import React from 'react' +/* eslint-disable no-inner-declarations */ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { browserDimensions } from '../utils/utils'; -export default function Scanner() { - return ( -
Scanner
- ) +let mediaStream = null; +let videoUnmount = null; +let unmoutRenderLoop = null; + +const isScannerSupport = () => navigator && navigator.mediaDevices; + +const handleError = ({ disbaleToast, msg, msgType, failureCb }) => { + console.log(msgType); + if (!disbaleToast && msg) console.log(msg); + failureCb({ + msgType, + msg, + }); +}; + +const handleSuccess = ({ disbaleToast, msg, msgType, data, successCb }) => { + console.log(msgType); + if (!disbaleToast && msg) console.log('Success:', msg); + successCb({ + msgType, + msg, + data, + }); +}; + +function Scanner({ + disbaleToast, + successCb, + failureCb, + onClose, + successMsg, + failureMsg, + + cameraType, + zIndex, + bgColor, + // scanBoxTop, + // scanBoxBottom, + // scanBoxBorderWidth, + // scanBoxBorderColor, +}) { + let list = null; + let video = null; + let facingMode; + + const [flash, setFlash] = useState(false); + const [appCss, setAppCss] = useState({}); + const [isBrowser, setIsBrowser] = useState(false); + + const stopStreaming = () => { + if (mediaStream) { + mediaStream.getTracks().forEach((track) => { + track.stop(); + }); + } + }; + + const detectCodes = async () => { + if ('BarcodeDetector' in window) { + const WindowBarcodeDetector = window.BarcodeDetector; + const barcodeDetector = new WindowBarcodeDetector(); + const itemsFound = []; + + function render() { + // eslint-disable-next-line no-unused-expressions + barcodeDetector.detect ? ( + barcodeDetector + .detect(video) + .then((barcodes) => { + barcodes.forEach((barcode) => { + if (!itemsFound.includes(barcode.rawValue)) { + itemsFound.push(barcode.rawValue); + handleSuccess({ + disbaleToast, + msgType: 'SUCCESS', + msg: successMsg, + successCb, + data: { barCodeValue: barcode.rawValue, barCodeType: barcode.format }, + }); + } + }); + }) + .catch((error) => { + handleError({ disbaleToast, msgType: 'BAR_CODE_DETECTION', msg: failureMsg.barCodeDetection || error, failureCb }); + }) + ) : null; + } + + unmoutRenderLoop = setTimeout(() => { + (function renderLoop() { + videoUnmount = requestAnimationFrame(renderLoop); + render(); + }()); + }, 1000); + } else { + handleError({ disbaleToast, msgType: 'UN_SUPPORTED_FEATURE', msg: failureMsg.unSupported, failureCb }); + } + }; + + const createVideo = async (id) => { + video = document.createElement('video'); + video.id = 'streaming-video'; + video.srcObject = mediaStream; + video.autoplay = true; + video.play(); + video.style.width = '100%'; + video.style.height = '100%'; + video.style.position = 'absolute'; + video.style.overflow = 'hidden'; + video.style.display = 'block'; + video.style.zIndex = zIndex; + video.style.top = '0'; + video.style.left = '0'; + video.style.objectFit = 'fill'; + list = document.getElementById(id); + list.before(video); + }; + + const startStreaming = async () => { + try { + mediaStream = await navigator.mediaDevices.getUserMedia({ + video: { + // deviceId: camera.deviceId, + facingMode, + zoom: true, + resizeMode: true, + focusDistance: true, + focusMode: true, + }, + }); + } catch (error) { + handleError({ disbaleToast, msgType: 'STREAMING_ERROR', msg: failureMsg.streaming || error, failureCb }); + } + return mediaStream; + }; + + const startVideo = async (id = 'camera') => { + mediaStream = await startStreaming(); + createVideo(id); + detectCodes(); + }; + + const toggleCamera = () => { + facingMode = facingMode === 'user' ? 'environment' : 'user'; + + stopStreaming(); + cancelAnimationFrame(videoUnmount); + clearTimeout(unmoutRenderLoop); + startVideo(); + }; + + const toggleFlash = async () => { + const track = mediaStream.getVideoTracks()[0]; + try { + await track.applyConstraints({ + advanced: [{ torch: !flash }], + }); + setFlash((s) => !s); + } catch (error) { + handleError({ disbaleToast, msgType: 'FLASH', msg: failureMsg.flash, failureCb }); + } + }; + + const allClear = () => { + console.log('unmount'); + cancelAnimationFrame(videoUnmount); + stopStreaming(); + clearTimeout(unmoutRenderLoop); + }; + + useEffect(() => { + const screen = browserDimensions(); + const borderTop = `${((screen.height - screen.width) / 2)}px solid rgba(0, 0, 0, 0.3)`; + const borderBottom = `${((screen.height - screen.width) / 2) + 50}px solid rgba(0, 0, 0, 0.3)`; + + setIsBrowser(true); + setAppCss({ borderTop, borderBottom, width: screen.width, height: screen.height }); + + if (isScannerSupport()) { + facingMode = cameraType === 'back' ? 'environment' : 'user'; + startVideo(); + } else { + handleError({ disbaleToast, msgType: 'UN_SUPPORTED_FEATURE', msg: failureMsg.unSupported, failureCb }); + } + + return () => { + allClear(); + }; + }, []); + + return isBrowser && isScannerSupport() ? ( +
+
+ ) : null; } + +Scanner.propTypes = { + disbaleToast: PropTypes.bool, + successCb: PropTypes.func, + failureCb: PropTypes.func, + onClose: PropTypes.func, + successMsg: PropTypes.string, + failureMsg: PropTypes.object, + + zIndex: PropTypes.number, + cameraType: PropTypes.oneOf(['back', 'front']), +}; + +Scanner.defaultProps = { + disbaleToast: false, + successCb: () => {}, + failureCb: () => {}, + onClose: () => {}, + successMsg: '', + failureMsg: { + unSupported: '', + streaming: '', + barCodeDetection: '', + flash: '', + error: '', + }, + + zIndex: 9, + cameraType: 'back', +}; + +export default Scanner; diff --git a/__app/component/Share/Share.js b/__app/component/Share/Share.js index 40b867d..2c64683 100644 --- a/__app/component/Share/Share.js +++ b/__app/component/Share/Share.js @@ -1,7 +1,9 @@ +/* eslint-disable */ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; const isShareAPISupport = () => navigator.share; + const isShareAPIDataValid = (sharingData) => { if (navigator.canShare) { return navigator.canShare(sharingData); @@ -10,6 +12,24 @@ const isShareAPIDataValid = (sharingData) => { return true; }; +const handleError = ({ disbaleToast, msg, msgType, failureCb }) => { + console.log(msgType); + if (!disbaleToast && msg) console.log(msg); + failureCb({ + msgType, + msg, + }); +}; + +const handleSuccess = ({ disbaleToast, msg, msgType, successCb }) => { + console.log(msgType); + if (!disbaleToast && msg) console.log('Success:', msg); + successCb({ + msgType, + msg, + }); +}; + function Share({ disbaleToast, successCb, @@ -23,28 +43,21 @@ function Share({ sUrl, }) { const [isBrowser, setIsBrowser] = useState(false); - const sharingData = { title: sName, textasxzdsc: sTitle, url: sUrl }; + const sharingData = { title: sName, text: sTitle, url: sUrl }; const showDropdown = () => { if (isShareAPISupport()) { if (isShareAPIDataValid(sharingData)) { navigator.share(sharingData).then(() => { - if (!disbaleToast && successMsg) console.log('Success:', successMsg); - successCb({ msgType: 'SUCCESS', msg: successMsg }); + handleSuccess({ disbaleToast, msgType: 'SUCCESS', msg: successMsg, successCb }); }).catch((error) => { - console.log(error); - if (!disbaleToast && failureMsg.generalMsg) console.log(failureMsg.generalMsg || error); - failureCb({ msgType: 'ERROR', msg: failureMsg.generalMsg || error }); + handleError({ disbaleToast, msgType: 'ERROR', msg: failureMsg.error || error, failureCb }); }); } else { - console.log('BAD_REQUEST'); - if (!disbaleToast && failureMsg.badRequestMsg) console.log(failureMsg.badRequestMsg); - failureCb({ msgType: 'BAD_REQUEST', msg: failureMsg.badRequestMsg }); + handleError({ disbaleToast, msgType: 'BAD_REQUEST', msg: failureMsg.badRequest, failureCb }); } } else { - console.log('UN_SUPPORTED_FEATURE'); - if (!disbaleToast && failureMsg.unSupportedMsg) console.log(failureMsg.unSupportedMsg); - failureCb({ msgType: 'UN_SUPPORTED_FEATURE', msg: failureMsg.unSupportedMsg }); + handleError({ disbaleToast, msgType: 'UN_SUPPORTED_FEATURE', msg: failureMsg.unSupported, failureCb }); } }; @@ -77,9 +90,9 @@ Share.defaultProps = { failureCb: () => {}, successMsg: '', failureMsg: { - unSupportedMsg: '', - badRequestMsg: '', - generalMsg: '', + unSupported: '', + badRequest: '', + error: '', }, showForever: true, sName: 'fe-pilot', diff --git a/__app/component/utils/utils.js b/__app/component/utils/utils.js new file mode 100644 index 0000000..6953e78 --- /dev/null +++ b/__app/component/utils/utils.js @@ -0,0 +1,16 @@ +export const browserDimensions = () => ({ + width: globalThis.document && ( + window.innerWidth + || document.documentElement.clientWidth + || document.body.clientWidth + ), + height: globalThis.document && ( + window.innerHeight + || document.documentElement.clientHeight + || document.body.clientHeight + ), +}); + +export const saveLocal = () => { + +}; diff --git a/package.json b/package.json index a03926b..39f533e 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "", "scripts": { "test": "echo \"Success: Verified\"", + "start": "npm run local", "local": "babel __app/component -d __build --watch", "build": "npm run build:component && npm run build:indexfile", "build:component": "babel __app/component -d ./__build",