|
@@ -1,8 +1,12 @@ |
|
|
import { Button, Card, CardContent, Grid, Modal, ModalProps, Stack, SxProps, Typography } from "@mui/material"; |
|
|
|
|
|
import { Html5Qrcode, Html5QrcodeCameraScanConfig, Html5QrcodeFullConfig, Html5QrcodeResult, Html5QrcodeScanner, QrcodeErrorCallback, QrcodeSuccessCallback } from "html5-qrcode"; |
|
|
|
|
|
|
|
|
import { Autocomplete, Box, Button, Card, CardContent, Grid, Modal, ModalProps, Stack, SxProps, TextField, Typography } from "@mui/material"; |
|
|
|
|
|
import { CameraDevice, Html5Qrcode, Html5QrcodeCameraScanConfig, Html5QrcodeFullConfig, Html5QrcodeResult, Html5QrcodeScanner, Html5QrcodeScannerState, QrcodeErrorCallback, QrcodeSuccessCallback } from "html5-qrcode"; |
|
|
import { Html5QrcodeError } from "html5-qrcode/esm/core"; |
|
|
import { Html5QrcodeError } from "html5-qrcode/esm/core"; |
|
|
import { Html5QrcodeScannerConfig } from "html5-qrcode/esm/html5-qrcode-scanner"; |
|
|
import { Html5QrcodeScannerConfig } from "html5-qrcode/esm/html5-qrcode-scanner"; |
|
|
import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react"; |
|
|
import React, { RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react"; |
|
|
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
|
|
import QrCodeScannerIcon from '@mui/icons-material/QrCodeScanner'; |
|
|
|
|
|
import StopCircleOutlinedIcon from '@mui/icons-material/StopCircleOutlined'; |
|
|
|
|
|
import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined'; |
|
|
|
|
|
|
|
|
const scannerSx: React.CSSProperties = { |
|
|
const scannerSx: React.CSSProperties = { |
|
|
position: "absolute", |
|
|
position: "absolute", |
|
@@ -15,28 +19,36 @@ const scannerSx: React.CSSProperties = { |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
type QrCodeScannerProps = { |
|
|
type QrCodeScannerProps = { |
|
|
|
|
|
title?: string, |
|
|
onScanSuccess: (result: string) => void, |
|
|
onScanSuccess: (result: string) => void, |
|
|
onScanError?: (error: string) => void, |
|
|
onScanError?: (error: string) => void, |
|
|
isOpen: boolean |
|
|
|
|
|
|
|
|
isOpen: boolean, |
|
|
|
|
|
onClose: () => void |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ |
|
|
const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ |
|
|
|
|
|
title, |
|
|
onScanSuccess, |
|
|
onScanSuccess, |
|
|
onScanError, |
|
|
onScanError, |
|
|
isOpen |
|
|
|
|
|
|
|
|
isOpen, |
|
|
|
|
|
onClose |
|
|
}) => { |
|
|
}) => { |
|
|
|
|
|
|
|
|
|
|
|
const { t } = useTranslation() |
|
|
const [isScanned, setIsScanned] = useState<boolean>(false) |
|
|
const [isScanned, setIsScanned] = useState<boolean>(false) |
|
|
const [scanner, setScanner] = useState<Html5Qrcode | null>(null) |
|
|
const [scanner, setScanner] = useState<Html5Qrcode | null>(null) |
|
|
|
|
|
const [cameraList, setCameraList] = useState<CameraDevice[]>([]) |
|
|
|
|
|
const [selectedCameraId, setSelectedCameraId] = useState<string | null>(null) |
|
|
|
|
|
const stringList = ["ABC: abc", "123:123", "ABC: abc", "123:123", "ABC: abc", "123:123"] |
|
|
|
|
|
|
|
|
const scannerConfig: Html5QrcodeFullConfig = { |
|
|
const scannerConfig: Html5QrcodeFullConfig = { |
|
|
verbose: false |
|
|
verbose: false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const cameraConfig: Html5QrcodeCameraScanConfig = { |
|
|
const cameraConfig: Html5QrcodeCameraScanConfig = { |
|
|
fps: 10, |
|
|
fps: 10, |
|
|
qrbox: { width: 400, height: 400 }, |
|
|
|
|
|
|
|
|
qrbox: { width: 250, height: 250 }, |
|
|
// aspectRatio: cardRef.current ? (cardRef.current.offsetWidth / cardRef.current.offsetHeight) : 1.78, |
|
|
// aspectRatio: cardRef.current ? (cardRef.current.offsetWidth / cardRef.current.offsetHeight) : 1.78, |
|
|
aspectRatio: 2.5 |
|
|
|
|
|
|
|
|
aspectRatio: (window.innerWidth / window.innerHeight) * 1.5 // can be better |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// MediaTrackConstraintSet |
|
|
// MediaTrackConstraintSet |
|
@@ -44,26 +56,56 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ |
|
|
facingMode: "environment" |
|
|
facingMode: "environment" |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
setScanner(new Html5Qrcode( |
|
|
|
|
|
"qr-reader", |
|
|
|
|
|
scannerConfig |
|
|
|
|
|
)) |
|
|
|
|
|
|
|
|
const handleScanStart = useCallback(() => { |
|
|
|
|
|
if (scanner && selectedCameraId) { |
|
|
|
|
|
if (scanner.getState() === Html5QrcodeScannerState.SCANNING) { |
|
|
|
|
|
console.log("first") |
|
|
|
|
|
scanner.stop() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
scanner.start( |
|
|
|
|
|
selectedCameraId, |
|
|
|
|
|
cameraConfig, |
|
|
|
|
|
handleScanSuccess, |
|
|
|
|
|
handleScanError |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
}, [selectedCameraId, scanner]) |
|
|
|
|
|
|
|
|
|
|
|
const handleCameraList = useCallback(async () => { |
|
|
|
|
|
const cameras = await Html5Qrcode.getCameras() |
|
|
|
|
|
setCameraList(cameras) |
|
|
|
|
|
if (cameras.length > 0) { |
|
|
|
|
|
handleCameraChange(cameras[0].id) |
|
|
|
|
|
} |
|
|
|
|
|
}, []) |
|
|
|
|
|
|
|
|
|
|
|
const handleCameraChange = useCallback((id: string) => { |
|
|
|
|
|
setSelectedCameraId(id) |
|
|
}, []) |
|
|
}, []) |
|
|
|
|
|
|
|
|
const handleStartScan = useCallback(() => { |
|
|
|
|
|
|
|
|
const switchScanStatus = useCallback(() => { |
|
|
if (scanner) { |
|
|
if (scanner) { |
|
|
setIsScanned(false) |
|
|
|
|
|
scanner.resume(); |
|
|
|
|
|
|
|
|
console.log(isScanned) |
|
|
|
|
|
switch (isScanned) { |
|
|
|
|
|
case true: |
|
|
|
|
|
setIsScanned(false); |
|
|
|
|
|
scanner.resume(); |
|
|
|
|
|
break; |
|
|
|
|
|
case false: |
|
|
|
|
|
setIsScanned(true); |
|
|
|
|
|
scanner.pause(true); |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
}, [scanner]) |
|
|
|
|
|
|
|
|
}, [scanner, isScanned]) |
|
|
|
|
|
|
|
|
const handleScanSuccess = useCallback<QrcodeSuccessCallback>((decodedText, result) => { |
|
|
const handleScanSuccess = useCallback<QrcodeSuccessCallback>((decodedText, result) => { |
|
|
if (scanner) { |
|
|
if (scanner) { |
|
|
console.log(`Decoded text: ${decodedText}`); |
|
|
console.log(`Decoded text: ${decodedText}`); |
|
|
// Handle the decoded text as needed |
|
|
// Handle the decoded text as needed |
|
|
setIsScanned(true) |
|
|
|
|
|
scanner.pause(); |
|
|
|
|
|
|
|
|
switchScanStatus() |
|
|
onScanSuccess(decodedText) |
|
|
onScanSuccess(decodedText) |
|
|
} |
|
|
} |
|
|
}, [scanner, onScanSuccess]) |
|
|
}, [scanner, onScanSuccess]) |
|
@@ -76,55 +118,99 @@ const QrCodeScanner: React.FC<QrCodeScannerProps> = ({ |
|
|
} |
|
|
} |
|
|
}, [scanner, onScanError]) |
|
|
}, [scanner, onScanError]) |
|
|
|
|
|
|
|
|
const handleScanClose = useCallback(async () => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleScanCloseButton = useCallback(async () => { |
|
|
if (scanner) { |
|
|
if (scanner) { |
|
|
console.log("Cleaning up scanner..."); |
|
|
console.log("Cleaning up scanner..."); |
|
|
await scanner.stop() |
|
|
await scanner.stop() |
|
|
await scanner.clear() |
|
|
await scanner.clear() |
|
|
|
|
|
onClose() |
|
|
} |
|
|
} |
|
|
}, [scanner]) |
|
|
}, [scanner]) |
|
|
|
|
|
|
|
|
|
|
|
// close modal without using Cancel Button |
|
|
|
|
|
const handleScanClose = useCallback(async () => { |
|
|
|
|
|
if (scanner && !isOpen) { |
|
|
|
|
|
handleScanCloseButton() |
|
|
|
|
|
} |
|
|
|
|
|
}, [scanner, isOpen, handleScanCloseButton]) |
|
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------// |
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
if (scanner) { |
|
|
|
|
|
console.log("Scanner Instance:", scanner); |
|
|
|
|
|
|
|
|
setScanner(new Html5Qrcode( |
|
|
|
|
|
"qr-reader", |
|
|
|
|
|
scannerConfig |
|
|
|
|
|
)) |
|
|
|
|
|
|
|
|
scanner.start( |
|
|
|
|
|
mediaTrackConstraintSet, |
|
|
|
|
|
cameraConfig, |
|
|
|
|
|
handleScanSuccess, |
|
|
|
|
|
handleScanError |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
handleCameraList() |
|
|
|
|
|
}, []) |
|
|
|
|
|
|
|
|
return () => { |
|
|
|
|
|
handleScanClose() |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
}, [scanner]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
handleScanStart() |
|
|
|
|
|
}, [scanner, selectedCameraId]); |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
handleScanClose() |
|
|
|
|
|
}, [isOpen]) |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<> |
|
|
<> |
|
|
<Stack spacing={2}> |
|
|
<Stack spacing={2}> |
|
|
<Typography variant="overline" display="block" marginBlockEnd={1} paddingLeft={2}> |
|
|
|
|
|
|
|
|
{title ? <Typography variant="overline" display="block" marginBlockEnd={1} paddingLeft={2}> |
|
|
{"Title"} |
|
|
{"Title"} |
|
|
</Typography> |
|
|
|
|
|
<Grid container columns={{ xs: 6, sm: 12 }}> |
|
|
|
|
|
<Grid item xs={12} justifyContent={"center"}> |
|
|
|
|
|
<div style={{textAlign: "center", margin: "auto", justifyContent: "center"}} id="qr-reader" hidden={isScanned} /> |
|
|
|
|
|
|
|
|
</Typography> : null} |
|
|
|
|
|
<Grid container alignItems="center" rowSpacing={2} columns={{ xs: 6, sm: 12 }}> |
|
|
|
|
|
<Grid item xs={12}> |
|
|
|
|
|
<div style={{ textAlign: "center", margin: "auto", justifyContent: "center" }} id="qr-reader" hidden={isScanned} /> |
|
|
</Grid> |
|
|
</Grid> |
|
|
|
|
|
{cameraList.length > 0 ? <Grid item xs={6} > |
|
|
|
|
|
<Autocomplete |
|
|
|
|
|
disableClearable |
|
|
|
|
|
disableCloseOnSelect |
|
|
|
|
|
noOptionsText={t("No Options")} |
|
|
|
|
|
options={cameraList} |
|
|
|
|
|
value={cameraList.find((camera) => camera.id === selectedCameraId)} |
|
|
|
|
|
onChange={((event, value) => { |
|
|
|
|
|
setSelectedCameraId(value.id) |
|
|
|
|
|
})} |
|
|
|
|
|
renderInput={(params) => ( |
|
|
|
|
|
<TextField |
|
|
|
|
|
{...params} |
|
|
|
|
|
variant="outlined" |
|
|
|
|
|
label={t("Selected Camera")} |
|
|
|
|
|
/> |
|
|
|
|
|
)} |
|
|
|
|
|
/> |
|
|
|
|
|
</Grid> : null} |
|
|
|
|
|
{ |
|
|
|
|
|
stringList.map((string) => { |
|
|
|
|
|
return <Grid item xs={7}>{string}</Grid> |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
</Grid> |
|
|
</Grid> |
|
|
<Stack direction="row" justifyContent={"flex-end"} spacing={2} sx={{ margin: 2 }}> |
|
|
<Stack direction="row" justifyContent={"flex-end"} spacing={2} sx={{ margin: 2 }}> |
|
|
<Button |
|
|
<Button |
|
|
size="small" |
|
|
size="small" |
|
|
onClick={handleStartScan} |
|
|
|
|
|
|
|
|
onClick={switchScanStatus} |
|
|
|
|
|
variant="contained" |
|
|
|
|
|
sx={{ margin: 2 }} |
|
|
|
|
|
startIcon={isScanned ? <QrCodeScannerIcon /> : <StopCircleOutlinedIcon />} |
|
|
|
|
|
> |
|
|
|
|
|
{isScanned ? t("Start Scanning") : t("Stop Scanning")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
<Button |
|
|
|
|
|
size="small" |
|
|
|
|
|
onClick={handleScanCloseButton} |
|
|
variant="contained" |
|
|
variant="contained" |
|
|
|
|
|
color="error" |
|
|
sx={{ margin: 2 }} |
|
|
sx={{ margin: 2 }} |
|
|
|
|
|
startIcon={<CloseOutlinedIcon />} |
|
|
> |
|
|
> |
|
|
{isScanned ? "Re-Scan" : "Stop-Scanning"} |
|
|
|
|
|
|
|
|
{t("Cancel")} |
|
|
</Button> |
|
|
</Button> |
|
|
</Stack> |
|
|
</Stack> |
|
|
</Stack> |
|
|
</Stack> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</> |
|
|
</> |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|