From 3eda3580f60fec182569be9d261eba54f134a2ba Mon Sep 17 00:00:00 2001 From: Donkie Date: Thu, 3 Aug 2023 21:51:49 +0200 Subject: [PATCH] Client: Added simple qr code scanner --- client/package.json | 1 + client/src/components/header/index.tsx | 2 + client/src/components/qrCodeScanner.tsx | 114 ++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 client/src/components/qrCodeScanner.tsx diff --git a/client/package.json b/client/package.json index 6b8800e..a3e6c72 100644 --- a/client/package.json +++ b/client/package.json @@ -21,6 +21,7 @@ "i18next": "^23.2.8", "i18next-browser-languagedetector": "^7.1.0", "i18next-xhr-backend": "^3.2.2", + "qr-scanner": "^1.4.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^13.0.1", diff --git a/client/src/components/header/index.tsx b/client/src/components/header/index.tsx index e57c734..eacdd25 100644 --- a/client/src/components/header/index.tsx +++ b/client/src/components/header/index.tsx @@ -15,6 +15,7 @@ import { ColorModeContext } from "../../contexts/color-mode"; import "/node_modules/flag-icons/css/flag-icons.min.css"; import { languages } from "../../i18n"; +import QRCodeScannerModal from "../qrCodeScanner"; const { useToken } = theme; @@ -84,6 +85,7 @@ export const Header: React.FC = ({ onChange={() => setMode(mode === "light" ? "dark" : "light")} defaultChecked={mode === "dark"} /> + ); diff --git a/client/src/components/qrCodeScanner.tsx b/client/src/components/qrCodeScanner.tsx new file mode 100644 index 0000000..74f0265 --- /dev/null +++ b/client/src/components/qrCodeScanner.tsx @@ -0,0 +1,114 @@ +import React, { useEffect, useRef, useState } from "react"; +import { Button, FloatButton, Modal, Space } from "antd"; +import QrScanner from "qr-scanner"; // Make sure to import from the correct path +import { useNavigate } from "react-router-dom"; +import { CameraOutlined } from "@ant-design/icons"; + +const QRCodeScannerModal: React.FC = () => { + const [visible, setVisible] = useState(false); + const videoRef = useRef(null); + const scanner = useRef(null); + const navigate = useNavigate(); + const [hasCamera, setHasCamera] = useState(false); + + useEffect(() => { + const onScan = (result: string) => { + // Check for the spoolman ID format + const match = result.match(/^web+spoolman:s-(?[0-9]+)$/); + if (match && match.groups) { + navigate(`/spool/show/${match.groups.id}`); + } + }; + + const startCamera = async () => { + console.log("Starting camera"); + if (!(await QrScanner.hasCamera())) { + console.log("No camera found on this device"); + setHasCamera(false); + return; + } else { + console.log("Camera found on this device"); + setHasCamera(true); + if (videoRef.current) { + scanner.current = new QrScanner( + videoRef.current, + (result) => { + console.log("QR Code detected:", result); + onScan(result.data); + }, + { + preferredCamera: "environment", + } + ); + console.log("Starting scanner"); + try { + await scanner.current.start(); + } catch (e) { + setHasCamera(false); + console.log(e); + } + } else { + console.log("No video ref"); + } + } + }; + if (visible) { + startCamera(); + } + + return () => { + console.log("Destroying scanner"); + scanner.current?.destroy(); + scanner.current = null; + }; + }, [visible, navigate]); + + // Draw a centered div if no camera was found + + return ( + <> + setVisible(true)} + icon={} + shape="circle" + /> + setVisible(false)} + footer={null} + title="QR Code Scanner" + > + +

Scan a Spoolman QR code to view details about the spool.

+
+
+ + ); +}; + +export default QRCodeScannerModal;