diff --git a/hello-world/react-hooks/README.md b/hello-world/react-hooks/README.md index 86361228..07ba10c8 100644 --- a/hello-world/react-hooks/README.md +++ b/hello-world/react-hooks/README.md @@ -1,6 +1,14 @@ # Hello World Sample for React with Hooks -[React](https://reactjs.org/) is a JavaScript library meant explicitly for creating interactive UIs. Follow this guide to learn how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a React application. Note that in this sample we will use `TypeScript` and [Hooks](https://reactjs.org/docs/hooks-intro.html). Also, there is another sample `react` defining components as classes in React. +[React](https://reactjs.org/) is a JavaScript library meant explicitly for creating interactive UIs. Follow this guide to learn how to implement [Dynamsoft Barcode Reader JavaScript SDK](https://www.dynamsoft.com/barcode-reader/sdk-javascript/) (hereafter called "the library") into a React application. Note that in this sample we will use `TypeScript` and [Hooks](https://reactjs.org/docs/hooks-intro.html). Also, there is another sample `react` defining components as classes in React. + +In this guide, we will be using [`dynamsoft-barcode-reader-bundle 10.2.1000`](https://www.npmjs.com/package/dynamsoft-barcode-reader-bundle/v/10.2.1000). + +> Note: +> +> If you’re looking to integrate DBR-JS into a framework that we don't yet have a sample, don't worry! We have a [comprehensive guide](https://www.dynamsoft.com/barcode-reader/docs/web/programming/javascript/user-guide/use-in-framework.html) that provides detailed instruction and best practices for a seamless integration into any frameworks! +> +> Additionally, we're here to help! Please don't hesitate to [contact us](#Support) for any support or questions you might have. ## Official Sample @@ -9,10 +17,24 @@ ## Preparation -Make sure you have [node](https://nodejs.org/) installed. `node 16.20.1` and `react 18.2.0` used in the example below. +Make sure you have [node](https://nodejs.org/) installed. `node 16.20.1` and `react 18.2.0` are used in the example below. + +## Quick Start + +```cmd +npm install +npm start +``` +Then open http://localhost:3000/ to view the sample app. ## Create the sample project +In this section, we will be creating a React application utilizing the Dynamsoft Barcode Reader bundle sdk. + +We'll be exploring how you could create a page that not only enables barcode scanning via a webcam or a built-in camera, but also decode barcodes from local images. + +By the end of this guide, you'll have a good understanding of the SDK and be ready to discover more ways to use it! + ### Create a Bootstrapped Raw React Application with TypeScript ```cmd @@ -23,30 +45,35 @@ npx create-react-app my-app --template typescript ```cmd cd my-app -npm install dynamsoft-core -npm install dynamsoft-license -npm install dynamsoft-utility -npm install dynamsoft-barcode-reader -npm install dynamsoft-capture-vision-router -npm install dynamsoft-camera-enhancer +npm install dynamsoft-barcode-reader-bundle ``` ## Start to implement -### Add file "cvr.ts" under "/src/" to configure libraries +### Add file "dynamsoft.config.ts" under "/src/" to configure libraries ```typescript -import { CoreModule } from 'dynamsoft-core'; -import { LicenseManager } from 'dynamsoft-license'; -import 'dynamsoft-barcode-reader'; +/* /src/dynamsoft.config.ts */ +import { CoreModule } from "dynamsoft-core"; +import { LicenseManager } from "dynamsoft-license"; +import "dynamsoft-barcode-reader"; + +// Configures the paths where the .wasm files and other necessary resources for modules are located. +CoreModule.engineResourcePaths = { + std: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-std@1.2.10/dist/", + dip: "https://cdn.jsdelivr.net/npm/dynamsoft-image-processing@2.2.30/dist/", + core: "https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.30/dist/", + license: "https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.21/dist/", + cvr: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.30/dist/", + dbr: "https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader@10.2.10/dist/", + dce: "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.3/dist/", +}; /** LICENSE ALERT - README * To use the library, you need to first specify a license key using the API "initLicense()" as shown below. */ -LicenseManager.initLicense( - 'DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9' -); +LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9", true); /** * You can visit https://www.dynamsoft.com/customer/license/trialLicense?utm_source=github&product=dbr&package=js to get your own trial license good for 30 days. @@ -55,18 +82,8 @@ LicenseManager.initLicense( * LICENSE ALERT - THE END */ -CoreModule.engineResourcePaths = { - std: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-std@1.2.10/dist/", - dip: "https://cdn.jsdelivr.net/npm/dynamsoft-image-processing@2.2.30/dist/", - core: "https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.30/dist/", - license: "https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.21/dist/", - cvr: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.30/dist/", - dbr: "https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader@10.2.10/dist/", - dce: "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.3/dist/" -}; - -// Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading. -CoreModule.loadWasm(['DBR']); +// Optional. Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading. +CoreModule.loadWasm(["DBR"]); ``` > Note: @@ -76,144 +93,125 @@ CoreModule.loadWasm(['DBR']); ### Build directory structure -* Create a directory "components" under "/src/", and then create another three directories "HelloWorld", "VideoCapture" and "ImageCapture" under "/src/components/". +* Create a directory `components` under `/src/`, and then create two other directories, `VideoCapture` and `ImageCapture` under `/components/`. ### Create and edit the `VideoCapture` component -* Create `VideoCapture.tsx` and `VideoCapture.css` under "/src/components/VideoCapture/". The `VideoCapture` component helps decode barcodes via camera. +* Create `VideoCapture.tsx` under `/src/components/VideoCapture/`. The `VideoCapture` component helps decode barcodes via camera. -* In `VideoCapture.tsx`, add code for initializing and destroying some instances. +* In `VideoCapture.tsx`, add code for initializing and destroying some instances. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). ```tsx -import React, { useEffect, useRef } from "react"; -import { EnumCapturedResultItemType } from "dynamsoft-core"; -import { DecodedBarcodesResult } from "dynamsoft-barcode-reader"; -import { - CameraEnhancer, - CameraView, -} from "dynamsoft-camera-enhancer"; -import { - CapturedResultReceiver, - CaptureVisionRouter, -} from "dynamsoft-capture-vision-router"; +/* /src/components/VideoCapture/VideoCapture.tsx */ +import { useEffect, useRef } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import { CameraEnhancer, CameraView } from "dynamsoft-camera-enhancer"; +import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; import { MultiFrameResultCrossFilter } from "dynamsoft-utility"; -import "./VideoCapture.css"; + +const componentDestroyedErrorMsg = "VideoCapture Component Destroyed"; function VideoCapture() { - const uiContainer = useRef(null); + const cameraViewContainer = useRef(null); const resultsContainer = useRef(null); - const pInit = useRef( - null as Promise<{ - cameraView: CameraView; - cameraEnhancer: CameraEnhancer; - cvRouter: CaptureVisionRouter; - }> | null - ); - const pDestroy = useRef(null as Promise | null); + useEffect((): any => { + let resolveInit: () => void; + const pInit: Promise = new Promise((r) => { + resolveInit = r; + }); + let isDestroyed = false; - const init = async (): Promise<{ - cameraView: CameraView; - cameraEnhancer: CameraEnhancer; - cvRouter: CaptureVisionRouter; - }> => { - try { - // Create a `CameraEnhancer` instance for camera control and a `CameraView` instance for UI control. - const cameraView = await CameraView.createInstance(); - const cameraEnhancer = await CameraEnhancer.createInstance(cameraView); - uiContainer.current!.innerText = ""; - uiContainer.current!.append(cameraView.getUIElement()); // Get default UI and append it to DOM. - - // Create a `CaptureVisionRouter` instance and set `CameraEnhancer` instance as its image source. - const cvRouter = await CaptureVisionRouter.createInstance(); - cvRouter.setInput(cameraEnhancer); - - // Define a callback for results. - const resultReceiver = new CapturedResultReceiver(); - resultReceiver.onDecodedBarcodesReceived = ( - result: DecodedBarcodesResult - ) => { - if (!result.barcodeResultItems.length) return; - - resultsContainer.current!.textContent = ''; - console.log(result); - for (let item of result.barcodeResultItems) { - resultsContainer.current!.append( - `${item.formatString}: ${item.text}`, - document.createElement('br'), - document.createElement('hr'), - ); - } - }; - cvRouter.addResultReceiver(resultReceiver); - - // Filter out unchecked and duplicate results. - const filter = new MultiFrameResultCrossFilter(); - filter.enableResultCrossVerification( - "barcode", - true - ); // Filter out unchecked barcodes. - // Filter out duplicate barcodes within 3 seconds. - filter.enableResultDeduplication( - "barcode", - true - ); - filter.setDuplicateForgetTime( - "barcode", - 3000 - ); - await cvRouter.addResultFilter(filter); - - // Open camera and start scanning single barcode. - await cameraEnhancer.open(); - await cvRouter.startCapturing("ReadSingleBarcode"); - return { - cameraView, - cameraEnhancer, - cvRouter, - }; - } catch (ex: any) { - let errMsg = ex.message || ex; - console.error(errMsg); - alert(errMsg); - throw ex; - } - }; - - const destroy = async (): Promise => { - if (pInit.current) { - const { cameraView, cameraEnhancer, cvRouter } = await pInit.current; - cvRouter.dispose(); - cameraEnhancer.dispose(); - cameraView.dispose(); - } - }; + let cvRouter: CaptureVisionRouter; + let cameraEnhancer: CameraEnhancer; - useEffect(() => { (async () => { - // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. - if (pDestroy.current) { - await pDestroy.current; - pInit.current = init(); - } else { - pInit.current = init(); + try { + // Create a `CameraEnhancer` instance for camera control and a `CameraView` instance for UI control. + const cameraView = await CameraView.createInstance(); + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } // Check if component is destroyed after every async + cameraEnhancer = await CameraEnhancer.createInstance(cameraView); + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + + // Get default UI and append it to DOM. + cameraViewContainer.current!.append(cameraView.getUIElement()); + + // Create a `CaptureVisionRouter` instance and set `CameraEnhancer` instance as its image source. + cvRouter = await CaptureVisionRouter.createInstance(); + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + cvRouter.setInput(cameraEnhancer); + + // Define a callback for results. + cvRouter.addResultReceiver({ + onDecodedBarcodesReceived: (result) => { + if (!result.barcodeResultItems.length) return; + + resultsContainer.current!.textContent = ""; + console.log(result); + for (let item of result.barcodeResultItems) { + resultsContainer.current!.textContent += `${item.formatString}: ${item.text}\n\n`; + } + }, + }); + + // Filter out unchecked and duplicate results. + const filter = new MultiFrameResultCrossFilter(); + // Filter out unchecked barcodes. + filter.enableResultCrossVerification("barcode", true); + // Filter out duplicate barcodes within 3 seconds. + filter.enableResultDeduplication("barcode", true); + await cvRouter.addResultFilter(filter); + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + + // Open camera and start scanning single barcode. + await cameraEnhancer.open(); + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + await cvRouter.startCapturing("ReadSingleBarcode"); + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + } catch (ex: any) { + if ((ex as Error)?.message === componentDestroyedErrorMsg) { + console.log(componentDestroyedErrorMsg); + } else { + let errMsg = ex.message || ex; + console.error(errMsg); + alert(errMsg); + } } })(); - return () => { - (async () => { - await (pDestroy.current = destroy()); - console.log("VideoCapture Component Unmount"); - })(); + // Resolve pInit promise once initialization is complete. + resolveInit!(); + + // componentWillUnmount. dispose cvRouter when it's no longer needed + return async () => { + isDestroyed = true; + try { + // Wait for the pInit to complete before disposing resources. + await pInit; + cvRouter?.dispose(); + cameraEnhancer?.dispose(); + } catch (_) {} }; }, []); return (
-
- Results: +

-
+ Results: +
); } @@ -223,101 +221,87 @@ export default VideoCapture; > Note: > -> * The component should never update (check the code for `shouldComponentUpdate()`) so that events bound to the UI stay valid. - -* Define the style of the element in `VideoCapture.css` - -```css -.div-ui-container { - width: 100%; - height: 70vh; -} - -.div-results-container { - width: 100%; - height: 10vh; - overflow: auto; -} -``` +> * The component should never update so that events bound to the UI stay valid. In this copmonent, the useEffect() hook is used to handle the component’s mount and unmount lifecycle events, and there are no state updates that would cause a re-render. +> * If you're looking to customize the UI, the UI customization feature are provided by the auxiliary SDK "Dynamsoft Camera Enhancer". For more details, refer to our [User Guide](https://www.dynamsoft.com/barcode-reader/docs/web/programming/javascript/user-guide/index.html#customize-the-ui) ### Create and edit the `ImageCapture` component -* Create `ImageCapture.tsx` and `ImageCapture.css` under "/src/components/ImageCapture/". The `ImageCapture` component helps decode barcodes in an image. +* Create `ImageCapture.tsx` under `/src/components/ImageCapture/`. The `ImageCapture` component helps decode barcodes in an image. -* In `ImageCapture.tsx`, add code for initializing and destroying the `CaptureVisionRouter` instance. +* In `ImageCapture.tsx`, add code for initializing and destroying the `CaptureVisionRouter` instance. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). ```tsx -import React, { useRef, useEffect } from "react"; +/* /src/components/ImageCapture/ImageCapture.tsx */ +import React, { useRef, useEffect, MutableRefObject, useCallback } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import { EnumCapturedResultItemType } from "dynamsoft-core"; import { BarcodeResultItem } from "dynamsoft-barcode-reader"; import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; -import "../../cvr"; // import side effects. The license, engineResourcePath, so on. -import "./ImageCapture.css"; function ImageCapture() { - const pInit = useRef(null as null | Promise); - const pDestroy = useRef(null as null | Promise); - - const init = async (): Promise => { - const cvRouter = await CaptureVisionRouter.createInstance(); - return cvRouter; - }; - - const destroy = async (): Promise => { - if (pInit.current) { - const cvRouter = (await pInit.current)!; - cvRouter.dispose(); - } - }; + const resultsContainer: MutableRefObject = useRef(null); + + let pCvRouter: MutableRefObject | null> = useRef(null); + let isDestroyed = useRef(false); + + const decodeImg = useCallback(async (e: React.ChangeEvent) => { + let files = [...(e.target.files as any as File[])]; + e.target.value = ""; // reset input + resultsContainer.current!.innerText = ""; - const decodeImg = async (e: React.ChangeEvent) => { try { - const cvRouter = (await pInit.current)!; - // Decode selected image with 'ReadBarcodes_SpeedFirst' template. - const result = await cvRouter.capture( - e.target.files![0], - "ReadBarcodes_SpeedFirst" - ); - let texts = ""; - for (let item of result.items) { - console.log((item as BarcodeResultItem).text); - texts += (item as BarcodeResultItem).text + "\n"; + // ensure cvRouter is created only once + const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance()); + if (isDestroyed.current) return; + + for (let file of files) { + // Decode selected image with 'ReadBarcodes_SpeedFirst' template. + const result = await cvRouter.capture(file, "ReadBarcodes_SpeedFirst"); + if (isDestroyed.current) return; + + // Print file name if there's multiple files + if (files.length > 1) { + resultsContainer.current!.innerText += `\n${file.name}:\n`; + } + for (let _item of result.items) { + if (_item.type !== EnumCapturedResultItemType.CRIT_BARCODE) { + continue; // check if captured result item is a barcode + } + let item = _item as BarcodeResultItem; + resultsContainer.current!.innerText += item.text + "\n"; // output the decoded barcode text + console.log(item.text); + } + // If no items are found, display that no barcode was detected + if (!result.items.length) resultsContainer.current!.innerText += "No barcode found"; } - if (texts !== "") alert(texts); - if (!result.items.length) alert("No barcode found"); } catch (ex: any) { let errMsg = ex.message || ex; console.error(errMsg); alert(errMsg); } - e.target.value = ""; - }; + }, []); - useEffect(() => { - (async () => { - // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. - if (pDestroy) { - await pDestroy; - pInit.current = init(); - } else { - pInit.current = init(); + useEffect((): any => { + // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. + isDestroyed.current = false; + + // componentWillUnmount. dispose cvRouter when it's no longer needed + return async () => { + isDestroyed.current = true; + if (pCvRouter.current) { + try { + (await pCvRouter.current).dispose(); + } catch (_) {} } - })(); - - return () => { - (async () => { - await (pDestroy.current = destroy()); - console.log("ImageCapture Component Unmount"); - })(); }; }, []); return ( -
- +
+
+ +
+
); } @@ -325,53 +309,39 @@ function ImageCapture() { export default ImageCapture; ``` -* Define the style of the element in `ImageCapture.css` +### Add the `VideoCapture` and `ImageCapture` component to `App.tsx` -```css -.div-image-capture { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - border: 1px solid black; -} -``` +* On `/src/App.tsx`, we will edit the component so that it offers buttons to switch components between `VideoCapture` and `ImageCapture`. + +* Add following code to `App.tsx`. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). -### Create and edit the `HelloWorld` component +```tsx +/* /src/App.tsx */ +import { useState } from "react"; +import VideoCapture from "./components/VideoCapture/VideoCapture"; +import ImageCapture from "./components/ImageCapture/ImageCapture"; + +enum Modes { + VIDEO_CAPTURE = "video", + IMAGE_CAPTURE = "image", +} -* Create `HelloWorld.tsx` and `HelloWorld.css` under "/src/components/HelloWorld/". The `HelloWorld` component offers buttons to switch components between `VideoCapture` and `ImageCapture`; +function App() { + const [mode, setMode] = useState(Modes.VIDEO_CAPTURE); -* Add following code to `HelloWorld.tsx`. + const showVideoCapture = () => setMode(Modes.VIDEO_CAPTURE); -```tsx -import React, { useState } from "react"; -import "../../cvr"; // import side effects. The license, engineResourcePath, so on. -import VideoCapture from "../VideoCapture/VideoCapture"; -import ImageCapture from "../ImageCapture/ImageCapture"; -import "./HelloWorld.css"; - -function HelloWorld() { - let [bShowVideoCapture, setBShowVideoCapture] = useState(true); - let [bShowImageCapture, setBShowImageCapture] = useState(false); - - const showVideoCapture = () => { - setBShowVideoCapture(true); - setBShowImageCapture(false); - }; - - const showImageCapture = () => { - setBShowVideoCapture(false); - setBShowImageCapture(true); - }; + const showImageCapture = () => setMode(Modes.IMAGE_CAPTURE); return ( -
-

Hello World for React(Hooks)

-
+
+
+

Hello World for React

+
+
-
- {bShowVideoCapture ? : ""} - {bShowImageCapture ? : ""} -
-
- ); -} - -export default HelloWorld; -``` - -* Define the style of the element in `HelloWorld.css` - -```css -.div-hello-world { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - color: #455A64; -} - -h1 { - font-size: 1.5em; -} - -button { - font-size: 1.5rem; - margin: 1.5vh 0; - border: 1px solid black; - background-color: white; - color: black; -} - -.container { - margin: 2vmin auto; - font-size: medium; - width: 80vw; -} -``` - -### Add the `HelloWorld` component to `App.tsx` - -Edit the file `App.tsx` to be like this - -```jsx -import HelloWorld from './components/HelloWorld/HelloWorld'; -import './App.css'; - -function App() { - return ( -
- +
{mode === Modes.VIDEO_CAPTURE ? : }
); } @@ -454,7 +370,7 @@ export default App; npm start ``` -If you followed all the steps correctly, you will have a working page that turns one of the cameras hooked to or built in your computer or mobile device into a barcode scanner. Also, if you want to decode a local image, just click the `Image Decode` button and select the image you want to decode. Once barcodes are found, the results will show in a dialog. +If you followed all the steps correctly, you will have a working page that turns one of the cameras hooked to or built in your computer or mobile device into a barcode scanner. Also, if you want to decode a local image, just click the `Decode Image` button and select the image you want to decode. Once barcodes are found, the results will show in a dialog. ## Development server @@ -472,7 +388,7 @@ It correctly bundles React in production mode and optimizes the build for the be The build is minified and the filenames include the hashes.\ Your app is ready to be deployed! -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +See the section about [deployment](https://create-react-app.dev/docs/deployment/) for more information. ## Support diff --git a/hello-world/react-hooks/package.json b/hello-world/react-hooks/package.json index c41c8e1e..4a7e6c34 100644 --- a/hello-world/react-hooks/package.json +++ b/hello-world/react-hooks/package.json @@ -1,19 +1,18 @@ { "name": "dbrjs-react-sample", - "version": "0.0.0", + "version": "0.1.0", "private": true, - "homepage": "./", "dependencies": { - "@testing-library/jest-dom": "^5.16.5", + "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", - "@types/node": "^16.18.12", - "@types/react": "^18.0.27", - "@types/react-dom": "^18.0.10", + "@types/node": "^16.18.99", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "dynamsoft-barcode-reader-bundle": "10.2.1000", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -42,4 +41,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/hello-world/react-hooks/public/index.html b/hello-world/react-hooks/public/index.html index c9a80cda..f358c3c6 100644 --- a/hello-world/react-hooks/public/index.html +++ b/hello-world/react-hooks/public/index.html @@ -7,9 +7,16 @@ + + + + - read-video-react + Hello World for React - Dynamsoft Barcode Reader Sample diff --git a/hello-world/react-hooks/public/logo192.png b/hello-world/react-hooks/public/logo192.png new file mode 100644 index 00000000..fc44b0a3 Binary files /dev/null and b/hello-world/react-hooks/public/logo192.png differ diff --git a/hello-world/react-hooks/public/logo512.png b/hello-world/react-hooks/public/logo512.png new file mode 100644 index 00000000..a4e47a65 Binary files /dev/null and b/hello-world/react-hooks/public/logo512.png differ diff --git a/hello-world/react-hooks/public/manifest.json b/hello-world/react-hooks/public/manifest.json new file mode 100644 index 00000000..080d6c77 --- /dev/null +++ b/hello-world/react-hooks/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/hello-world/react-hooks/src/App.css b/hello-world/react-hooks/src/App.css index f3fee6a1..4b1f0cbc 100644 --- a/hello-world/react-hooks/src/App.css +++ b/hello-world/react-hooks/src/App.css @@ -1,3 +1,6 @@ +.hello-world-page { + text-align: center; +} .title { display: flex; justify-content: center; @@ -9,39 +12,39 @@ height: 60px; animation: retate 5s infinite linear; } -.top-btns { - width: 30%; +.buttons-container { + text-align: center; margin: 20px auto; } -.top-btns button { +.buttons-container button { display: inline-block; border: 1px solid black; padding: 5px 15px; background-color: transparent; cursor: pointer; } -.top-btns button:first-child { +.buttons-container button:first-child { border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-right: transparent; } -.top-btns button:nth-child(2) { +.buttons-container button:nth-child(2) { border-top-right-radius: 10px; border-bottom-right-radius: 10px; border-left: transparent; } -@media screen and (max-width: 500px) { - .top-btns { - width: 70%; +@media screen and (max-width: 800px) { + .buttons-container { + width: 70%; } } @keyframes retate { from { - transform: rotate(0deg); + transform: rotate(0deg); } to { - transform: rotate(360deg); + transform: rotate(360deg); } -} \ No newline at end of file +} diff --git a/hello-world/react-hooks/src/App.tsx b/hello-world/react-hooks/src/App.tsx index bd21f205..51c1029d 100644 --- a/hello-world/react-hooks/src/App.tsx +++ b/hello-world/react-hooks/src/App.tsx @@ -1,22 +1,46 @@ -import { useState } from 'react'; -import './App.css'; -import reactLogo from './assets/logo.svg'; -import VideoCapture from './components/VideoCapture/VideoCapture'; -import ImageCapture from './components/ImageCapture/ImageCapture'; +import { useState } from "react"; +import reactLogo from "./assets/logo.svg"; +import VideoCapture from "./components/VideoCapture/VideoCapture"; +import ImageCapture from "./components/ImageCapture/ImageCapture"; +import "./App.css"; + +enum Modes { + VIDEO_CAPTURE = "video", + IMAGE_CAPTURE = "image", +} function App() { - const [mode, setMode] = useState("video"); + const [mode, setMode] = useState(Modes.VIDEO_CAPTURE); + + const showVideoCapture = () => setMode(Modes.VIDEO_CAPTURE); + + const showImageCapture = () => setMode(Modes.IMAGE_CAPTURE); + return ( -
-
-

Hello World for React

- logo +
+
+

Hello World for React

+ logo
-
- - +
+ +
- { mode === "video" ? : } +
{mode === Modes.VIDEO_CAPTURE ? : }
); } diff --git a/hello-world/react-hooks/src/components/ImageCapture/ImageCapture.css b/hello-world/react-hooks/src/components/ImageCapture/ImageCapture.css index 4b689a19..03da581c 100644 --- a/hello-world/react-hooks/src/components/ImageCapture/ImageCapture.css +++ b/hello-world/react-hooks/src/components/ImageCapture/ImageCapture.css @@ -1,10 +1,11 @@ -.capture-img { +.image-capture-container { width: 100%; height: 100%; - font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; + font-family: Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, + Courier New, monospace; } -.capture-img .img-ipt { +.image-capture-container .input-container { width: 80%; height: 100%; display: flex; @@ -13,6 +14,7 @@ margin: 0 auto; } -.capture-img .result-area { +.image-capture-container .results { margin-top: 20px; -} \ No newline at end of file + height: 100%; +} diff --git a/hello-world/react-hooks/src/components/ImageCapture/ImageCapture.tsx b/hello-world/react-hooks/src/components/ImageCapture/ImageCapture.tsx index 363cc0e3..e8aa4630 100644 --- a/hello-world/react-hooks/src/components/ImageCapture/ImageCapture.tsx +++ b/hello-world/react-hooks/src/components/ImageCapture/ImageCapture.tsx @@ -1,39 +1,45 @@ -import { useEffect, useRef, MutableRefObject, useCallback, ChangeEvent } from "react"; -import "../../dynamsoft.config"; +import React, { useRef, useEffect, MutableRefObject, useCallback } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. import { EnumCapturedResultItemType } from "dynamsoft-core"; -import type { BarcodeResultItem } from "dynamsoft-barcode-reader"; +import { BarcodeResultItem } from "dynamsoft-barcode-reader"; import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; import "./ImageCapture.css"; -export default () => { - const resDiv: MutableRefObject = useRef(null); +function ImageCapture() { + const resultsContainer: MutableRefObject = useRef(null); - const pCvRouter: MutableRefObject | null> = useRef(null); - const bDestoried = useRef(false); + let pCvRouter: MutableRefObject | null> = useRef(null); + let isDestroyed = useRef(false); - const captureImage = useCallback(async(e: ChangeEvent)=>{ + const captureImage = useCallback(async (e: React.ChangeEvent) => { let files = [...(e.target.files as any as File[])]; - e.target.value = ''; - resDiv.current!.innerText = ""; + e.target.value = ""; // reset input + resultsContainer.current!.innerText = ""; + try { + // ensure cvRouter is created only once const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance()); - if (bDestoried.current) return; - - for(let file of files){ + if (isDestroyed.current) return; + + for (let file of files) { // Decode selected image with 'ReadBarcodes_SpeedFirst' template. const result = await cvRouter.capture(file, "ReadBarcodes_SpeedFirst"); - if (bDestoried.current) return; - - if(files.length > 1){ - resDiv.current!.innerText += `\n${file.name}:\n`; + if (isDestroyed.current) return; + + // Print file name if there's multiple files + if (files.length > 1) { + resultsContainer.current!.innerText += `\n${file.name}:\n`; } for (let _item of result.items) { - if(_item.type !== EnumCapturedResultItemType.CRIT_BARCODE) { continue; } + if (_item.type !== EnumCapturedResultItemType.CRIT_BARCODE) { + continue; // check if captured result item is a barcode + } let item = _item as BarcodeResultItem; - resDiv.current!.innerText += item.text + "\n"; + resultsContainer.current!.innerText += item.text + "\n"; // output the decoded barcode text console.log(item.text); } - if (!result.items.length) resDiv.current!.innerText += 'No barcode found\n'; + // If no items are found, display that no barcode was detected + if (!result.items.length) resultsContainer.current!.innerText += "No barcode found"; } } catch (ex: any) { let errMsg = ex.message || ex; @@ -43,25 +49,28 @@ export default () => { }, []); useEffect((): any => { - // reset value so works in React.StrictMode - bDestoried.current = false; - // onBeforeUnmount + // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. + isDestroyed.current = false; + + // componentWillUnmount. dispose cvRouter when it's no longer needed return async () => { - bDestoried.current = true; - if(pCvRouter.current){ - try{ + isDestroyed.current = true; + if (pCvRouter.current) { + try { (await pCvRouter.current).dispose(); - }catch(_){} + } catch (_) {} } - } + }; }, []); return ( -
-
- +
+
+
-
+
- ) -}; + ); +} + +export default ImageCapture; diff --git a/hello-world/react-hooks/src/components/VideoCapture/VideoCapture.css b/hello-world/react-hooks/src/components/VideoCapture/VideoCapture.css index 810b49cb..465e054e 100644 --- a/hello-world/react-hooks/src/components/VideoCapture/VideoCapture.css +++ b/hello-world/react-hooks/src/components/VideoCapture/VideoCapture.css @@ -1,11 +1,10 @@ -.div-ui-container { - width: 100%; - height: 70vh; - background: #eee; +.camera-view-container { + width: 100%; + height: 70vh; } -.div-results-container { - width: 100%; - height: 10vh; - overflow: auto; -} \ No newline at end of file +.results { + width: 100%; + height: 10vh; + overflow: auto; +} diff --git a/hello-world/react-hooks/src/components/VideoCapture/VideoCapture.tsx b/hello-world/react-hooks/src/components/VideoCapture/VideoCapture.tsx index c370eca3..7ab5ea16 100644 --- a/hello-world/react-hooks/src/components/VideoCapture/VideoCapture.tsx +++ b/hello-world/react-hooks/src/components/VideoCapture/VideoCapture.tsx @@ -1,55 +1,61 @@ -import { useEffect, useRef, MutableRefObject } from 'react'; -import "../../dynamsoft.config"; +import { useEffect, useRef } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. import { CameraEnhancer, CameraView } from "dynamsoft-camera-enhancer"; import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; import { MultiFrameResultCrossFilter } from "dynamsoft-utility"; import "./VideoCapture.css"; -const strErrorDistoryed = 'videoCapture component destoryed'; +const componentDestroyedErrorMsg = "VideoCapture Component Destroyed"; -export default () => { - const uiContainer: MutableRefObject = useRef(null); - const resultsContainer: MutableRefObject = useRef(null); +function VideoCapture() { + const cameraViewContainer = useRef(null); + const resultsContainer = useRef(null); useEffect((): any => { - let resolveInit:()=>void; - const pInit:Promise = new Promise(r=>{resolveInit=r}); - let bDestoryed = false; - - let cvRouter:CaptureVisionRouter; - let cameraEnhancer:CameraEnhancer; - - (async()=>{ - try{ + let resolveInit: () => void; + const pInit: Promise = new Promise((r) => { + resolveInit = r; + }); + let isDestroyed = false; + + let cvRouter: CaptureVisionRouter; + let cameraEnhancer: CameraEnhancer; + + (async () => { + try { // Create a `CameraEnhancer` instance for camera control and a `CameraView` instance for UI control. const cameraView = await CameraView.createInstance(); - if(bDestoryed){ throw Error(strErrorDistoryed); } // Check if component is destroyed after every async + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } // Check if component is destroyed after every async cameraEnhancer = await CameraEnhancer.createInstance(cameraView); - if(bDestoryed){ throw Error(strErrorDistoryed); } - + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + // Get default UI and append it to DOM. - uiContainer.current!.append(cameraView.getUIElement()); - + cameraViewContainer.current!.append(cameraView.getUIElement()); + // Create a `CaptureVisionRouter` instance and set `CameraEnhancer` instance as its image source. cvRouter = await CaptureVisionRouter.createInstance(); - if(bDestoryed){ throw Error(strErrorDistoryed); } + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } cvRouter.setInput(cameraEnhancer); - + // Define a callback for results. - cvRouter.addResultReceiver({ onDecodedBarcodesReceived: (result) => { - if (!result.barcodeResultItems.length) return; - - resultsContainer.current!.textContent = ''; - console.log(result); - for (let item of result.barcodeResultItems) { - resultsContainer.current!.append( - `${item.formatString}: ${item.text}`, - document.createElement('br'), - document.createElement('hr'), - ); - } - }}); - + cvRouter.addResultReceiver({ + onDecodedBarcodesReceived: (result) => { + if (!result.barcodeResultItems.length) return; + + resultsContainer.current!.textContent = ""; + console.log(result); + for (let item of result.barcodeResultItems) { + resultsContainer.current!.textContent += `${item.formatString}: ${item.text}\n\n`; + } + }, + }); + // Filter out unchecked and duplicate results. const filter = new MultiFrameResultCrossFilter(); // Filter out unchecked barcodes. @@ -57,19 +63,23 @@ export default () => { // Filter out duplicate barcodes within 3 seconds. filter.enableResultDeduplication("barcode", true); await cvRouter.addResultFilter(filter); - if(bDestoryed){ throw Error(strErrorDistoryed); } - + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + // Open camera and start scanning single barcode. await cameraEnhancer.open(); - if(bDestoryed){ throw Error(strErrorDistoryed); } + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } await cvRouter.startCapturing("ReadSingleBarcode"); - if(bDestoryed){ throw Error(strErrorDistoryed); } - - }catch(ex:any){ - - if((ex as Error)?.message === strErrorDistoryed){ - console.log(strErrorDistoryed); - }else{ + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + } catch (ex: any) { + if ((ex as Error)?.message === componentDestroyedErrorMsg) { + console.log(componentDestroyedErrorMsg); + } else { let errMsg = ex.message || ex; console.error(errMsg); alert(errMsg); @@ -77,25 +87,29 @@ export default () => { } })(); - // distroy function will wait pInit + // Resolve pInit promise once initialization is complete. resolveInit!(); - // onBeforeUnmount + // componentWillUnmount. dispose cvRouter when it's no longer needed return async () => { - bDestoryed = true; - try{ + isDestroyed = true; + try { + // Wait for the pInit to complete before disposing resources. await pInit; cvRouter?.dispose(); cameraEnhancer?.dispose(); - }catch(_){} + } catch (_) {} }; }, []); return (
-
- Results:
-
+
+
+ Results: +
); } + +export default VideoCapture; diff --git a/hello-world/react-hooks/src/dynamsoft.config.ts b/hello-world/react-hooks/src/dynamsoft.config.ts index 8a0d59c1..f31509c2 100644 --- a/hello-world/react-hooks/src/dynamsoft.config.ts +++ b/hello-world/react-hooks/src/dynamsoft.config.ts @@ -2,11 +2,22 @@ import { CoreModule } from "dynamsoft-core"; import { LicenseManager } from "dynamsoft-license"; import "dynamsoft-barcode-reader"; +// Configures the paths where the .wasm files and other necessary resources for modules are located. +CoreModule.engineResourcePaths = { + std: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-std@1.2.10/dist/", + dip: "https://cdn.jsdelivr.net/npm/dynamsoft-image-processing@2.2.30/dist/", + core: "https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.30/dist/", + license: "https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.21/dist/", + cvr: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.30/dist/", + dbr: "https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader@10.2.10/dist/", + dce: "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.3/dist/", +}; + /** LICENSE ALERT - README * To use the library, you need to first specify a license key using the API "initLicense()" as shown below. */ -LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"); +LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9", true); /** * You can visit https://www.dynamsoft.com/customer/license/trialLicense?utm_source=github&product=dbr&package=js to get your own trial license good for 30 days. @@ -15,15 +26,5 @@ LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"); * LICENSE ALERT - THE END */ -CoreModule.engineResourcePaths = { - std: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-std@1.2.10/dist/", - dip: "https://cdn.jsdelivr.net/npm/dynamsoft-image-processing@2.2.30/dist/", - core: "https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.30/dist/", - license: "https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.21/dist/", - cvr: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.30/dist/", - dbr: "https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader@10.2.10/dist/", - dce: "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.3/dist/" -}; - // Optional. Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading. CoreModule.loadWasm(["DBR"]); diff --git a/hello-world/react-hooks/src/index.css b/hello-world/react-hooks/src/index.css index 5cf73443..e9927237 100644 --- a/hello-world/react-hooks/src/index.css +++ b/hello-world/react-hooks/src/index.css @@ -1,10 +1,11 @@ -* { +body { margin: 0; - padding: 0; - box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -html, body { - text-align: center; - min-width: 350px; -} \ No newline at end of file +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; +} diff --git a/hello-world/react-hooks/src/index.tsx b/hello-world/react-hooks/src/index.tsx index 652531e4..f260548a 100644 --- a/hello-world/react-hooks/src/index.tsx +++ b/hello-world/react-hooks/src/index.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; -const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); root.render( diff --git a/hello-world/react/README.md b/hello-world/react/README.md index d4a9e3d6..f845a084 100644 --- a/hello-world/react/README.md +++ b/hello-world/react/README.md @@ -1,6 +1,14 @@ # Hello World Sample for React -[React](https://reactjs.org/) is a JavaScript library meant explicitly for creating interactive UIs. Follow this guide to learn how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a React application. Note that in this sample we will use `TypeScript` and define components as classes. Also, there is another sample `react-hooks` using `Hooks` in React. +[React](https://reactjs.org/) is a JavaScript library meant explicitly for creating interactive UIs. Follow this guide to learn how to implement [Dynamsoft Barcode Reader JavaScript SDK](https://www.dynamsoft.com/barcode-reader/sdk-javascript/) (hereafter called "the library") into a React application. Note that in this sample we will use `TypeScript` and define components as classes. Also, there is another sample `react-hooks` using `Hooks` in React. + +In this guide, we will be using [`dynamsoft-barcode-reader-bundle 10.2.1000`](https://www.npmjs.com/package/dynamsoft-barcode-reader-bundle/v/10.2.1000). + +> Note: +> +> If you’re looking to integrate DBR-JS into a framework that we don't yet have a sample, don't worry! We have a [comprehensive guide](https://www.dynamsoft.com/barcode-reader/docs/web/programming/javascript/user-guide/use-in-framework.html) that provides detailed instruction and best practices for a seamless integration into any frameworks! +> +> Additionally, we're here to help! Please don't hesitate to [contact us](#Support) for any support or questions you might have. ## Official Sample @@ -11,82 +19,61 @@ Make sure you have [node](https://nodejs.org/) installed. `node 16.20.1` and `react 18.2.0` are used in the example below. -## Create the sample project - -### Create a Bootstrapped Raw React Application with TypeScript +## Quick Start ```cmd -npx create-react-app my-app --template typescript +npm install +npm start ``` +Then open http://localhost:3000/ to view the sample app. -### **CD** to the root directory of the application and install necessary libraries - -```cmd -cd my-app -npm install dynamsoft-core -npm install dynamsoft-license -npm install dynamsoft-utility -npm install dynamsoft-barcode-reader -npm install dynamsoft-capture-vision-router -npm install dynamsoft-camera-enhancer -``` +## Creating the sample project -## Start to implement +In this section, we will be creating a React application utilizing the Dynamsoft Barcode Reader bundle sdk. -### Add file "cvr.ts" under "/src/" to configure libraries +We'll be exploring how you could create a page that not only enables barcode scanning via a webcam or a built-in camera, but also decode barcodes from local images. -```typescript -# Hello World Sample for NuxtJS - -[Nuxt](https://nuxtjs.org/) is a higher-level framework that builds on top of [Vue](https://vuejs.org/). Check out the following guide on how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a Nuxt application. Note that in this sample `TypeScript` is used. - -## Official Sample - -* Hello World in Nuxt - Source Code - -## Preparation - -Make sure you have [node](https://nodejs.org/) installed. `node 16.20.1` and `nuxt 3.2.3` are used in this article. - -## Create the sample project - -### Create a Bootstrapped Nuxt Application +By the end of this guide, you'll have a good understanding of the SDK and be ready to discover more ways to use it! + +### Create a Bootstrapped Raw React Application with TypeScript ```cmd -npx nuxi@latest init my-app +npx create-react-app my-app --template typescript ``` -You will be asked to configure quite a few things for the application during the creation. In our example, we chose the default one in every step. - -### **CD** to the root directory of the application and install the dependencies +### **CD** to the root directory of the application and install necessary libraries ```cmd cd my-app -npm install -npm install dynamsoft-core -npm install dynamsoft-license -npm install dynamsoft-utility -npm install dynamsoft-barcode-reader -npm install dynamsoft-capture-vision-router -npm install dynamsoft-camera-enhancer +npm install dynamsoft-barcode-reader-bundle ``` ## Start to implement -### Add file "cvr.ts" at the root of the app to configure libraries +### Add file "dynamsoft.config.ts" at the root of the app to configure libraries ```typescript -import { CoreModule } from 'dynamsoft-core'; -import { LicenseManager } from 'dynamsoft-license'; -import 'dynamsoft-barcode-reader'; +/* /src/dynamsoft.config.ts */ +import { CoreModule } from "dynamsoft-core"; +import { LicenseManager } from "dynamsoft-license"; +import "dynamsoft-barcode-reader"; + +// Configures the paths where the .wasm files and other necessary resources for modules are located. +CoreModule.engineResourcePaths = { + std: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-std@1.2.10/dist/", + dip: "https://cdn.jsdelivr.net/npm/dynamsoft-image-processing@2.2.30/dist/", + core: "https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.30/dist/", + license: "https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.21/dist/", + cvr: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.30/dist/", + dbr: "https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader@10.2.10/dist/", + dce: "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.3/dist/", +}; /** LICENSE ALERT - README * To use the library, you need to first specify a license key using the API "initLicense()" as shown below. */ -LicenseManager.initLicense( - 'DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9' -); +LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9", true); /** * You can visit https://www.dynamsoft.com/customer/license/trialLicense?utm_source=github&product=dbr&package=js to get your own trial license good for 30 days. @@ -95,18 +82,8 @@ LicenseManager.initLicense( * LICENSE ALERT - THE END */ -CoreModule.engineResourcePaths = { - std: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-std@1.2.10/dist/", - dip: "https://cdn.jsdelivr.net/npm/dynamsoft-image-processing@2.2.30/dist/", - core: "https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.30/dist/", - license: "https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.21/dist/", - cvr: "https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.30/dist/", - dbr: "https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader@10.2.10/dist/", - dce: "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@4.0.3/dist/" -}; - -// Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading. -CoreModule.loadWasm(['DBR']); +// Optional. Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading. +CoreModule.loadWasm(["DBR"]); ``` > Note: @@ -114,132 +91,116 @@ CoreModule.loadWasm(['DBR']); > * `initLicense()` specify a license key to use the library. You can visit https://www.dynamsoft.com/customer/license/trialLicense?utm_source=sample&product=dbr&package=js to get your own trial license good for 30 days. > * `engineResourcePaths` tells the library where to get the necessary resources at runtime. + ### Build directory structure -* Create a directory "components" under "/src/", and then create another three directories "HelloWorld", "VideoCapture" and "ImageCapture" under "/src/components/". +* Create a directory `components` under `/src/`, and then create two other directories, `VideoCapture` and `ImageCapture` under `/components/`. ### Create and edit the `VideoCapture` component -* Create `VideoCapture.tsx` and `VideoCapture.css` under "/src/components/VideoCapture/". The `VideoCapture` component helps decode barcodes via camera. +* Create `VideoCapture.tsx` under `/src/components/VideoCapture/`. The `VideoCapture` component helps decode barcodes via camera. -* In `VideoCapture.tsx`, add code for initializing and destroying some instances. +* In `VideoCapture.tsx`, add code for initializing and destroying some instances. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). ```tsx +/* /src/components/VideoCapture/VideoCapture.tsx */ import React from "react"; -import { EnumCapturedResultItemType } from "dynamsoft-core"; -import { DecodedBarcodesResult } from "dynamsoft-barcode-reader"; -import { - CameraEnhancer, - CameraView, -} from "dynamsoft-camera-enhancer"; -import { - CapturedResultReceiver, - CaptureVisionRouter, -} from "dynamsoft-capture-vision-router"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import { CameraEnhancer, CameraView } from "dynamsoft-camera-enhancer"; +import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; import { MultiFrameResultCrossFilter } from "dynamsoft-utility"; -import "./VideoCapture.css"; + +const componentDestroyedErrorMsg = "VideoCapture Component Destroyed"; class VideoCapture extends React.Component { - pInit: Promise<{ - cameraView: CameraView; - cameraEnhancer: CameraEnhancer; - cvRouter: CaptureVisionRouter; - }> | null = null; - pDestroy: Promise | null = null; - - uiContainer: React.RefObject = React.createRef(); + cameraViewContainer: React.RefObject = React.createRef(); resultsContainer: React.RefObject = React.createRef(); - async init(): Promise<{ - cameraView: CameraView; - cameraEnhancer: CameraEnhancer; - cvRouter: CaptureVisionRouter; - }> { + resolveInit?: () => void; + pInit: Promise = new Promise((r) => (this.resolveInit = r)); + isDestroyed = false; + + cvRouter?: CaptureVisionRouter; + cameraEnhancer?: CameraEnhancer; + + async componentDidMount() { try { // Create a `CameraEnhancer` instance for camera control and a `CameraView` instance for UI control. const cameraView = await CameraView.createInstance(); - const cameraEnhancer = await CameraEnhancer.createInstance(cameraView); - this.uiContainer.current!.innerText = ""; - this.uiContainer.current!.append(cameraView.getUIElement()); // Get default UI and append it to DOM. + if (this.isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } // Check if component is destroyed after every async + + this.cameraEnhancer = await CameraEnhancer.createInstance(cameraView); + if (this.isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + + // Get default UI and append it to DOM. + this.cameraViewContainer.current!.append(cameraView.getUIElement()); // Create a `CaptureVisionRouter` instance and set `CameraEnhancer` instance as its image source. - const cvRouter = await CaptureVisionRouter.createInstance(); - cvRouter.setInput(cameraEnhancer); + this.cvRouter = await CaptureVisionRouter.createInstance(); + if (this.isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + this.cvRouter.setInput(this.cameraEnhancer); // Define a callback for results. - const resultReceiver = new CapturedResultReceiver(); - resultReceiver.onDecodedBarcodesReceived = ( - result: DecodedBarcodesResult - ) => { - if (!result.barcodeResultItems.length) return; - - this.resultsContainer.current!.textContent = ''; - console.log(result); - for (let item of result.barcodeResultItems) { - this.resultsContainer.current!.append( - `${item.formatString}: ${item.text}`, - document.createElement('br'), - document.createElement('hr'), - ); - } - }; - cvRouter.addResultReceiver(resultReceiver); + this.cvRouter.addResultReceiver({ + onDecodedBarcodesReceived: (result) => { + if (!result.barcodeResultItems.length) return; + + this.resultsContainer.current!.textContent = ""; + console.log(result); + for (let item of result.barcodeResultItems) { + this.resultsContainer.current!.textContent += `${item.formatString}: ${item.text}\n\n`; + } + }, + }); // Filter out unchecked and duplicate results. const filter = new MultiFrameResultCrossFilter(); - filter.enableResultCrossVerification( - "barcode", - true - ); // Filter out unchecked barcodes. + // Filter out unchecked barcodes. + filter.enableResultCrossVerification("barcode", true); // Filter out duplicate barcodes within 3 seconds. - filter.enableResultDeduplication( - "barcode", - true - ); - filter.setDuplicateForgetTime( - "barcode", - 3000 - ); - await cvRouter.addResultFilter(filter); + filter.enableResultDeduplication("barcode", true); + await this.cvRouter.addResultFilter(filter); + if (this.isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } // Open camera and start scanning single barcode. - await cameraEnhancer.open(); - await cvRouter.startCapturing("ReadSingleBarcode"); - return { - cameraView, - cameraEnhancer, - cvRouter, - }; + await this.cameraEnhancer.open(); + if (this.isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } + await this.cvRouter.startCapturing("ReadSingleBarcode"); + if (this.isDestroyed) { + throw Error(componentDestroyedErrorMsg); + } } catch (ex: any) { - let errMsg = ex.message || ex; - console.error(errMsg); - alert(errMsg); - throw ex; - } - } - - async destroy(): Promise { - if (this.pInit) { - const { cameraView, cameraEnhancer, cvRouter } = await this.pInit; - cvRouter.dispose(); - cameraEnhancer.dispose(); - cameraView.dispose(); + if ((ex as Error)?.message === componentDestroyedErrorMsg) { + console.log(componentDestroyedErrorMsg); + } else { + let errMsg = ex.message || ex; + console.error(errMsg); + alert(errMsg); + } } - } - async componentDidMount() { - // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. - if (this.pDestroy) { - await this.pDestroy; - this.pInit = this.init(); - } else { - this.pInit = this.init(); - } + // Resolve pInit promise once initialization is complete. + this.resolveInit!(); } async componentWillUnmount() { - await (this.pDestroy = this.destroy()); - console.log("VideoCapture Component Unmount"); + this.isDestroyed = true; + try { + // Wait for the pInit to complete before disposing resources. + await this.pInit; + this.cvRouter?.dispose(); + this.cameraEnhancer?.dispose(); + } catch (_) {} } shouldComponentUpdate() { @@ -250,10 +211,10 @@ class VideoCapture extends React.Component { render() { return (
-
+
+
Results: -

-
+
); } @@ -265,97 +226,87 @@ export default VideoCapture; > Note: > > * The component should never update (check the code for `shouldComponentUpdate()`) so that events bound to the UI stay valid. - -* Define the style of the element in `VideoCapture.css` - -```css -.div-ui-container { - width: 100%; - height: 70vh; -} - -.div-results-container { - width: 100%; - height: 10vh; - overflow: auto; -} -``` +> * Also, during 'development', React executes setup and cleanup phases twice in [Strict Mode](https://react.dev/reference/react/StrictMode). To ensure proper functioning of VideoCapture in development, it's advised to avoid using Strict Mode for this component. +> However, if you're still interested on using Strict Mode, we do have a workaround available. Please contact our [support team](#Support) for further assistance. +> * If you're looking to customize the UI, the UI customization feature are provided by the auxiliary SDK "Dynamsoft Camera Enhancer". For more details, refer to our [User Guide](https://www.dynamsoft.com/barcode-reader/docs/web/programming/javascript/user-guide/index.html#customize-the-ui) ### Create and edit the `ImageCapture` component -* Create `ImageCapture.tsx` and `ImageCapture.css` under "/src/components/ImageCapture/". The `ImageCapture` component helps decode barcodes in an image. +* Create `ImageCapture.tsx` under `/src/components/ImageCapture/`. The `ImageCapture` component helps decode barcodes in an image. -* In `ImageCapture.tsx`, add code for initializing and destroying the `CaptureVisionRouter` instance. +* In `ImageCapture.tsx`, add code for initializing and destroying the `CaptureVisionRouter` instance. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). ```tsx -import React from "react"; -import { BarcodeResultItem } from "dynamsoft-barcode-reader"; +/* /src/components/ImageCapture/ImageCapture.tsx */ +import React, { ChangeEvent } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import { EnumCapturedResultItemType } from "dynamsoft-core"; +import type { BarcodeResultItem } from "dynamsoft-barcode-reader"; import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; -import "../../cvr"; // import side effects. The license, engineResourcePath, so on. -import "./ImageCapture.css"; class ImageCapture extends React.Component { - pInit: Promise | null = null; - pDestroy: Promise | null = null; + resultsContainer: React.RefObject = React.createRef(); - async init(): Promise { - const cvRouter = await CaptureVisionRouter.createInstance(); - return cvRouter; - } + pCvRouter: Promise | null = null; + isDestroyed = false; - async destroy(): Promise { - if (this.pInit) { - const cvRouter = await this.pInit; - cvRouter.dispose(); - } - } + async captureImage(e: ChangeEvent) { + let files = [...(e.target.files as any as File[])]; + e.target.value = ""; // reset input + this.resultsContainer.current!.innerText = ""; - decodeImg = async (e: React.ChangeEvent) => { try { - const cvRouter = await this.pInit; - // Decode selected image with 'ReadBarcodes_SpeedFirst' template. - const result = await cvRouter!.capture( - e.target.files![0], - "ReadBarcodes_SpeedFirst" - ); - let texts = ""; - for (let item of result.items) { - console.log((item as BarcodeResultItem).text); - texts += (item as BarcodeResultItem).text + "\n"; + const cvRouter = await (this.pCvRouter = this.pCvRouter || CaptureVisionRouter.createInstance()); + if (this.isDestroyed) return; + + for (let file of files) { + // Decode selected image with 'ReadBarcodes_SpeedFirst' template. + const result = await cvRouter.capture(file, "ReadBarcodes_SpeedFirst"); + if (this.isDestroyed) return; + + // Print file name if there's multiple files + if (files.length > 1) { + this.resultsContainer.current!.innerText += `\n${file.name}:\n`; + } + for (let _item of result.items) { + if (_item.type !== EnumCapturedResultItemType.CRIT_BARCODE) { + continue; // check if captured result item is a barcode + } + let item = _item as BarcodeResultItem; + this.resultsContainer.current!.innerText += item.text + "\n"; + console.log(item.text); + } + // If no items are found, display that no barcode was detected + if (!result.items.length) this.resultsContainer.current!.innerText += "No barcode found\n"; } - if (texts !== "") alert(texts); - if (!result.items.length) alert("No barcode found"); } catch (ex: any) { let errMsg = ex.message || ex; console.error(errMsg); alert(errMsg); } - e.target.value = ""; - }; - - async componentDidMount() { - // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. - if (this.pDestroy) { - await this.pDestroy; - this.pInit = this.init(); - } else { - this.pInit = this.init(); - } } async componentWillUnmount() { - await (this.pDestroy = this.destroy()); - console.log("ImageCapture Component Unmount"); + this.isDestroyed = true; + if (this.pCvRouter) { + try { + (await this.pCvRouter).dispose(); + } catch (_) {} + } } render() { return ( -
- +
+
+ +
+
); } @@ -364,62 +315,51 @@ class ImageCapture extends React.Component { export default ImageCapture; ``` -* Define the style of the element in `ImageCapture.css` +### Add the `VideoCapture` and `ImageCapture` component to `App.tsx` -```css -.div-image-capture { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - border: 1px solid black; -} -``` +* On `/src/App.tsx`, we will edit the component so that it offers buttons to switch components between `VideoCapture` and `ImageCapture`. -### Create and edit the `HelloWorld` component - -* Create `HelloWorld.tsx` and `HelloWorld.css` under "/src/components/HelloWorld/". The `HelloWorld` component offers buttons to switch components between `VideoCapture` and `ImageCapture`; - -* Add following code to `HelloWorld.tsx`. +* Add following code to `App.tsx`. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). ```tsx +/* src/App.tsx */ import React from "react"; -import "../../cvr"; // import side effects. The license, engineResourcePath, so on. -import VideoCapture from "../VideoCapture/VideoCapture"; -import ImageCapture from "../ImageCapture/ImageCapture"; -import "./HelloWorld.css"; +import "./dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import VideoCapture from "./components/VideoCapture/VideoCapture"; +import ImageCapture from "./components/ImageCapture/ImageCapture"; + +enum Modes { + VIDEO_CAPTURE = "video", + IMAGE_CAPTURE = "image", +} -class HelloWorld extends React.Component { +class App extends React.Component { state = { - bShowVideoCapture: true, - bShowImageCapture: false, + mode: Modes.VIDEO_CAPTURE, }; showVideoCapture = () => { this.setState({ - bShowVideoCapture: true, - bShowImageCapture: false, + mode: Modes.VIDEO_CAPTURE, }); }; showImageCapture = () => { this.setState({ - bShowVideoCapture: false, - bShowImageCapture: true, + mode: Modes.IMAGE_CAPTURE, }); }; render() { return ( -
-

Hello World for React

-
+
+
+

Hello World for React

+
+
-
- {this.state.bShowVideoCapture ? : ""} - {this.state.bShowImageCapture ? : ""} -
+
{this.state.mode === Modes.VIDEO_CAPTURE ? : }
); } } -export default HelloWorld; -``` - -* Define the style of the element in `HelloWorld.css` - -```css -.div-hello-world { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - color: #455A64; -} - -h1 { - font-size: 1.5em; -} - -button { - font-size: 1.5rem; - margin: 1.5vh 0; - border: 1px solid black; - background-color: white; - color: black; -} - -.container { - margin: 2vmin auto; - font-size: medium; - width: 80vw; -} -``` - -### Add the `HelloWorld` component to `App.tsx` - -Edit the file `App.tsx` to be like this - -```jsx -import HelloWorld from './components/HelloWorld/HelloWorld'; -import './App.css'; - -function App() { - return ( -
- -
- ); -} - export default App; ``` @@ -523,8 +407,8 @@ It correctly bundles React in production mode and optimizes the build for the be The build is minified and the filenames include the hashes.\ Your app is ready to be deployed! -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +See the section about [deployment](https://create-react-app.dev/docs/deployment/) for more information. ## Support -If you have any questions, feel free to [contact Dynamsoft support](https://www.dynamsoft.com/company/contact?utm_source=sampleReadme). +If you have any questions, feel free to [contact Dynamsoft support](https://www.dynamsoft.com/company/contact?utm_source=sampleReadme). \ No newline at end of file diff --git a/hello-world/react/package.json b/hello-world/react/package.json index 102c2b0d..84e11d02 100644 --- a/hello-world/react/package.json +++ b/hello-world/react/package.json @@ -2,18 +2,17 @@ "name": "dbrjs-react-sample", "version": "0.1.0", "private": true, - "homepage": "./", "dependencies": { - "dynamsoft-barcode-reader-bundle": "10.2.1000", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", - "@types/node": "^16.18.59", - "@types/react": "^18.2.31", - "@types/react-dom": "^18.2.14", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "@types/node": "^16.18.98", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "dynamsoft-barcode-reader-bundle": "10.2.1000", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -42,4 +41,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/hello-world/react/public/index.html b/hello-world/react/public/index.html index 70592c1d..4f81c0fb 100644 --- a/hello-world/react/public/index.html +++ b/hello-world/react/public/index.html @@ -5,9 +5,12 @@ - - - + + +