ff-admin/src/helpers/codeScanner.ts

143 lines
4.3 KiB
TypeScript

import type { BarcodeFormat, DetectedBarcode } from "barcode-detector/pure";
/*** select camera ***/
export interface Camera {
label: string;
constraints: {
deviceId?: string;
facingMode: string;
};
}
export const defaultConstraintOptions: Array<Camera> = [
{ label: "rear camera", constraints: { facingMode: "environment" } },
{ label: "front camera", constraints: { facingMode: "user" } },
];
export async function getAvailableCameras(useDefault: boolean = false): Promise<Array<Camera>> {
// NOTE: on iOS we can't invoke `enumerateDevices` before the user has given
// camera access permission. `QrcodeStream` internally takes care of
// requesting the permissions. The `camera-on` event should guarantee that this
// has happened.
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(({ kind }) => kind === "videoinput");
return [
...(useDefault ? defaultConstraintOptions : []),
...videoDevices.map(({ deviceId, label }) => ({
label: `${label}`, //(ID: ${deviceId})
constraints: { deviceId, facingMode: "custom" },
})),
];
}
/*** track functons ***/
export function paintOutline(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
for (const detectedCode of detectedCodes) {
const [firstPoint, ...otherPoints] = detectedCode.cornerPoints;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.moveTo(firstPoint.x, firstPoint.y);
for (const { x, y } of otherPoints) {
ctx.lineTo(x, y);
}
ctx.lineTo(firstPoint.x, firstPoint.y);
ctx.closePath();
ctx.stroke();
}
}
export function paintBoundingBox(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
for (const detectedCode of detectedCodes) {
const {
boundingBox: { x, y, width, height },
} = detectedCode;
ctx.lineWidth = 2;
ctx.strokeStyle = "#007bff";
ctx.strokeRect(x, y, width, height);
}
}
export function paintCenterText(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) {
for (const detectedCode of detectedCodes) {
const { boundingBox, rawValue } = detectedCode;
const centerX = boundingBox.x + boundingBox.width / 2;
const centerY = boundingBox.y + boundingBox.height / 2;
const fontSize = Math.max(12, (50 * boundingBox.width) / ctx.canvas.width);
ctx.font = `bold ${fontSize}px sans-serif`;
ctx.textAlign = "center";
ctx.lineWidth = 3;
ctx.strokeStyle = "#35495e";
ctx.strokeText(detectedCode.rawValue, centerX, centerY);
ctx.fillStyle = "#5cb984";
ctx.fillText(rawValue, centerX, centerY);
}
}
export const trackFunctionOptions = [
{ text: "nothing (default)", value: undefined },
{ text: "outline", value: paintOutline },
{ text: "centered text", value: paintCenterText },
{ text: "bounding box", value: paintBoundingBox },
{
text: "mixed",
value: (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => {
paintOutline(detectedCodes, ctx);
paintCenterText(detectedCodes, ctx);
},
},
];
/*** barcode formats ***/
export const barcodeFormats: Array<BarcodeFormat> = [
"aztec",
"code_128",
"code_39",
"code_93",
"codabar",
"databar",
"databar_expanded",
"data_matrix",
"dx_film_edge",
"ean_13",
"ean_8",
"itf",
"maxi_code",
"micro_qr_code",
"pdf417",
"qr_code",
"rm_qr_code",
"upc_a",
"upc_e",
"linear_codes",
"matrix_codes",
];
/*** error handling ***/
export function handleScannerError(err: Error) {
let error = `[${err.name}]: `;
if (err.name === "NotAllowedError") {
error += "you need to grant camera access permission";
} else if (err.name === "NotFoundError") {
error += "no camera on this device";
} else if (err.name === "NotSupportedError") {
error += "secure context required (HTTPS, localhost)";
} else if (err.name === "NotReadableError") {
error += "is the camera already in use?";
} else if (err.name === "OverconstrainedError") {
error += "installed cameras are not suitable";
} else if (err.name === "StreamApiNotSupportedError") {
error += "Stream API is not supported in this browser";
} else if (err.name === "InsecureContextError") {
error += "Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.";
} else {
error += err.message;
}
return error;
}