Skip to content

Commit

Permalink
[WC-2405]: Add barcode format controls to prevent incorrect scanning (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
HedwigAR authored Jun 11, 2024
2 parents 8e64c33 + 8cd46d9 commit 660941c
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 76 deletions.
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/barcode-scanner-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- Added barcode format controls to prevent incorrect scanning

## [2.3.1] - 2023-09-27

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions packages/pluggableWidgets/barcode-scanner-web/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@mendix/barcode-scanner-web",
"widgetName": "BarcodeScanner",
"version": "2.3.1",
"version": "2.4.0",
"description": "Displays a barcode scanner",
"copyright": "© Mendix Technology BV 2023. All rights reserved.",
"license": "Apache-2.0",
Expand Down Expand Up @@ -52,7 +52,7 @@
"@rollup/plugin-replace": "^2.4.2"
},
"dependencies": {
"@zxing/library": "~0.20.0",
"@zxing/library": "~0.21.0",
"classnames": "^2.3.2"
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { StructurePreviewProps } from "@mendix/widget-plugin-platform/preview/structure-preview-api";
import { Properties, transformGroupsIntoTabs } from "@mendix/pluggable-widgets-tools";
import { hidePropertyIn, Properties, transformGroupsIntoTabs } from "@mendix/pluggable-widgets-tools";

import { BarcodeScannerContainerProps } from "../typings/BarcodeScannerProps";
import BarcodeScannerSvg from "./assets/barcodescanner.svg";
import BarcodeScannerSvgDark from "./assets/barcodescanner-dark.svg";

export function getProperties(
_: BarcodeScannerContainerProps,
values: BarcodeScannerContainerProps,
defaultProperties: Properties,
platform: "web" | "desktop"
): Properties {
if (platform === "web") {
transformGroupsIntoTabs(defaultProperties);
}
if (values.useAllFormats) {
hidePropertyIn(defaultProperties, values, "barcodeFormats");
}
return defaultProperties;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const BarcodeScanner: FunctionComponent<BarcodeScannerContainerProps> = p
onDetect={onDetect}
showMask={props.showMask}
class={props.class}
useAllFormats={props.useAllFormats}
barcodeFormats={props.barcodeFormats}
heightUnit={props.heightUnit}
height={props.height}
widthUnit={props.widthUnit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,37 @@
<caption>Show barcode mask</caption>
<description>Apply a mask to camera view, as a specific target area for the barcode.</description>
</property>
<property key="useAllFormats" type="boolean" required="true" defaultValue="true">
<caption>Use all barcode formats</caption>
<description>Scan for all available barcode formats</description>
</property>
<property key="barcodeFormats" type="object" isList="true">
<caption>Enabled barcode formats</caption>
<description />
<properties>
<propertyGroup caption="Object list group">
<property key="barcodeFormat" type="enumeration" defaultValue="AZTEC">
<caption>Barcode format</caption>
<description>Barcode format which should be recognized by the scanner</description>
<enumerationValues>
<enumerationValue key="AZTEC">Aztec</enumerationValue>
<enumerationValue key="CODE_39">Code 39</enumerationValue>
<enumerationValue key="CODE_128">Code 128</enumerationValue>
<enumerationValue key="DATA_MATRIX">Data Matrix</enumerationValue>
<enumerationValue key="EAN_8">EAN-8</enumerationValue>
<enumerationValue key="EAN_13">EAN-13</enumerationValue>
<enumerationValue key="ITF">ITF</enumerationValue>
<enumerationValue key="PDF_417">PDF 417</enumerationValue>
<enumerationValue key="QR_CODE">QR Code</enumerationValue>
<enumerationValue key="RSS_14">RSS-14</enumerationValue>
<enumerationValue key="UPC_A">UPC-A</enumerationValue>
<enumerationValue key="UPC_E">UPC-E</enumerationValue>
</enumerationValues>
</property>
</propertyGroup>
</properties>
</property>
</propertyGroup>

<propertyGroup caption="Events">
<property key="onDetect" type="action" required="false">
<caption>On detect action</caption>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { createElement, ReactElement, ReactNode, useCallback, SyntheticEvent } from "react";
import { createElement, ReactElement, ReactNode, useCallback, SyntheticEvent, useState } from "react";
import classNames from "classnames";
import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
import { Dimensions, getDimensions } from "@mendix/widget-plugin-platform/utils/get-dimensions";
import { useCustomErrorMessage } from "../hooks/useCustomErrorMessage";
import { useReader } from "../hooks/useReader";
import { BarcodeFormatsType } from "../../typings/BarcodeScannerProps";

import "../ui/BarcodeScanner.scss";

interface BarcodeScannerOverlayProps extends Dimensions {
showMask: boolean;
class: string;
children?: ReactNode;
canvasMiddleMiddleRef?: (ref: HTMLDivElement | null) => void;
}

export function BarcodeScannerOverlay({
children,
class: className,
showMask,
canvasMiddleMiddleRef,
...dimensions
}: BarcodeScannerOverlayProps): ReactElement {
return (
Expand All @@ -28,7 +31,15 @@ export function BarcodeScannerOverlay({
<div className={classNames("canvas-left", "canvas-background")} />
<div className={classNames("canvas-middle")}>
<div className={classNames("canvas-middle-top", "canvas-background")} />
<div className={classNames("canvas-middle-middle")}>
<div
ref={ref => {
if (canvasMiddleMiddleRef !== undefined) {
canvasMiddleMiddleRef(ref);
}
}}
id={"canvas-middle-middle"}
className={classNames("canvas-middle-middle")}
>
<div className={classNames("corner", "corner-top-left")} />
<div className={classNames("corner", "corner-top-right")} />
<div className={classNames("corner", "corner-bottom-right")} />
Expand All @@ -48,19 +59,27 @@ export interface BarcodeScannerProps extends Dimensions {
onDetect?: (data: string) => void;
showMask: boolean;
class: string;
useAllFormats: boolean;
barcodeFormats?: BarcodeFormatsType[];
}

export function BarcodeScanner({
onDetect,
showMask,
class: className,
barcodeFormats,
useAllFormats,
...dimensions
}: BarcodeScannerProps): ReactElement | null {
const [errorMessage, setError] = useCustomErrorMessage();
const [canvasMiddleRef, setCanvasMiddleRef] = useState<HTMLDivElement>();
const videoRef = useReader({
onSuccess: onDetect,
onError: setError,
useCrop: showMask
useCrop: showMask,
barcodeFormats,
useAllFormats,
canvasMiddleRef
});
const supportsCameraAccess = typeof navigator?.mediaDevices?.getUserMedia === "function";
const onCanPlay = useCallback((event: SyntheticEvent<HTMLVideoElement>) => {
Expand Down Expand Up @@ -88,7 +107,16 @@ export function BarcodeScanner({
}

return (
<BarcodeScannerOverlay class={className} showMask={showMask} {...dimensions}>
<BarcodeScannerOverlay
class={className}
showMask={showMask}
canvasMiddleMiddleRef={ref => {
if (ref !== null) {
setCanvasMiddleRef(ref);
}
}}
{...dimensions}
>
<video className="video" ref={videoRef} onCanPlay={onCanPlay} />
</BarcodeScannerOverlay>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,19 @@ describe("Barcode scanner", () => {

it("renders video and overlay correctly", () => {
mockGetUserMedia(jest.fn());
expect(render(<BarcodeScanner class="" showMask {...dimensions} />).container).toMatchSnapshot();
expect(render(<BarcodeScanner useAllFormats class="" showMask {...dimensions} />).container).toMatchSnapshot();
});

it("does not show the overlay when the user opts out of it", () => {
mockGetUserMedia(jest.fn());
expect(render(<BarcodeScanner class="" showMask={false} {...dimensions} />).container).toMatchSnapshot();
expect(
render(<BarcodeScanner useAllFormats class="" showMask={false} {...dimensions} />).container
).toMatchSnapshot();
});

it("shows an appropriate error when the mediaDevices API is not present (like over http)", async () => {
expect(navigator.mediaDevices).toBe(undefined);
expect(render(<BarcodeScanner class="" showMask {...dimensions} />).container).toMatchSnapshot();
expect(render(<BarcodeScanner useAllFormats class="" showMask {...dimensions} />).container).toMatchSnapshot();
});

it("prop health check: pass onDetect prop as onSuccess callback", async () => {
Expand All @@ -58,7 +60,7 @@ describe("Barcode scanner", () => {
});
mockGetUserMedia(jest.fn());

render(<BarcodeScanner class="" onDetect={onDetectMock} showMask {...dimensions} />);
render(<BarcodeScanner useAllFormats class="" onDetect={onDetectMock} showMask {...dimensions} />);

await waitFor(() => expect(onDetectMock).toBeCalledWith("42"));
});
Expand All @@ -71,7 +73,7 @@ describe("Barcode scanner", () => {
mockGetUserMedia(jest.fn());

await act(async () => {
render(<BarcodeScanner class="" showMask {...dimensions} />);
render(<BarcodeScanner useAllFormats class="" showMask {...dimensions} />);
});
await waitFor(() => expect(screen.getByText(/some error message/i)).toBeVisible());
});
Expand All @@ -86,7 +88,7 @@ describe("Barcode scanner", () => {
mockGetUserMedia(jest.fn());

await act(async () => {
render(<BarcodeScanner class="" showMask {...dimensions} />);
render(<BarcodeScanner useAllFormats class="" showMask {...dimensions} />);
});
await waitFor(() => expect(screen.getByText(/Unable to decode from stream/i)).toBeVisible());
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ exports[`Barcode scanner renders video and overlay correctly 1`] = `
/>
<div
class="canvas-middle-middle"
id="canvas-middle-middle"
>
<div
class="corner corner-top-left"
Expand Down
Loading

0 comments on commit 660941c

Please sign in to comment.