From 6c3386aa9bca0113b4b6376745a9ebcc5e35a942 Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Wed, 19 Jun 2024 14:20:05 -0700 Subject: [PATCH 1/9] feat: updated nextjs sample and docs --- hello-world/next/README.md | 500 +++++++++++++++++- hello-world/next/app/layout.tsx | 5 +- hello-world/next/app/page.tsx | 17 +- hello-world/next/component/ImageCapture.css | 18 - hello-world/next/component/ImageCapture.tsx | 70 --- hello-world/next/component/VideoCapture.css | 11 - hello-world/next/component/VideoCapture.tsx | 103 ---- .../HelloWorld/HelloWorld.css} | 21 +- .../next/components/HelloWorld/HelloWorld.tsx | 53 ++ .../components/ImageCapture/ImageCapture.css | 19 + .../components/ImageCapture/ImageCapture.tsx | 83 +++ .../components/VideoCapture/VideoCapture.css | 11 + .../components/VideoCapture/VideoCapture.tsx | 117 ++++ hello-world/next/dynamsoft.config.ts | 2 +- hello-world/next/package.json | 4 +- 15 files changed, 785 insertions(+), 249 deletions(-) delete mode 100644 hello-world/next/component/ImageCapture.css delete mode 100644 hello-world/next/component/ImageCapture.tsx delete mode 100644 hello-world/next/component/VideoCapture.css delete mode 100644 hello-world/next/component/VideoCapture.tsx rename hello-world/next/{app/page.css => components/HelloWorld/HelloWorld.css} (71%) create mode 100644 hello-world/next/components/HelloWorld/HelloWorld.tsx create mode 100644 hello-world/next/components/ImageCapture/ImageCapture.css create mode 100644 hello-world/next/components/ImageCapture/ImageCapture.tsx create mode 100644 hello-world/next/components/VideoCapture/VideoCapture.css create mode 100644 hello-world/next/components/VideoCapture/VideoCapture.tsx diff --git a/hello-world/next/README.md b/hello-world/next/README.md index c4033664..6719b374 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -1,24 +1,498 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Hello World Sample for Next.js -## Getting Started +[Next.js](https://nextjs.org/) is a react framework that enables functionalities such as server-side rendering and geenrating static websites for react-based web applications. Follow this guide to learn how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a Next.js application. Note that in this sample we will use `TypeScript` and define components as classes. -First, run the development server: +## Official Sample -```bash +* Hello World in Next.js - Source Code + +## Preparation + +Make sure you have [node](https://nodejs.org/) installed. `node 18.17.1` and `next 14.2.3` are used in the example below. + +## Quick Start + +```cmd +npm install npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev ``` +Then open http://localhost:3000/ to view the sample app. + +## Creating the sample project + +In this section, we will be creating a Next.js 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 [Next.js](https://nextjs.org/) Application bootstrapped with [create-next-app](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with TypeScript. + +```cmd +npx create-next-app@latest +``` + +On installation, you will be prompted to configure your project.\ +You can customize these options according to your preferences.\ +Below is the configuration used for this sample. + +``` +√ What is your project named? ... my-app +√ Would you like to use TypeScript? ... Yes +√ Would you like to use ESLint? ... Yes +√ Would you like to use Tailwind CSS? ... No +√ Would you like to use `src/` directory? ... No +√ Would you like to use App Router? (recommended) ... Yes +√ Would you like to customize the default import alias (@/*)? ... Yes +√ What import alias would you like configured? ... @/* +``` + +### **CD** to the root directory of the application and install necessary libraries + +```cmd +cd my-app +npm install dynamsoft-barcode-reader-bundle +``` + +## Start to implement + +### 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"; + +/** 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"); + +/** + * 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. + * Note that if you downloaded this sample from Dynamsoft while logged in, the above license key may already be your own 30-day trial license. + * For more information, see https://www.dynamsoft.com/barcode-reader/programming/javascript/user-guide/?ver=10.2.10&utm_source=github#specify-the-license or contact support@dynamsoft.com. + * LICENSE ALERT - THE END + */ + +// 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/", +}; + +// Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading. +CoreModule.loadWasm(["DBR"]); +``` + +> Note: +> +> * `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" on the root direction, and then create another three directories "HelloWorld", "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. + +* In `VideoCapture.tsx`, add code for initializing and destroying some instances. + +```tsx +import { useEffect, useRef } from "react"; +import "../../dynamsoft.config"; +import { DecodedBarcodesResult } from "dynamsoft-barcode-reader"; +import { CameraEnhancer, CameraView } from "dynamsoft-camera-enhancer"; +import { CapturedResultReceiver, CaptureVisionRouter } from "dynamsoft-capture-vision-router"; +import { MultiFrameResultCrossFilter } from "dynamsoft-utility"; +import "./VideoCapture.css"; + +function VideoCapture() { + 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); + + 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); + cameraViewContainer.current!.innerText = ""; + cameraViewContainer.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!.textContent += `${item.formatString}: ${item.text}\n\n`; + } + }; + cvRouter.addResultReceiver(resultReceiver); + + // 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); + + // 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(); + } + }; + + useEffect(() => { + (async () => { + try { + // 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(); + } + } catch (_) {} + })(); + + return () => { + (async () => { + try { + await (pDestroy.current = destroy()); + console.log("VideoCapture Component Unmount"); + } catch (_) {} + })(); + }; + }, []); + + return ( +
+
+ Results: +
+
+
+ ); +} + +export default VideoCapture; +``` + +> Note: +> +> * 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. + +* Define the style of the element in `VideoCapture.css` + +```css +.camera-view-container { + width: 100%; + height: 70vh; +} + +.results { + width: 100%; + height: 10vh; + overflow: auto; +} +``` +### 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. + +* In `ImageCapture.tsx`, add code for initializing and destroying the `CaptureVisionRouter` instance. + +```tsx +import React, { useRef, useEffect, MutableRefObject } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import { BarcodeResultItem } from "dynamsoft-barcode-reader"; +import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; +import "./ImageCapture.css"; + +function ImageCapture() { + const resultsContainer: MutableRefObject = useRef(null); + + 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 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"); + + // Initialize an empty string to hold the decoded barcode texts + let texts = ""; + for (let item of result.items) { + console.log((item as BarcodeResultItem).text); + texts += (item as BarcodeResultItem).text + "\n"; + } + // If the 'texts' string is not empty, display the decoded bacode texts + if (texts !== "") resultsContainer.current!.innerText = texts; + + // 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; + console.error(errMsg); + alert(errMsg); + } + e.target.value = ""; + }; + + useEffect(() => { + (async () => { + try { + // 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(); + } + } catch (_) {} + })(); + + return () => { + try { + (async () => { + await (pDestroy.current = destroy()); + console.log("ImageCapture Component Unmount"); + })(); + } catch (_) {} + }; + }, []); + + return ( +
+
+ +
+
+
+ ); +} + +export default ImageCapture; +``` + +* Define the style of the element in `ImageCapture.css` + +```css +.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; +} + +.image-capture-container .input-container { + width: 80%; + height: 100%; + display: flex; + justify-content: center; + border: 1px solid black; + margin: 0 auto; +} + +.image-capture-container .results { + margin-top: 20px; +} +``` + + +### 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`. + +```tsx +import { useState } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import "./HelloWorld.css"; +import dynamic from "next/dynamic"; + +const VideoCapture = dynamic(() => import("../VideoCapture/VideoCapture"), { + ssr: false, +}); +const ImageCapture = dynamic(() => import("../ImageCapture/ImageCapture"), { + ssr: false, +}); + +enum Modes { + VIDEO_CAPTURE = "video", + IMAGE_CAPTURE = "image", +} + +function HelloWorld() { + const [mode, setMode] = useState(Modes.VIDEO_CAPTURE); + + const showVideoCapture = () => setMode(Modes.VIDEO_CAPTURE); + + const showImageCapture = () => setMode(Modes.IMAGE_CAPTURE); + + return ( +
+

Hello World for Next.js

+
+ + +
+
{mode === Modes.VIDEO_CAPTURE ? : }
+
+ ); +} + +export default HelloWorld; +``` +> Note: +> +> with Next.js' dynamic `import()`, we can significantly improve the initial load speed and performance when we dynamically import Dynamsoft's Barcode Scanning component on-demand. +> +> Additionally, we need to set `{ ssr: false }` since the component is client-side only and could not be rendered server-side. + +* Define the style of the element in `HelloWorld.css` + +```css +.hello-world-page { + 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; + cursor: pointer; +} + +.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 "./App.css"; +import HelloWorld from "./components/HelloWorld/HelloWorld"; + +function App() { + return ( +
+ +
+ ); +} + +export default App; +``` + +* Try running the project. + +```cmd +npm run dev +``` + +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. -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +## Development server -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. +The page will reload if you make edits.\ +You will also see any lint errors in the console. ## Learn More diff --git a/hello-world/next/app/layout.tsx b/hello-world/next/app/layout.tsx index 3314e478..3e561528 100644 --- a/hello-world/next/app/layout.tsx +++ b/hello-world/next/app/layout.tsx @@ -5,8 +5,9 @@ import "./globals.css"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Hello World for Next.js - Dynamsoft Barcode Reader Sample", + description: "Dynamsoft Barcode Reader in a Next.js Application, helps read barcodes from camera or images.", + keywords: "barcodes, camera, images, nextjs", }; export default function RootLayout({ diff --git a/hello-world/next/app/page.tsx b/hello-world/next/app/page.tsx index 9f005fd7..43a5140a 100644 --- a/hello-world/next/app/page.tsx +++ b/hello-world/next/app/page.tsx @@ -1,22 +1,11 @@ "use client"; -import { useState } from 'react'; -import "./page.css"; -import VideoCapture from '@/component/VideoCapture'; -import ImageCapture from '@/component/ImageCapture'; +import HelloWorld from "@/components/HelloWorld/HelloWorld"; export default function Home() { - const [mode, setMode] = useState("video"); return ( -
-
-

Hello World for React

-
-
- - -
- { mode === "video" ? : } +
+
); } diff --git a/hello-world/next/component/ImageCapture.css b/hello-world/next/component/ImageCapture.css deleted file mode 100644 index 4b689a19..00000000 --- a/hello-world/next/component/ImageCapture.css +++ /dev/null @@ -1,18 +0,0 @@ -.capture-img { - width: 100%; - height: 100%; - font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; -} - -.capture-img .img-ipt { - width: 80%; - height: 100%; - display: flex; - justify-content: center; - border: 1px solid black; - margin: 0 auto; -} - -.capture-img .result-area { - margin-top: 20px; -} \ No newline at end of file diff --git a/hello-world/next/component/ImageCapture.tsx b/hello-world/next/component/ImageCapture.tsx deleted file mode 100644 index b51e9f23..00000000 --- a/hello-world/next/component/ImageCapture.tsx +++ /dev/null @@ -1,70 +0,0 @@ -"use client"; - -import { useEffect, useRef, MutableRefObject, useCallback, ChangeEvent } from "react"; -import "../dynamsoft.config"; -import { EnumCapturedResultItemType } from "dynamsoft-core"; -import type { BarcodeResultItem } from "dynamsoft-barcode-reader"; -import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; -import "./ImageCapture.css"; - -export default () => { - const resDiv: MutableRefObject = useRef(null); - - const pCvRouter: MutableRefObject | null> = useRef(null); - const bDestoried = useRef(false); - - const captureImage = useCallback(async(e: ChangeEvent)=>{ - let files = [...(e.target.files as any as File[])]; - e.target.value = ''; - resDiv.current!.innerText = ""; - try { - const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance()); - if (bDestoried.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`; - } - for (let _item of result.items) { - if(_item.type !== EnumCapturedResultItemType.CRIT_BARCODE) { continue; } - let item = _item as BarcodeResultItem; - resDiv.current!.innerText += item.text + "\n"; - console.log(item.text); - } - if (!result.items.length) resDiv.current!.innerText += 'No barcode found\n'; - } - } catch (ex: any) { - let errMsg = ex.message || ex; - console.error(errMsg); - alert(errMsg); - } - }, []); - - useEffect((): any => { - // reset value so works in React.StrictMode - bDestoried.current = false; - // onBeforeUnmount - return async () => { - console.log('destroy???')//debug - bDestoried.current = true; - if(pCvRouter.current){ - try{ - (await pCvRouter.current).dispose(); - }catch(_){} - } - } - }, []); - - return ( -
-
- -
-
-
- ) -}; diff --git a/hello-world/next/component/VideoCapture.css b/hello-world/next/component/VideoCapture.css deleted file mode 100644 index 810b49cb..00000000 --- a/hello-world/next/component/VideoCapture.css +++ /dev/null @@ -1,11 +0,0 @@ -.div-ui-container { - width: 100%; - height: 70vh; - background: #eee; -} - -.div-results-container { - width: 100%; - height: 10vh; - overflow: auto; -} \ No newline at end of file diff --git a/hello-world/next/component/VideoCapture.tsx b/hello-world/next/component/VideoCapture.tsx deleted file mode 100644 index 5ae142e3..00000000 --- a/hello-world/next/component/VideoCapture.tsx +++ /dev/null @@ -1,103 +0,0 @@ -"use client"; - -import { useEffect, useRef, MutableRefObject } from 'react'; -import "../dynamsoft.config"; -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'; - -export default () => { - const uiContainer: MutableRefObject = useRef(null); - const resultsContainer: MutableRefObject = 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{ - // 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 - cameraEnhancer = await CameraEnhancer.createInstance(cameraView); - if(bDestoryed){ throw Error(strErrorDistoryed); } - - // Get default UI and append it to DOM. - uiContainer.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); } - 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'), - ); - } - }}); - - // 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(bDestoryed){ throw Error(strErrorDistoryed); } - - // Open camera and start scanning single barcode. - await cameraEnhancer.open(); - if(bDestoryed){ throw Error(strErrorDistoryed); } - await cvRouter.startCapturing("ReadSingleBarcode"); - if(bDestoryed){ throw Error(strErrorDistoryed); } - - }catch(ex:any){ - - if((ex as Error)?.message === strErrorDistoryed){ - console.log(strErrorDistoryed); - }else{ - let errMsg = ex.message || ex; - console.error(errMsg); - alert(errMsg); - } - } - })(); - - // distroy function will wait pInit - resolveInit!(); - - // onBeforeUnmount - return async () => { - bDestoryed = true; - try{ - await pInit; - cvRouter?.dispose(); - cameraEnhancer?.dispose(); - }catch(_){} - }; - }, []); - - return ( -
-
- Results:
-
-
- ); -} diff --git a/hello-world/next/app/page.css b/hello-world/next/components/HelloWorld/HelloWorld.css similarity index 71% rename from hello-world/next/app/page.css rename to hello-world/next/components/HelloWorld/HelloWorld.css index f3fee6a1..b14b494b 100644 --- a/hello-world/next/app/page.css +++ b/hello-world/next/components/HelloWorld/HelloWorld.css @@ -9,39 +9,30 @@ height: 60px; animation: retate 5s infinite linear; } -.top-btns { +.buttons-container { width: 30%; 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%; + .buttons-container { + width: 70%; } } - -@keyframes retate { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} \ No newline at end of file diff --git a/hello-world/next/components/HelloWorld/HelloWorld.tsx b/hello-world/next/components/HelloWorld/HelloWorld.tsx new file mode 100644 index 00000000..d65fd870 --- /dev/null +++ b/hello-world/next/components/HelloWorld/HelloWorld.tsx @@ -0,0 +1,53 @@ +import { useState } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import "./HelloWorld.css"; +import dynamic from "next/dynamic"; + +const VideoCapture = dynamic(() => import("../VideoCapture/VideoCapture"), { + ssr: false, +}); +const ImageCapture = dynamic(() => import("../ImageCapture/ImageCapture"), { + ssr: false, +}); + +enum Modes { + VIDEO_CAPTURE = "video", + IMAGE_CAPTURE = "image", +} + +function HelloWorld() { + const [mode, setMode] = useState(Modes.VIDEO_CAPTURE); + + const showVideoCapture = () => setMode(Modes.VIDEO_CAPTURE); + + const showImageCapture = () => setMode(Modes.IMAGE_CAPTURE); + + return ( +
+
+

Hello World for Next.js

+
+
+ + +
+
{mode === Modes.VIDEO_CAPTURE ? : }
+
+ ); +} + +export default HelloWorld; diff --git a/hello-world/next/components/ImageCapture/ImageCapture.css b/hello-world/next/components/ImageCapture/ImageCapture.css new file mode 100644 index 00000000..facc69e6 --- /dev/null +++ b/hello-world/next/components/ImageCapture/ImageCapture.css @@ -0,0 +1,19 @@ +.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; +} + +.image-capture-container .input-container { + width: 80%; + height: 100%; + display: flex; + justify-content: center; + border: 1px solid black; + margin: 0 auto; +} + +.image-capture-container .results { + margin-top: 20px; +} diff --git a/hello-world/next/components/ImageCapture/ImageCapture.tsx b/hello-world/next/components/ImageCapture/ImageCapture.tsx new file mode 100644 index 00000000..d4ffc6c7 --- /dev/null +++ b/hello-world/next/components/ImageCapture/ImageCapture.tsx @@ -0,0 +1,83 @@ +import React, { useRef, useEffect, MutableRefObject } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. +import { BarcodeResultItem } from "dynamsoft-barcode-reader"; +import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; +import "./ImageCapture.css"; + +function ImageCapture() { + const resultsContainer: MutableRefObject = useRef(null); + + 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 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"); + + // Initialize an empty string to hold the decoded barcode texts + let texts = ""; + for (let item of result.items) { + console.log((item as BarcodeResultItem).text); + texts += (item as BarcodeResultItem).text + "\n"; + } + // If the 'texts' string is not empty, display the decoded bacode texts + if (texts !== "") resultsContainer.current!.innerText = texts; + + // 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; + console.error(errMsg); + alert(errMsg); + } + e.target.value = ""; + }; + + useEffect(() => { + (async () => { + try { + // 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(); + } + } catch (_) {} + })(); + + return () => { + try { + (async () => { + await (pDestroy.current = destroy()); + console.log("ImageCapture Component Unmount"); + })(); + } catch (_) {} + }; + }, []); + + return ( +
+
+ +
+
+
+ ); +} + +export default ImageCapture; diff --git a/hello-world/next/components/VideoCapture/VideoCapture.css b/hello-world/next/components/VideoCapture/VideoCapture.css new file mode 100644 index 00000000..245b44b7 --- /dev/null +++ b/hello-world/next/components/VideoCapture/VideoCapture.css @@ -0,0 +1,11 @@ +.camera-view-container { + width: 100%; + height: 70vh; + background: #eee; +} + +.results { + width: 100%; + height: 10vh; + overflow: auto; +} diff --git a/hello-world/next/components/VideoCapture/VideoCapture.tsx b/hello-world/next/components/VideoCapture/VideoCapture.tsx new file mode 100644 index 00000000..12c4b08b --- /dev/null +++ b/hello-world/next/components/VideoCapture/VideoCapture.tsx @@ -0,0 +1,117 @@ +import { useEffect, useRef } from "react"; +import "../../dynamsoft.config"; +import { DecodedBarcodesResult } from "dynamsoft-barcode-reader"; +import { CameraEnhancer, CameraView } from "dynamsoft-camera-enhancer"; +import { CapturedResultReceiver, CaptureVisionRouter } from "dynamsoft-capture-vision-router"; +import { MultiFrameResultCrossFilter } from "dynamsoft-utility"; +import "./VideoCapture.css"; + +function VideoCapture() { + 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); + + 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); + cameraViewContainer.current!.innerText = ""; + cameraViewContainer.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!.textContent += `${item.formatString}: ${item.text}\n\n`; + } + }; + cvRouter.addResultReceiver(resultReceiver); + + // 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); + + // 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(); + } + }; + + useEffect(() => { + (async () => { + try { + // 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(); + } + } catch (_) {} + })(); + + return () => { + (async () => { + try { + await (pDestroy.current = destroy()); + console.log("VideoCapture Component Unmount"); + } catch (_) {} + })(); + }; + }, []); + + return ( +
+
+ Results: +
+
+
+ ); +} + +export default VideoCapture; diff --git a/hello-world/next/dynamsoft.config.ts b/hello-world/next/dynamsoft.config.ts index 8a0d59c1..e5303dbc 100644 --- a/hello-world/next/dynamsoft.config.ts +++ b/hello-world/next/dynamsoft.config.ts @@ -22,7 +22,7 @@ CoreModule.engineResourcePaths = { 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/" + 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. diff --git a/hello-world/next/package.json b/hello-world/next/package.json index 6a807bc1..15a52796 100644 --- a/hello-world/next/package.json +++ b/hello-world/next/package.json @@ -1,5 +1,5 @@ { - "name": "read-video-nextjs", + "name": "dbrjs-next-sample", "version": "0.1.0", "private": true, "scripts": { @@ -22,4 +22,4 @@ "eslint": "^8", "eslint-config-next": "14.2.3" } -} +} \ No newline at end of file From c9b911965f38add2d71e1cf683ebbfdb980af1ca Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Wed, 19 Jun 2024 14:34:51 -0700 Subject: [PATCH 2/9] fix: added link to dynamic import components --- hello-world/next/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hello-world/next/README.md b/hello-world/next/README.md index 6719b374..e40e91ef 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -426,6 +426,8 @@ export default HelloWorld; > with Next.js' dynamic `import()`, we can significantly improve the initial load speed and performance when we dynamically import Dynamsoft's Barcode Scanning component on-demand. > > Additionally, we need to set `{ ssr: false }` since the component is client-side only and could not be rendered server-side. +> +> Read more: (https://nextjs.org/learn-pages-router/seo/improve/dynamic-import-components)[https://nextjs.org/learn-pages-router/seo/improve/dynamic-import-components] * Define the style of the element in `HelloWorld.css` From 0752c7c48fadb8d22581df335c52f84b2e2ded74 Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Mon, 24 Jun 2024 09:34:52 -0700 Subject: [PATCH 3/9] fix: update nextjs to show frameworks --- hello-world/next/README.md | 99 ++++--------------- .../components/ImageCapture/ImageCapture.tsx | 4 +- 2 files changed, 19 insertions(+), 84 deletions(-) diff --git a/hello-world/next/README.md b/hello-world/next/README.md index e40e91ef..c30ff5cc 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -1,6 +1,14 @@ # Hello World Sample for Next.js -[Next.js](https://nextjs.org/) is a react framework that enables functionalities such as server-side rendering and geenrating static websites for react-based web applications. Follow this guide to learn how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a Next.js application. Note that in this sample we will use `TypeScript` and define components as classes. +[Next.js](https://nextjs.org/) is a react framework that enables functionalities such as server-side rendering and generating static websites for react-based web applications. Follow this guide to learn how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a Next.js application. + +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 @@ -102,9 +110,9 @@ CoreModule.loadWasm(["DBR"]); ### 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. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). -* In `VideoCapture.tsx`, add code for initializing and destroying some instances. +* In `VideoCapture.tsx`, add code for initializing and destroying some instances. ```tsx import { useEffect, useRef } from "react"; @@ -113,7 +121,6 @@ import { DecodedBarcodesResult } from "dynamsoft-barcode-reader"; import { CameraEnhancer, CameraView } from "dynamsoft-camera-enhancer"; import { CapturedResultReceiver, CaptureVisionRouter } from "dynamsoft-capture-vision-router"; import { MultiFrameResultCrossFilter } from "dynamsoft-utility"; -import "./VideoCapture.css"; function VideoCapture() { const cameraViewContainer = useRef(null); @@ -230,32 +237,17 @@ export default VideoCapture; > > * 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. -* Define the style of the element in `VideoCapture.css` - -```css -.camera-view-container { - width: 100%; - height: 70vh; -} - -.results { - width: 100%; - height: 10vh; - overflow: auto; -} -``` ### 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, MutableRefObject } from "react"; import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. import { BarcodeResultItem } from "dynamsoft-barcode-reader"; import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; -import "./ImageCapture.css"; function ImageCapture() { const resultsContainer: MutableRefObject = useRef(null); @@ -304,8 +296,8 @@ function ImageCapture() { (async () => { try { // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. - if (pDestroy) { - await pDestroy; + if (pDestroy.current) { + await pDestroy.current; pInit.current = init(); } else { pInit.current = init(); @@ -336,34 +328,9 @@ function ImageCapture() { export default ImageCapture; ``` -* Define the style of the element in `ImageCapture.css` - -```css -.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; -} - -.image-capture-container .input-container { - width: 80%; - height: 100%; - display: flex; - justify-content: center; - border: 1px solid black; - margin: 0 auto; -} - -.image-capture-container .results { - margin-top: 20px; -} -``` - - ### 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`. +* Create `HelloWorld.tsx` under "/src/components/HelloWorld/". The `HelloWorld` component offers buttons to switch components between `VideoCapture` and `ImageCapture`. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). * Add following code to `HelloWorld.tsx`. @@ -423,44 +390,12 @@ export default HelloWorld; ``` > Note: > -> with Next.js' dynamic `import()`, we can significantly improve the initial load speed and performance when we dynamically import Dynamsoft's Barcode Scanning component on-demand. +> With Next.js' dynamic `import()`, we can significantly improve the initial load speed and performance when we dynamically import Dynamsoft's Barcode Scanning component on-demand. > > Additionally, we need to set `{ ssr: false }` since the component is client-side only and could not be rendered server-side. > > Read more: (https://nextjs.org/learn-pages-router/seo/improve/dynamic-import-components)[https://nextjs.org/learn-pages-router/seo/improve/dynamic-import-components] -* Define the style of the element in `HelloWorld.css` - -```css -.hello-world-page { - 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; - cursor: pointer; -} - -.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 diff --git a/hello-world/next/components/ImageCapture/ImageCapture.tsx b/hello-world/next/components/ImageCapture/ImageCapture.tsx index d4ffc6c7..4b4e70e5 100644 --- a/hello-world/next/components/ImageCapture/ImageCapture.tsx +++ b/hello-world/next/components/ImageCapture/ImageCapture.tsx @@ -51,8 +51,8 @@ function ImageCapture() { (async () => { try { // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. - if (pDestroy) { - await pDestroy; + if (pDestroy.current) { + await pDestroy.current; pInit.current = init(); } else { pInit.current = init(); From bed6859ebfa70f428ceccaddf57db6b5126e6de8 Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Mon, 24 Jun 2024 09:40:19 -0700 Subject: [PATCH 4/9] add typescript language on the description --- hello-world/next/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hello-world/next/README.md b/hello-world/next/README.md index c30ff5cc..8ffcc099 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -1,6 +1,6 @@ # Hello World Sample for Next.js -[Next.js](https://nextjs.org/) is a react framework that enables functionalities such as server-side rendering and generating static websites for react-based web applications. Follow this guide to learn how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a Next.js application. +[Next.js](https://nextjs.org/) is a react framework that enables functionalities such as server-side rendering and generating static websites for react-based web applications. Follow this guide to learn how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a Next.js application. Note that in this sample, `TypeScript` is used. 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). From e6e40c806cb9f1273fc5e0896a87f969c8f9ee5d Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Mon, 24 Jun 2024 09:43:02 -0700 Subject: [PATCH 5/9] add link to sdk website --- hello-world/next/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hello-world/next/README.md b/hello-world/next/README.md index 8ffcc099..b0545195 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -1,6 +1,6 @@ # Hello World Sample for Next.js -[Next.js](https://nextjs.org/) is a react framework that enables functionalities such as server-side rendering and generating static websites for react-based web applications. Follow this guide to learn how to implement Dynamsoft Barcode Reader JavaScript SDK (hereafter called "the library") into a Next.js application. Note that in this sample, `TypeScript` is used. +[Next.js](https://nextjs.org/) is a react framework that enables functionalities such as server-side rendering and generating static websites for react-based web applications. 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 Next.js application. Note that in this sample, `TypeScript` is used. 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). From 6b873b55416b128d83364e65976b9e185eeecac6 Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Tue, 25 Jun 2024 05:20:43 -0700 Subject: [PATCH 6/9] fix: updated repo to match use-in-frameworks guide --- hello-world/next/README.md | 356 ++++++++---------- .../HelloWorld.css => app/page.css} | 5 - hello-world/next/app/page.tsx | 46 ++- .../next/components/HelloWorld/HelloWorld.tsx | 53 --- .../components/ImageCapture/ImageCapture.tsx | 93 +++-- .../components/VideoCapture/VideoCapture.tsx | 161 ++++---- hello-world/next/dynamsoft.config.ts | 23 +- 7 files changed, 342 insertions(+), 395 deletions(-) rename hello-world/next/{components/HelloWorld/HelloWorld.css => app/page.css} (87%) delete mode 100644 hello-world/next/components/HelloWorld/HelloWorld.tsx diff --git a/hello-world/next/README.md b/hello-world/next/README.md index b0545195..8fcef1a9 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -67,15 +67,27 @@ npm install dynamsoft-barcode-reader-bundle ### Add file "dynamsoft.config.ts" at the root of the app to configure libraries ```typescript +/* /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. @@ -84,18 +96,7 @@ LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"); * LICENSE ALERT - THE END */ -// 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/", -}; - -// Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading. +// Optional. Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading. CoreModule.loadWasm(["DBR"]); ``` @@ -106,117 +107,115 @@ CoreModule.loadWasm(["DBR"]); ### Build directory structure -* Create a directory "components" on the root direction, and then create another three directories "HelloWorld", "VideoCapture" and "ImageCapture" under "/components/". +* Create a directory `components` on the root directory, and then create another two directories, `VideoCapture` and `ImageCapture` under `/components/`. ### Create and edit the `VideoCapture` component -* Create `VideoCapture.tsx` under "/src/components/VideoCapture/". The `VideoCapture` component helps decode barcodes via camera. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). +* Create `VideoCapture.tsx` under `/components/VideoCapture/`. The `VideoCapture` component helps decode barcodes via camera. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). * In `VideoCapture.tsx`, add code for initializing and destroying some instances. ```tsx -import { useEffect, useRef } from "react"; -import "../../dynamsoft.config"; -import { DecodedBarcodesResult } from "dynamsoft-barcode-reader"; +/* /components/VideoCapture/VideoCapture.tsx */ +import React, { useEffect, useRef } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. import { CameraEnhancer, CameraView } from "dynamsoft-camera-enhancer"; -import { CapturedResultReceiver, CaptureVisionRouter } from "dynamsoft-capture-vision-router"; +import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; import { MultiFrameResultCrossFilter } from "dynamsoft-utility"; +const componentDestroyedErrorMsg = "VideoCapture Component Destroyed"; + function VideoCapture() { 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); - cameraViewContainer.current!.innerText = ""; - cameraViewContainer.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!.textContent += `${item.formatString}: ${item.text}\n\n`; - } - }; - cvRouter.addResultReceiver(resultReceiver); - - // 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); - - // 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 () => { try { - // 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(); + // 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 { - pInit.current = init(); + let errMsg = ex.message || ex; + console.error(errMsg); + alert(errMsg); } - } catch (_) {} + } })(); - return () => { - (async () => { - try { - await (pDestroy.current = destroy()); - console.log("VideoCapture Component Unmount"); - } catch (_) {} - })(); + // Resolve pInit promise once initialization is complete. + resolveInit!(); + + return async () => { + isDestroyed = true; + try { + // Wait for the pInit to complete before disposing resources. + await pInit; + cvRouter?.dispose(); + cameraEnhancer?.dispose(); + } catch (_) {} }; }, []); @@ -235,90 +234,84 @@ export default VideoCapture; > Note: > -> * 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. +> * The component should never update so that events bound to the UI stay valid. In this component, 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. ### Create and edit the `ImageCapture` component -* Create `ImageCapture.tsx` under "/src/components/ImageCapture/". The `ImageCapture` component helps decode barcodes in an image. +* Create `ImageCapture.tsx` under `/components/ImageCapture/`. The `ImageCapture` component helps decode barcodes in an image. * 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, MutableRefObject } from "react"; +/* /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"; function ImageCapture() { const resultsContainer: MutableRefObject = useRef(null); - const pInit = useRef(null as null | Promise); - const pDestroy = useRef(null as null | Promise); + let pCvRouter: MutableRefObject | null> = useRef(null); + let isDestroyed = useRef(false); - const init = async (): Promise => { - const cvRouter = await CaptureVisionRouter.createInstance(); - return cvRouter; - }; + 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 destroy = async (): Promise => { - if (pInit.current) { - const cvRouter = (await pInit.current)!; - cvRouter.dispose(); - } - }; - - const decodeImg = async (e: React.ChangeEvent) => { + // ensure cvRouter is created only once try { - const cvRouter = (await pInit.current)!; - // Decode selected image with 'ReadBarcodes_SpeedFirst' template. - const result = await cvRouter.capture(e.target.files![0], "ReadBarcodes_SpeedFirst"); - - // Initialize an empty string to hold the decoded barcode texts - let texts = ""; - for (let item of result.items) { - console.log((item as BarcodeResultItem).text); - texts += (item as BarcodeResultItem).text + "\n"; - } - // If the 'texts' string is not empty, display the decoded bacode texts - if (texts !== "") resultsContainer.current!.innerText = texts; + const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance()); + if (isDestroyed.current) return; - // If no items are found, display that no barcode was detected - if (!result.items.length) resultsContainer.current!.innerText = "No barcode found"; + 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"; + } } catch (ex: any) { let errMsg = ex.message || ex; console.error(errMsg); alert(errMsg); } - e.target.value = ""; - }; + }, []); - useEffect(() => { - (async () => { - try { - // 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(); - } - } catch (_) {} - })(); + useEffect((): any => { + // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. + isDestroyed.current = false; - return () => { - try { - (async () => { - await (pDestroy.current = destroy()); - console.log("ImageCapture Component Unmount"); - })(); - } catch (_) {} + // componentWillUnmount. dispose cvRouter when it's no longer neededs + return async () => { + isDestroyed.current = true; + if (pCvRouter.current) { + try { + (await pCvRouter.current).dispose(); + } catch (_) {} + } }; }, []); return (
- +
@@ -328,22 +321,24 @@ function ImageCapture() { export default ImageCapture; ``` -### Create and edit the `HelloWorld` component +### Add the `VideoCapture` and `ImageCapture` component to `page.tsx` -* Create `HelloWorld.tsx` under "/src/components/HelloWorld/". The `HelloWorld` component offers buttons to switch components between `VideoCapture` and `ImageCapture`. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). +* On `/app/page.tsx`, we will edit the component so that it offers buttons to switch components between `VideoCapture` and `ImageCapture`. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). -* Add following code to `HelloWorld.tsx`. +* Add following code to `page.tsx`. ```tsx -import { useState } from "react"; -import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. -import "./HelloWorld.css"; +/* /app/page.tsx */ +"use client"; + import dynamic from "next/dynamic"; +import { useState } from "react"; +import "./page.css"; -const VideoCapture = dynamic(() => import("../VideoCapture/VideoCapture"), { +const VideoCapture = dynamic(() => import("../components/VideoCapture/VideoCapture"), { ssr: false, }); -const ImageCapture = dynamic(() => import("../ImageCapture/ImageCapture"), { +const ImageCapture = dynamic(() => import("../components/ImageCapture/ImageCapture"), { ssr: false, }); @@ -352,20 +347,20 @@ enum Modes { IMAGE_CAPTURE = "image", } -function HelloWorld() { +export default function Home() { const [mode, setMode] = useState(Modes.VIDEO_CAPTURE); const showVideoCapture = () => setMode(Modes.VIDEO_CAPTURE); - const showImageCapture = () => setMode(Modes.IMAGE_CAPTURE); return (
-

Hello World for Next.js

-
+
+

Hello World for Next.js

+
+
+ +
+
{mode === Modes.VIDEO_CAPTURE ? : }
); } diff --git a/hello-world/next/components/HelloWorld/HelloWorld.tsx b/hello-world/next/components/HelloWorld/HelloWorld.tsx deleted file mode 100644 index d65fd870..00000000 --- a/hello-world/next/components/HelloWorld/HelloWorld.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useState } from "react"; -import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. -import "./HelloWorld.css"; -import dynamic from "next/dynamic"; - -const VideoCapture = dynamic(() => import("../VideoCapture/VideoCapture"), { - ssr: false, -}); -const ImageCapture = dynamic(() => import("../ImageCapture/ImageCapture"), { - ssr: false, -}); - -enum Modes { - VIDEO_CAPTURE = "video", - IMAGE_CAPTURE = "image", -} - -function HelloWorld() { - const [mode, setMode] = useState(Modes.VIDEO_CAPTURE); - - const showVideoCapture = () => setMode(Modes.VIDEO_CAPTURE); - - const showImageCapture = () => setMode(Modes.IMAGE_CAPTURE); - - return ( -
-
-

Hello World for Next.js

-
-
- - -
-
{mode === Modes.VIDEO_CAPTURE ? : }
-
- ); -} - -export default HelloWorld; diff --git a/hello-world/next/components/ImageCapture/ImageCapture.tsx b/hello-world/next/components/ImageCapture/ImageCapture.tsx index 4b4e70e5..c97b0e22 100644 --- a/hello-world/next/components/ImageCapture/ImageCapture.tsx +++ b/hello-world/next/components/ImageCapture/ImageCapture.tsx @@ -1,5 +1,6 @@ -import React, { useRef, useEffect, MutableRefObject } from "react"; +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 "./ImageCapture.css"; @@ -7,73 +8,65 @@ import "./ImageCapture.css"; function ImageCapture() { const resultsContainer: MutableRefObject = useRef(null); - const pInit = useRef(null as null | Promise); - const pDestroy = useRef(null as null | Promise); + let pCvRouter: MutableRefObject | null> = useRef(null); + let isDestroyed = useRef(false); - const init = async (): Promise => { - const cvRouter = await CaptureVisionRouter.createInstance(); - return cvRouter; - }; + 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 destroy = async (): Promise => { - if (pInit.current) { - const cvRouter = (await pInit.current)!; - cvRouter.dispose(); - } - }; - - const decodeImg = async (e: React.ChangeEvent) => { + // ensure cvRouter is created only once try { - const cvRouter = (await pInit.current)!; - // Decode selected image with 'ReadBarcodes_SpeedFirst' template. - const result = await cvRouter.capture(e.target.files![0], "ReadBarcodes_SpeedFirst"); + const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance()); + if (isDestroyed.current) return; - // Initialize an empty string to hold the decoded barcode texts - let texts = ""; - for (let item of result.items) { - console.log((item as BarcodeResultItem).text); - texts += (item as BarcodeResultItem).text + "\n"; - } - // If the 'texts' string is not empty, display the decoded bacode texts - if (texts !== "") resultsContainer.current!.innerText = texts; + for (let file of files) { + // Decode selected image with 'ReadBarcodes_SpeedFirst' template. + const result = await cvRouter.capture(file, "ReadBarcodes_SpeedFirst"); + if (isDestroyed.current) return; - // If no items are found, display that no barcode was detected - if (!result.items.length) resultsContainer.current!.innerText = "No barcode found"; + // 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"; + } } catch (ex: any) { let errMsg = ex.message || ex; console.error(errMsg); alert(errMsg); } - e.target.value = ""; - }; + }, []); - useEffect(() => { - (async () => { - try { - // 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(); - } - } catch (_) {} - })(); + useEffect((): any => { + // In 'development', React runs setup and cleanup one extra time before the actual setup in Strict Mode. + isDestroyed.current = false; - return () => { - try { - (async () => { - await (pDestroy.current = destroy()); - console.log("ImageCapture Component Unmount"); - })(); - } catch (_) {} + // componentWillUnmount. dispose cvRouter when it's no longer neededs + return async () => { + isDestroyed.current = true; + if (pCvRouter.current) { + try { + (await pCvRouter.current).dispose(); + } catch (_) {} + } }; }, []); return (
- +
diff --git a/hello-world/next/components/VideoCapture/VideoCapture.tsx b/hello-world/next/components/VideoCapture/VideoCapture.tsx index 12c4b08b..b98b92e5 100644 --- a/hello-world/next/components/VideoCapture/VideoCapture.tsx +++ b/hello-world/next/components/VideoCapture/VideoCapture.tsx @@ -1,106 +1,103 @@ -import { useEffect, useRef } from "react"; -import "../../dynamsoft.config"; -import { DecodedBarcodesResult } from "dynamsoft-barcode-reader"; +import React, { useEffect, useRef } from "react"; +import "../../dynamsoft.config"; // import side effects. The license, engineResourcePath, so on. import { CameraEnhancer, CameraView } from "dynamsoft-camera-enhancer"; -import { CapturedResultReceiver, CaptureVisionRouter } from "dynamsoft-capture-vision-router"; +import { CaptureVisionRouter } from "dynamsoft-capture-vision-router"; import { MultiFrameResultCrossFilter } from "dynamsoft-utility"; import "./VideoCapture.css"; +const componentDestroyedErrorMsg = "VideoCapture Component Destroyed"; + function VideoCapture() { 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); - cameraViewContainer.current!.innerText = ""; - cameraViewContainer.current!.append(cameraView.getUIElement()); // Get default UI and append it to DOM. + let cvRouter: CaptureVisionRouter; + let cameraEnhancer: CameraEnhancer; - // Create a `CaptureVisionRouter` instance and set `CameraEnhancer` instance as its image source. - const cvRouter = await CaptureVisionRouter.createInstance(); - cvRouter.setInput(cameraEnhancer); + (async () => { + 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); + } - // Define a callback for results. - const resultReceiver = new CapturedResultReceiver(); - resultReceiver.onDecodedBarcodesReceived = (result: DecodedBarcodesResult) => { - if (!result.barcodeResultItems.length) return; + // Get default UI and append it to DOM. + cameraViewContainer.current!.append(cameraView.getUIElement()); - resultsContainer.current!.textContent = ""; - console.log(result); - for (let item of result.barcodeResultItems) { - resultsContainer.current!.textContent += `${item.formatString}: ${item.text}\n\n`; + // Create a `CaptureVisionRouter` instance and set `CameraEnhancer` instance as its image source. + cvRouter = await CaptureVisionRouter.createInstance(); + if (isDestroyed) { + throw Error(componentDestroyedErrorMsg); } - }; - cvRouter.addResultReceiver(resultReceiver); + cvRouter.setInput(cameraEnhancer); - // 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); + // Define a callback for results. + cvRouter.addResultReceiver({ + onDecodedBarcodesReceived: (result) => { + if (!result.barcodeResultItems.length) return; - // 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; - } - }; + resultsContainer.current!.textContent = ""; + console.log(result); + for (let item of result.barcodeResultItems) { + resultsContainer.current!.textContent += `${item.formatString}: ${item.text}\n\n`; + } + }, + }); - const destroy = async (): Promise => { - if (pInit.current) { - const { cameraView, cameraEnhancer, cvRouter } = await pInit.current; - cvRouter.dispose(); - cameraEnhancer.dispose(); - cameraView.dispose(); - } - }; + // 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); + } - useEffect(() => { - (async () => { - try { - // 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(); + // 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 { - pInit.current = init(); + let errMsg = ex.message || ex; + console.error(errMsg); + alert(errMsg); } - } catch (_) {} + } })(); - return () => { - (async () => { - try { - await (pDestroy.current = destroy()); - console.log("VideoCapture Component Unmount"); - } catch (_) {} - })(); + // Resolve pInit promise once initialization is complete. + resolveInit!(); + + return async () => { + isDestroyed = true; + try { + // Wait for the pInit to complete before disposing resources. + await pInit; + cvRouter?.dispose(); + cameraEnhancer?.dispose(); + } catch (_) {} }; }, []); diff --git a/hello-world/next/dynamsoft.config.ts b/hello-world/next/dynamsoft.config.ts index e5303dbc..f31509c2 100644 --- a/hello-world/next/dynamsoft.config.ts +++ b/hello-world/next/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"]); From 22adc6bf0d9474a4618f6dc0555ad4ed6c648861 Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Tue, 25 Jun 2024 11:30:13 -0700 Subject: [PATCH 7/9] fix: updated docs and css --- hello-world/next/README.md | 8 ++++---- hello-world/next/app/page.css | 2 +- hello-world/next/components/ImageCapture/ImageCapture.css | 1 + hello-world/next/components/VideoCapture/VideoCapture.tsx | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/hello-world/next/README.md b/hello-world/next/README.md index 8fcef1a9..d865c00a 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -111,9 +111,9 @@ CoreModule.loadWasm(["DBR"]); ### Create and edit the `VideoCapture` component -* Create `VideoCapture.tsx` under `/components/VideoCapture/`. The `VideoCapture` component helps decode barcodes via camera. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). +* Create `VideoCapture.tsx` under `/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. The `VideoCapture` component helps decode barcodes via camera. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). ```tsx /* /components/VideoCapture/VideoCapture.tsx */ @@ -323,9 +323,9 @@ export default ImageCapture; ### Add the `VideoCapture` and `ImageCapture` component to `page.tsx` -* On `/app/page.tsx`, we will edit the component so that it offers buttons to switch components between `VideoCapture` and `ImageCapture`. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). +* On `/app/page.tsx`, we will edit the component so that it offers buttons to switch components between `VideoCapture` and `ImageCapture`. -* Add following code to `page.tsx`. +* Add following code to `page.tsx`. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). ```tsx /* /app/page.tsx */ diff --git a/hello-world/next/app/page.css b/hello-world/next/app/page.css index 62e3d4f2..580a16f8 100644 --- a/hello-world/next/app/page.css +++ b/hello-world/next/app/page.css @@ -26,7 +26,7 @@ border-left: transparent; } -@media screen and (max-width: 500px) { +@media screen and (max-width: 800px) { .buttons-container { width: 70%; } diff --git a/hello-world/next/components/ImageCapture/ImageCapture.css b/hello-world/next/components/ImageCapture/ImageCapture.css index facc69e6..03da581c 100644 --- a/hello-world/next/components/ImageCapture/ImageCapture.css +++ b/hello-world/next/components/ImageCapture/ImageCapture.css @@ -16,4 +16,5 @@ .image-capture-container .results { margin-top: 20px; + height: 100%; } diff --git a/hello-world/next/components/VideoCapture/VideoCapture.tsx b/hello-world/next/components/VideoCapture/VideoCapture.tsx index b98b92e5..cef0e38d 100644 --- a/hello-world/next/components/VideoCapture/VideoCapture.tsx +++ b/hello-world/next/components/VideoCapture/VideoCapture.tsx @@ -104,8 +104,8 @@ function VideoCapture() { return (
- Results:
+ Results:
); From a66b29d409e00edf27aeeda0c4122554d6f5409c Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Tue, 25 Jun 2024 12:28:43 -0700 Subject: [PATCH 8/9] fix: typo --- hello-world/next/README.md | 5 +++-- hello-world/next/components/ImageCapture/ImageCapture.tsx | 4 ++-- hello-world/next/components/VideoCapture/VideoCapture.tsx | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/hello-world/next/README.md b/hello-world/next/README.md index d865c00a..23b9d578 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -208,6 +208,7 @@ function VideoCapture() { // Resolve pInit promise once initialization is complete. resolveInit!(); + // componentWillUnmount. dispose cvRouter when it's no longer needed return async () => { isDestroyed = true; try { @@ -261,8 +262,8 @@ function ImageCapture() { e.target.value = ""; // reset input resultsContainer.current!.innerText = ""; - // ensure cvRouter is created only once try { + // ensure cvRouter is created only once const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance()); if (isDestroyed.current) return; @@ -297,7 +298,7 @@ function ImageCapture() { // 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 neededs + // componentWillUnmount. dispose cvRouter when it's no longer needed return async () => { isDestroyed.current = true; if (pCvRouter.current) { diff --git a/hello-world/next/components/ImageCapture/ImageCapture.tsx b/hello-world/next/components/ImageCapture/ImageCapture.tsx index c97b0e22..03e52618 100644 --- a/hello-world/next/components/ImageCapture/ImageCapture.tsx +++ b/hello-world/next/components/ImageCapture/ImageCapture.tsx @@ -16,8 +16,8 @@ function ImageCapture() { e.target.value = ""; // reset input resultsContainer.current!.innerText = ""; - // ensure cvRouter is created only once try { + // ensure cvRouter is created only once const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance()); if (isDestroyed.current) return; @@ -52,7 +52,7 @@ function ImageCapture() { // 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 neededs + // componentWillUnmount. dispose cvRouter when it's no longer needed return async () => { isDestroyed.current = true; if (pCvRouter.current) { diff --git a/hello-world/next/components/VideoCapture/VideoCapture.tsx b/hello-world/next/components/VideoCapture/VideoCapture.tsx index cef0e38d..89302c07 100644 --- a/hello-world/next/components/VideoCapture/VideoCapture.tsx +++ b/hello-world/next/components/VideoCapture/VideoCapture.tsx @@ -90,6 +90,7 @@ function VideoCapture() { // Resolve pInit promise once initialization is complete. resolveInit!(); + // componentWillUnmount. dispose cvRouter when it's no longer needed return async () => { isDestroyed = true; try { From 5d5121aca5812a9a5466c39c08e47d193c7e8e17 Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Tue, 25 Jun 2024 15:45:23 -0700 Subject: [PATCH 9/9] add more notes to readme --- hello-world/next/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hello-world/next/README.md b/hello-world/next/README.md index 23b9d578..0feaf2f6 100644 --- a/hello-world/next/README.md +++ b/hello-world/next/README.md @@ -113,7 +113,7 @@ CoreModule.loadWasm(["DBR"]); * Create `VideoCapture.tsx` under `/components/VideoCapture/`. The `VideoCapture` component helps decode barcodes via camera. -* In `VideoCapture.tsx`, add code for initializing and destroying some instances. The `VideoCapture` component helps decode barcodes via camera. For our stylesheet (CSS) specification, please refer to our [source code](#Official-Sample). +* 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 /* /components/VideoCapture/VideoCapture.tsx */ @@ -236,6 +236,7 @@ export default VideoCapture; > Note: > > * The component should never update so that events bound to the UI stay valid. In this component, 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