mirror of
https://github.com/Donkie/Spoolman.git
synced 2026-10-23 02:22:20 -05:00
Widen the code scanner's accepted formats beyond qr_code to the common 2D matrix codes (micro/rm QR, Data Matrix, Aztec, PDF417) so manually-generated labels using those symbologies can be scanned. Payloads that don't match the spoolman spool format are ignored, so this is a safe superset. Claude-Session: https://claude.ai/code/session_01BQjJiwEbu6bHbeDjPPDm3V
89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
import { CameraOutlined } from "@ant-design/icons";
|
|
import { useTranslate } from "@refinedev/core";
|
|
import { IDetectedBarcode, Scanner } from "@yudiel/react-qr-scanner";
|
|
import { FloatButton, Modal, Space } from "antd";
|
|
import { useState } from "react";
|
|
import { useNavigate } from "react-router";
|
|
|
|
const QRCodeScannerModal = () => {
|
|
const [visible, setVisible] = useState(false);
|
|
const [lastError, setLastError] = useState<string | null>(null);
|
|
const t = useTranslate();
|
|
const navigate = useNavigate();
|
|
|
|
const onScan = (detectedCodes: IDetectedBarcode[]) => {
|
|
if (detectedCodes.length === 0) {
|
|
return;
|
|
}
|
|
const result = detectedCodes[0].rawValue;
|
|
|
|
// Check for the spoolman ID format
|
|
const match = result.match(/^web\+spoolman:s-(?<id>[0-9]+)$/i);
|
|
if (match && match.groups) {
|
|
setVisible(false);
|
|
navigate(`/spool/show/${match.groups.id}`);
|
|
}
|
|
const fullURLmatch = result.match(/^https?:\/\/[^/]+\/spool\/show\/(?<id>[0-9]+)$/i);
|
|
if (fullURLmatch && fullURLmatch.groups) {
|
|
setVisible(false);
|
|
navigate(`/spool/show/${fullURLmatch.groups.id}`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<FloatButton type="primary" onClick={() => setVisible(true)} icon={<CameraOutlined />} shape="circle" />
|
|
<Modal open={visible} destroyOnHidden onCancel={() => setVisible(false)} footer={null} title={t("scanner.title")}>
|
|
<Space direction="vertical" style={{ width: "100%" }}>
|
|
<p>{t("scanner.description")}</p>
|
|
<Scanner
|
|
constraints={{
|
|
facingMode: "environment",
|
|
}}
|
|
onScan={onScan}
|
|
// Accept common 2D matrix codes, not just QR, so labels generated with e.g.
|
|
// Data Matrix or Aztec codes can be scanned too (issue #887). Payloads that
|
|
// don't match the spoolman format are ignored, so widening this is harmless.
|
|
formats={["qr_code", "micro_qr_code", "rm_qr_code", "data_matrix", "aztec", "pdf417"]}
|
|
onError={(err: unknown) => {
|
|
const error = err as Error;
|
|
console.error(error);
|
|
if (error.name === "NotAllowedError") {
|
|
setLastError(t("scanner.error.notAllowed"));
|
|
} else if (
|
|
error.name === "InsecureContextError" ||
|
|
(location.protocol !== "https:" && navigator.mediaDevices === undefined)
|
|
) {
|
|
setLastError(t("scanner.error.insecureContext"));
|
|
} else if (error.name === "StreamApiNotSupportedError") {
|
|
setLastError(t("scanner.error.streamApiNotSupported"));
|
|
} else if (error.name === "NotReadableError") {
|
|
setLastError(t("scanner.error.notReadable"));
|
|
} else if (error.name === "NotFoundError") {
|
|
setLastError(t("scanner.error.notFound"));
|
|
} else {
|
|
setLastError(t("scanner.error.unknown", { error: error.name }));
|
|
}
|
|
}}
|
|
>
|
|
{lastError && (
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
textAlign: "center",
|
|
width: "100%",
|
|
top: "50%",
|
|
}}
|
|
>
|
|
<p>{lastError}</p>
|
|
</div>
|
|
)}
|
|
</Scanner>
|
|
</Space>
|
|
</Modal>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default QRCodeScannerModal;
|