diff --git a/Binner/Binner.Web/ClientApp/package-lock.json b/Binner/Binner.Web/ClientApp/package-lock.json
index 68053831..5219a74f 100644
--- a/Binner/Binner.Web/ClientApp/package-lock.json
+++ b/Binner/Binner.Web/ClientApp/package-lock.json
@@ -64,6 +64,7 @@
"smooth-scroll": "^16.1.3",
"tiny-slider": "^2.9.4",
"underscore": "^1.13.6",
+ "use-sound": "^4.0.1",
"uuid": "^9.0.0",
"yarn": "^1.22.19"
},
@@ -12031,6 +12032,11 @@
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
+ "node_modules/howler": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz",
+ "integrity": "sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg=="
+ },
"node_modules/hpack.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
@@ -22824,6 +22830,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/use-sound": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/use-sound/-/use-sound-4.0.1.tgz",
+ "integrity": "sha512-hykJ86kNcu6y/FzlSHcQxhjSGMslZx2WlfLpZNoPbvueakv4OF3xPxEtGV2YmculrIaH0tPp9LtG4jgy17xMWg==",
+ "dependencies": {
+ "howler": "^2.1.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
"node_modules/util": {
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
@@ -33172,6 +33189,11 @@
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
+ "howler": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz",
+ "integrity": "sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg=="
+ },
"hpack.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
@@ -41242,6 +41264,14 @@
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
"dev": true
},
+ "use-sound": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/use-sound/-/use-sound-4.0.1.tgz",
+ "integrity": "sha512-hykJ86kNcu6y/FzlSHcQxhjSGMslZx2WlfLpZNoPbvueakv4OF3xPxEtGV2YmculrIaH0tPp9LtG4jgy17xMWg==",
+ "requires": {
+ "howler": "^2.1.3"
+ }
+ },
"util": {
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
diff --git a/Binner/Binner.Web/ClientApp/package.json b/Binner/Binner.Web/ClientApp/package.json
index c64b6e9e..64885288 100644
--- a/Binner/Binner.Web/ClientApp/package.json
+++ b/Binner/Binner.Web/ClientApp/package.json
@@ -59,6 +59,7 @@
"smooth-scroll": "^16.1.3",
"tiny-slider": "^2.9.4",
"underscore": "^1.13.6",
+ "use-sound": "^4.0.1",
"uuid": "^9.0.0",
"yarn": "^1.22.19"
},
diff --git a/Binner/Binner.Web/ClientApp/src/App.js b/Binner/Binner.Web/ClientApp/src/App.js
index 38dcf1a0..418183c7 100644
--- a/Binner/Binner.Web/ClientApp/src/App.js
+++ b/Binner/Binner.Web/ClientApp/src/App.js
@@ -29,6 +29,7 @@ import { Settings } from "./pages/Settings";
import { OhmsLawCalculator } from "./pages/tools/OhmsLawCalculator";
import { ResistorColorCodeCalculator } from "./pages/tools/ResistorColorCodeCalculator";
import { VoltageDividerCalculator } from "./pages/tools/VoltageDividerCalculator";
+import { BarcodeScanner } from "./pages/tools/BarcodeScanner";
import { Help } from './pages/help/Home';
import { Scanning } from './pages/help/Scanning';
import { ApiIntegrations } from './pages/help/ApiIntegrations';
@@ -85,6 +86,10 @@ export default class App extends Component {
path="/tools/voltagedivider"
element={}
/>
+ }
+ />
} />
} />
} />
diff --git a/Binner/Binner.Web/ClientApp/src/components/BarcodeScannerInput.js b/Binner/Binner.Web/ClientApp/src/components/BarcodeScannerInput.js
index 390848ee..54805e75 100644
--- a/Binner/Binner.Web/ClientApp/src/components/BarcodeScannerInput.js
+++ b/Binner/Binner.Web/ClientApp/src/components/BarcodeScannerInput.js
@@ -77,10 +77,15 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
const processBarcodeInformation = (e, value) => {
let barcodeType = "code128";
let parsedValue = {};
+ let gsDetected = false;
+ let rsDetected = false;
if (value.startsWith("[)>")) {
// 2D DotMatrix barcode. Process into value.
barcodeType = "datamatrix";
- parsedValue = parseDataMatrix(value);
+ const parseResult = parseDataMatrix(value);
+ parsedValue = parseResult.value;
+ gsDetected = parseResult.gsDetected;
+ rsDetected = parseResult.rsDetected;
} else {
// 1D barcode
parsedValue = value.replace("\n", "").replace("\r", "");
@@ -89,7 +94,9 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
return {
type: barcodeType,
value: parsedValue,
- rawValue: value
+ rawValue: value,
+ rsDetected,
+ gsDetected
};
};
@@ -102,6 +109,8 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
const expectedFormatNumber = 6; /** 22z22 barcode */
const controlChars = ["P", "1P", "P1", "K", "1K", "10K", "11K", "4L", "Q", "11Z", "12Z", "13Z", "20Z"];
+ let gsCodePresent = false;
+ let rsCodePresent = false;
let formatNumber = "";
let buffer = "";
let i;
@@ -111,6 +120,7 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
if (buffer === header) {
if (value.charCodeAt(i + 1) === rsCharCode) {
// read the character after the RS token (sometimes not present)
+ rsCodePresent = true;
formatNumberIndex = i + 2;
} else {
formatNumberIndex = i + 1;
@@ -143,6 +153,9 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
if (gsLine.length > 0)
gsLines.push(gsLine);
+ if (gsLines.length > 0)
+ gsCodePresent = true;
+
// console.log('gsLines', gsLines);
for (i = 0; i < gsLines.length; i++) {
@@ -206,7 +219,11 @@ export function BarcodeScannerInput({listening, minInputLength, onReceived, help
break;
}
}
- return parsedValue;
+ return {
+ value: parsedValue,
+ gsDetected: gsCodePresent,
+ rsDetected: rsCodePresent
+ };
};
const scannerDebounced = useMemo(() => debounce(onReceivedBarcodeInput, BufferTimeMs), []);
diff --git a/Binner/Binner.Web/ClientApp/src/pages/Inventory.js b/Binner/Binner.Web/ClientApp/src/pages/Inventory.js
index bdda1130..d7dcc51b 100644
--- a/Binner/Binner.Web/ClientApp/src/pages/Inventory.js
+++ b/Binner/Binner.Web/ClientApp/src/pages/Inventory.js
@@ -152,6 +152,8 @@ export function Inventory(props) {
bulkScanIsOpenRef.current = bulkScanIsOpen;
const scannedPartsRef = useRef();
scannedPartsRef.current = scannedParts;
+ const partTypesRef = useRef();
+ partTypesRef.current = partTypes;
useEffect(() => {
const partNumberStr = props.params.partNumber;
@@ -174,12 +176,14 @@ export function Inventory(props) {
}, [props.params.partNumber]);
const fetchPartMetadata = async (input, part) => {
+ if (partTypesRef.current.length === 0)
+ console.error("There are no partTypes! This shouldn't happen and is a bug.");
Inventory.infoAbortController.abort();
Inventory.infoAbortController = new AbortController();
setLoadingPartMetadata(true);
setPartMetadataIsSubscribed(false);
try {
- const response = await fetchApi(`part/info?partNumber=${input}&partTypeId=${part.partTypeId}&mountingTypeId=${part.mountingTypeId}`, {
+ const response = await fetchApi(`part/info?partNumber=${input}&partTypeId=${part.partTypeId || "0"}&mountingTypeId=${part.mountingTypeId || "0"}`, {
signal: Inventory.infoAbortController.signal
});
const data = response.data;
@@ -196,7 +200,7 @@ export function Inventory(props) {
const suggestedPart = infoResponse.parts[0];
// populate the form with data from the part metadata
- if(!isEditing) setPartFromMetadata(metadataParts, suggestedPart, partTypes);
+ if(!isEditing) setPartFromMetadata(metadataParts, suggestedPart);
} else {
// no part metadata available
setPartMetadataIsSubscribed(true);
@@ -217,7 +221,7 @@ export function Inventory(props) {
}
};
- const searchDebounced = useMemo(() => debounce(fetchPartMetadata, 1000), [partTypes, part]);
+ const searchDebounced = useMemo(() => debounce(fetchPartMetadata, 1000), []);
const onUploadSubmit = async (uploadFiles, type) => {
setUploading(true);
@@ -388,7 +392,12 @@ export function Inventory(props) {
// scan single part
if (cleanPartNumber) {
setPartMetadataIsSubscribed(false);
- const newPart = {...part, partNumber: cleanPartNumber, quantity: input.value.quantity || "1"};
+ const newPart = {...part,
+ partNumber: cleanPartNumber,
+ quantity: input.value.quantity || "1",
+ partTypeId: -1,
+ mountingTypeId: -1,
+ };
setPart(newPart);
localStorage.setItem("viewPreferences", JSON.stringify({ ...viewPreferences, lastQuantity: newPart.quantity }));
setShowBarcodeBeingScanned(false);
@@ -702,15 +711,15 @@ export function Inventory(props) {
updatedPart[control.name] = control.value;
switch (control.name) {
case "partNumber":
- if (updatedPart.partNumber && updatedPart.partNumber.length > 0) searchDebounced(updatedPart.partNumber, updatedPart);
+ if (updatedPart.partNumber && updatedPart.partNumber.length > 0) searchDebounced(updatedPart.partNumber, updatedPart, partTypes);
break;
case "partTypeId":
localStorage.setItem("viewPreferences", JSON.stringify({ ...viewPreferences, lastPartTypeId: control.value }));
- if (updatedPart.partNumber && updatedPart.partNumber.length > 0) searchDebounced(updatedPart.partNumber, updatedPart);
+ if (updatedPart.partNumber && updatedPart.partNumber.length > 0) searchDebounced(updatedPart.partNumber, updatedPart, partTypes);
break;
case "mountingTypeId":
localStorage.setItem("viewPreferences", JSON.stringify({ ...viewPreferences, lastMountingTypeId: control.value }));
- if (updatedPart.partNumber && updatedPart.partNumber.length > 0) searchDebounced(updatedPart.partNumber, updatedPart);
+ if (updatedPart.partNumber && updatedPart.partNumber.length > 0) searchDebounced(updatedPart.partNumber, updatedPart, partTypes);
break;
case "lowStockThreshold":
localStorage.setItem("viewPreferences", JSON.stringify({ ...viewPreferences, lowStockThreshold: control.value }));
@@ -739,11 +748,14 @@ export function Inventory(props) {
await fetchApi(`part/print?partNumber=${part.partNumber}&generateImageOnly=false`, { method: "POST" });
};
- const setPartFromMetadata = (metadataParts, suggestedPart, partTypes) => {
+ const setPartFromMetadata = (metadataParts, suggestedPart) => {
+ if (partTypesRef.current.length === 0)
+ console.error("There are no partTypes! This shouldn't happen and is a bug.");
+
const entity = { ...part };
const mappedPart = {
partNumber: suggestedPart.basePartNumber,
- partTypeId: getPartTypeId(suggestedPart.partType, partTypes),
+ partTypeId: getPartTypeId(suggestedPart.partType, partTypesRef.current),
mountingTypeId: suggestedPart.mountingTypeId,
packageType: suggestedPart.packageType,
keywords: suggestedPart.keywords && suggestedPart.keywords.join(" ").toLowerCase(),
@@ -812,7 +824,7 @@ export function Inventory(props) {
};
const handleChooseAlternatePart = (e, part, partTypes) => {
- setPartFromMetadata(metadataParts, part, partTypes);
+ setPartFromMetadata(metadataParts, part);
setPartModalOpen(false);
};
diff --git a/Binner/Binner.Web/ClientApp/src/pages/Tools.js b/Binner/Binner.Web/ClientApp/src/pages/Tools.js
index 988fff97..fd1d92aa 100644
--- a/Binner/Binner.Web/ClientApp/src/pages/Tools.js
+++ b/Binner/Binner.Web/ClientApp/src/pages/Tools.js
@@ -34,6 +34,12 @@ export function Tools(props) {
Voltage Divider Calculator
+ route(e, "/tools/barcodescanner")} style={{ cursor: "pointer" }}>
+
+
+
+ Barcode Scanner
+
diff --git a/Binner/Binner.Web/ClientApp/src/pages/tools/BarcodeScanner.css b/Binner/Binner.Web/ClientApp/src/pages/tools/BarcodeScanner.css
new file mode 100644
index 00000000..5034658e
--- /dev/null
+++ b/Binner/Binner.Web/ClientApp/src/pages/tools/BarcodeScanner.css
@@ -0,0 +1,20 @@
+code {
+ background: #f4f4f4;
+ border: 1px solid #ddd;
+ border-left: 3px solid #f36d33;
+ color: #666;
+ page-break-inside: avoid;
+ font-family: monospace;
+ font-size: 15px;
+ line-height: 1.6;
+ margin-bottom: 1.6em;
+ max-width: 100%;
+ padding: 1em 1.5em;
+ display: block;
+ word-wrap: break-word !important;
+ white-space: pre-line !important;
+}
+
+pre {
+ white-space: pre !important;
+}
\ No newline at end of file
diff --git a/Binner/Binner.Web/ClientApp/src/pages/tools/BarcodeScanner.js b/Binner/Binner.Web/ClientApp/src/pages/tools/BarcodeScanner.js
new file mode 100644
index 00000000..50fe39a8
--- /dev/null
+++ b/Binner/Binner.Web/ClientApp/src/pages/tools/BarcodeScanner.js
@@ -0,0 +1,35 @@
+import React, { useState, useEffect, useMemo } from "react";
+import { Link } from "react-router-dom";
+import _ from "underscore";
+import { Label, Button, Image, Form, Table, Segment, Dimmer, Checkbox, Loader, Popup } from "semantic-ui-react";
+import { toast } from "react-toastify";
+import { BarcodeScannerInput } from "../../components/BarcodeScannerInput";
+import "./BarcodeScanner.css";
+
+export function BarcodeScanner(props) {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [message, setMessage] = useState(null);
+ const [isKeyboardListening, setIsKeyboardListening] = useState(true);
+ const [barcodeValue, setBarcodeValue] = useState('Waiting for input...');
+
+ // debounced handler for processing barcode scanner input
+ const handleBarcodeInput = (e, input) => {
+ // ignore single keypresses
+ setBarcodeValue(JSON.stringify(input, null, 2));
+ toast.info(`Barcode type ${input.type} received`);
+ };
+
+ return (
+
+
+
Barcode Scanner
+
Test your barcode scanner to see what values it outputs.
+
+
+ );
+}
diff --git a/Binner/Library/Binner.Common/Integrations/DigikeyApi.cs b/Binner/Library/Binner.Common/Integrations/DigikeyApi.cs
index 6e7a7e12..fe0196a6 100644
--- a/Binner/Library/Binner.Common/Integrations/DigikeyApi.cs
+++ b/Binner/Library/Binner.Common/Integrations/DigikeyApi.cs
@@ -437,7 +437,7 @@ private ICollection MapTaxonomies(string partType, MountingTypes pac
{
var taxonomies = new List();
var taxonomy = Taxonomies.None;
- if (!string.IsNullOrEmpty(partType))
+ if (!string.IsNullOrEmpty(partType) && partType != "-1")
{
if (Enum.TryParse(partType, true, out taxonomy))
{