diff --git a/.claude/skills/dbr-js-sample-creator/SKILL.md b/.claude/skills/dbr-js-sample-creator/SKILL.md new file mode 100644 index 00000000..709dc77f --- /dev/null +++ b/.claude/skills/dbr-js-sample-creator/SKILL.md @@ -0,0 +1,33 @@ +--- +name: dbr-js-sample-creator +description: > + Use when creating JavaScript, TypeScript, or HTML sample code using the Dynamsoft Barcode Reader + SDK (dynamsoft-barcode-reader-bundle npm package or CDN). This skill covers all web barcode + scanning use cases including live camera scanning, image file decoding, single-barcode scanning, + QR codes, 1D barcodes, DataMatrix, PDF417, DPM, and framework integrations (React, Vue, Angular, + Next.js, Nuxt, Svelte, Electron, Capacitor, Blazor, PWA, ES6 modules, plain HTML). + Use this skill whenever the user mentions Dynamsoft Barcode Reader JavaScript, DBR JS, + dynamsoft-barcode-reader-bundle, CaptureVisionRouter in the browser, barcode scanning with + Dynamsoft in a web app, or wants to create a JavaScript/TypeScript sample that scans or decodes + barcodes. +--- + +# DBR JavaScript Sample Creator + +The canonical skill definition lives in `.github/skills/dbr-js-sample-creator/`. + +**Read `.github/skills/dbr-js-sample-creator/SKILL.md` and its `references/` directory now.** + +The `SKILL.md` file contains: +- SDK architecture overview and loading methods (CDN UMD, CDN ES module, npm) +- License initialization patterns and the default public trial key +- Camera scanning and image capture code patterns +- Code style conventions for this repository + +The `references/` directory contains: +- `api-sdk.md` — SDK loading, namespaces, CoreModule, preset templates, BarcodeFormatIds, dynamic settings +- `api-camera.md` — CameraView, CameraEnhancer, tip messages, auto-zoom, drawing layers, audio feedback +- `api-image.md` — File capture, CapturedResult, BarcodeResultItem properties +- `api-frameworks.md` — React, Vue, Angular, Next.js (App Router) complete component patterns +- `api-parsing.md` — CodeParser for GS1, driver's license (AAMVA), VIN parsing +- `sample-patterns.md` — Copy-paste ready code for all common scenarios diff --git a/.codex/skills/dbr-js-sample-creator/SKILL.md b/.codex/skills/dbr-js-sample-creator/SKILL.md new file mode 100644 index 00000000..361d6752 --- /dev/null +++ b/.codex/skills/dbr-js-sample-creator/SKILL.md @@ -0,0 +1,32 @@ +--- +name: dbr-js-sample-creator +description: > + Use when creating JavaScript, TypeScript, or HTML sample code using the Dynamsoft Barcode Reader + SDK (dynamsoft-barcode-reader-bundle npm package or CDN). This skill covers all web barcode + scanning use cases including live camera scanning, image file decoding, single-barcode scanning, + QR codes, 1D barcodes, DataMatrix, PDF417, DPM, and framework integrations (React, Vue, Angular, + Next.js, Nuxt, Svelte, Electron, Capacitor, Blazor, PWA, ES6 modules, plain HTML). + Use this skill whenever the user mentions Dynamsoft Barcode Reader JavaScript, DBR JS, + dynamsoft-barcode-reader-bundle, CaptureVisionRouter in the browser, barcode scanning with + Dynamsoft in a web app, or wants to create a JavaScript/TypeScript sample that scans or decodes + barcodes. +--- + +# DBR JavaScript Sample Creator + +The canonical skill definition lives in `.github/skills/dbr-js-sample-creator/`. + +**Read `.github/skills/dbr-js-sample-creator/SKILL.md` and its `references/` directory now.** + +The `SKILL.md` file contains: +- SDK architecture overview and loading methods (CDN UMD, CDN ES module, npm) +- License initialization patterns and the default public trial key +- Camera scanning and image capture code patterns +- Code style conventions for this repository + +The `references/` directory contains: +- `api-sdk.md` — SDK loading, namespaces, CoreModule, preset templates +- `api-camera.md` — CameraView, CameraEnhancer, result receiver, dedup filter +- `api-image.md` — File capture, CapturedResult, BarcodeResultItem properties +- `api-frameworks.md` — React, Vue, Angular complete component patterns +- `sample-patterns.md` — Copy-paste ready code for all common scenarios diff --git a/.github/skills/dbr-js-sample-creator/README.md b/.github/skills/dbr-js-sample-creator/README.md new file mode 100644 index 00000000..ea74011a --- /dev/null +++ b/.github/skills/dbr-js-sample-creator/README.md @@ -0,0 +1,86 @@ +# DBR JavaScript Sample Creator — AI Coding Skill + +An AI coding skill that helps developers quickly build working web applications with the +[Dynamsoft Barcode Reader JavaScript Edition](https://www.dynamsoft.com/barcode-reader/sdk-javascript/) +SDK. Feed this skill to your AI agent (GitHub Copilot, Claude, Cursor, Windsurf, etc.) and +describe what you need — the agent will generate correct, production-ready code. + +## Who Is This For? + +Developers evaluating or integrating the `dynamsoft-barcode-reader-bundle` JavaScript SDK who want +to **accelerate POC development** using AI coding assistants. Instead of reading through +documentation and samples manually, let your AI agent do the heavy lifting with full SDK knowledge +built in. + +## No Installation Required + +This skill is organized into agent-specific directories so it is **auto-discovered** with no +configuration needed: + +| Directory | Agent | +|---|---| +| `.github/skills/dbr-js-sample-creator/` | GitHub Copilot (auto-discovered) | +| `.claude/skills/dbr-js-sample-creator/` | Claude Code (auto-discovered) | +| `.codex/skills/dbr-js-sample-creator/` | OpenAI Codex (auto-discovered) | + +Just clone the repository and open it in your AI-enabled editor. The skill is active immediately. + +## What's Inside + +``` +.github/skills/dbr-js-sample-creator/ +├── SKILL.md # Entry point — SDK architecture, patterns, conventions +├── README.md # This file +├── references/ +│ ├── api-sdk.md # SDK loading (CDN/npm/ES6), namespaces, CoreModule, templates +│ ├── api-camera.md # CameraView, CameraEnhancer, result receiver, filter +│ ├── api-image.md # File/image capture, CapturedResult, BarcodeResultItem +│ ├── api-frameworks.md # React, Vue, Angular integration patterns +│ └── sample-patterns.md # Complete working code for every common scenario +└── evals/ + └── evals.json # Test prompts & expectations for skill validation + +.claude/skills/dbr-js-sample-creator/SKILL.md # Claude Code entry point +.codex/skills/dbr-js-sample-creator/SKILL.md # Codex entry point +``` + +The AI agent reads `SKILL.md` first, which directs it to the appropriate reference files based on +your task. You do not need to understand these files — they are written for the AI. + +## SDK at a Glance + +- **Package:** `dynamsoft-barcode-reader-bundle@11.4.2001` +- **CDN:** `https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader-bundle@11.4.2001/dist/dbr.bundle.js` +- **npm:** `npm install dynamsoft-barcode-reader-bundle` +- **Trial license:** [Get a 30-day free trial](https://www.dynamsoft.com/customer/license/trialLicense?product=dbr&package=js&utm_source=sampleReadme) + +## Example Prompts + +Once the skill is active, try prompts like: + +| Prompt | What the AI generates | +|---|---| +| *"Create a plain HTML barcode scanner with camera"* | Full HTML with CDN UMD, camera pipeline, dedup filter | +| *"Build a React component that scans barcodes from camera"* | TypeScript React component with proper lifecycle + cleanup | +| *"Make a Vue 3 image barcode decoder"* | Vue SFC with ` +``` + +All APIs are on the global `Dynamsoft` object: +- `Dynamsoft.License.LicenseManager` +- `Dynamsoft.CVR.CaptureVisionRouter`, `Dynamsoft.CVR.CapturedResultReceiver` +- `Dynamsoft.DCE.CameraView`, `Dynamsoft.DCE.CameraEnhancer` +- `Dynamsoft.Utility.MultiFrameResultCrossFilter` +- `Dynamsoft.Core.CoreModule`, `Dynamsoft.Core.EnumCapturedResultItemType` + +### Method 2: CDN ES module (` + + +``` + +### ImageCapture Component (Vue) + +```vue + + + +``` + +--- + +## Angular + +The Angular pattern mirrors React/Vue with `ngAfterViewInit` / `ngOnDestroy` lifecycle hooks and +`@ViewChild` for the camera container: + +```ts +import { Component, AfterViewInit, OnDestroy, ViewChild, ElementRef } from "@angular/core"; +import "../../dynamsoft.config"; +import { CameraView, CameraEnhancer, CaptureVisionRouter, MultiFrameResultCrossFilter } from "dynamsoft-barcode-reader-bundle"; + +@Component({ selector: "app-video-capture", template: ` +
+
{{ resultText }}
+` }) +export class VideoCaptureComponent implements AfterViewInit, OnDestroy { + @ViewChild("cameraViewContainer") cameraViewContainer!: ElementRef; + resultText = ""; + private cvRouter!: CaptureVisionRouter; + private cameraEnhancer!: CameraEnhancer; + private isDestroyed = false; + + async ngAfterViewInit() { + const cameraView = await CameraView.createInstance(); + this.cameraEnhancer = await CameraEnhancer.createInstance(cameraView); + this.cameraViewContainer.nativeElement.append(cameraView.getUIElement()); + + this.cvRouter = await CaptureVisionRouter.createInstance(); + this.cvRouter.setInput(this.cameraEnhancer); + + await this.cvRouter.addResultReceiver({ + onDecodedBarcodesReceived: (result) => { + if (!result.barcodeResultItems.length) return; + this.resultText = result.barcodeResultItems.map(i => `${i.formatString}: ${i.text}`).join("\n"); + }, + }); + + const filter = new MultiFrameResultCrossFilter(); + filter.enableResultCrossVerification("barcode", true); + filter.enableResultDeduplication("barcode", true); + await this.cvRouter.addResultFilter(filter); + + await this.cameraEnhancer.open(); + cameraView.setScanLaserVisible(true); + await this.cvRouter.startCapturing("ReadBarcodes_SpeedFirst"); + } + + ngOnDestroy() { + this.isDestroyed = true; + this.cvRouter?.dispose(); + this.cameraEnhancer?.dispose(); + } +} +``` + +--- + +## Next.js (App Router) + +The Dynamsoft SDK is entirely client-side (WASM). Next.js App Router uses **server components +by default**, so you must explicitly opt into client rendering for any component that touches +the SDK. + +### Architecture: Server vs Client Components + +```text +app/ +├── layout.tsx ← server component (navigation, metadata) — no SDK imports +├── page.tsx ← server component (home page) — no SDK imports +├── scanner/ +│ └── page.tsx ← "use client" wrapper with dynamic import +└── upload/ + └── page.tsx ← "use client" wrapper with dynamic import +components/ +├── VideoCapture.tsx ← "use client" — SDK camera scanning logic +└── ImageCapture.tsx ← "use client" — SDK image decoding logic +dynamsoft.config.ts ← SDK init (client-side only, imported by components) +public/ +└── MyTemplate.json ← custom templates served as static files +``` + +**Rule:** `layout.tsx` and top-level `page.tsx` stay as server components. Only the page +wrappers that load SDK components need `"use client"`. + +### Page Wrapper Pattern — `next/dynamic` with `ssr: false` + +The SDK accesses browser APIs (`navigator`, `document`, WASM). Import SDK components with +`next/dynamic` to prevent server-side rendering: + +```tsx +// app/scanner/page.tsx +"use client"; + +import dynamic from "next/dynamic"; + +const VideoCapture = dynamic(() => import("../../components/VideoCapture"), { + ssr: false, +}); + +export default function ScannerPage() { + return ( +
+

Camera Scanner

+ +
+ ); +} +``` + +```tsx +// app/upload/page.tsx +"use client"; + +import dynamic from "next/dynamic"; + +const ImageCapture = dynamic(() => import("../../components/ImageCapture"), { + ssr: false, +}); + +export default function UploadPage() { + return ( +
+

Image Upload

+ +
+ ); +} +``` + +### SDK Components + +The actual `VideoCapture` and `ImageCapture` components are **identical to the React +patterns** shown above, with one addition — add `"use client"` at the top of each file: + +```tsx +// components/VideoCapture.tsx +"use client"; + +import { useEffect, useRef, useState } from "react"; +import "../dynamsoft.config"; +import { CameraEnhancer, CameraView, CaptureVisionRouter, MultiFrameResultCrossFilter } from "dynamsoft-barcode-reader-bundle"; + +// ... rest is identical to the React VideoCapture pattern +``` + +### Custom Templates in Next.js + +Place template JSON files in `public/` — they're served at the web root: + +```text +public/ReadPDF417.json → accessible at "/ReadPDF417.json" +``` + +Load in your component: + +```ts +await cvRouter.initSettings("/ReadPDF417.json"); +await cvRouter.startCapturing("ReadPDF417_SpeedFirst"); // template name from JSON +``` + +### Server Component Layout (no SDK) + +```tsx +// app/layout.tsx — stays as a server component +import type { Metadata } from "next"; +import Link from "next/link"; + +export const metadata: Metadata = { + title: "Barcode Scanner — Dynamsoft", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + ); +} +``` + +### Next.js Key Rules + +1. **Never import SDK packages in server components** — they access browser globals. +2. **Always use `dynamic()` with `ssr: false`** for components that import from + `dynamsoft-barcode-reader-bundle`. +3. **`dynamsoft.config.ts` is client-only** — import it inside `"use client"` components only. +4. **Custom templates go in `public/`** and are referenced by absolute URL path. +5. **Everything else follows the React pattern** — `useEffect`, `isDestroyed`, `pInit`, + `useRef`, cleanup/dispose. + +--- + +## Key Framework Rules + +1. **`isDestroyed` flag** — Set to `true` in cleanup. Check after every `await` before touching state + or DOM. This prevents React StrictMode double-invoke issues and Vue/Angular unmount race conditions. +2. **`pInit` promise** — Created before async work begins. Awaited in cleanup before `dispose()`. + Ensures resources are not disposed mid-initialization. +3. **Camera container ref** — Always a `ref` / `@ViewChild` / `useRef` pointing to a DOM element. + Never use `document.querySelector` inside framework components. +4. **`dynamsoft.config.ts` is a side effect** — Import it at the top of any component that uses the + SDK. Multiple imports are fine — the module is only evaluated once. +5. **Dispose order** — Always `cvRouter.dispose()` first, then `cameraEnhancer.dispose()`. diff --git a/.github/skills/dbr-js-sample-creator/references/api-image.md b/.github/skills/dbr-js-sample-creator/references/api-image.md new file mode 100644 index 00000000..52dc87d8 --- /dev/null +++ b/.github/skills/dbr-js-sample-creator/references/api-image.md @@ -0,0 +1,209 @@ +# DBR JavaScript Sample Creator — API Reference: Image / File Capture + +--- + +## CaptureVisionRouter.capture() + +Decodes barcodes from a single image synchronously (returns a Promise). + +```ts +const result = await cvRouter.capture(source, template); +``` + +**`source` accepts:** +- `File` — from `` +- `Blob` — binary data blob +- `string` — URL to an image file +- `HTMLImageElement` — an `` DOM element +- `HTMLCanvasElement` — a canvas element +- `HTMLVideoElement` — a video frame + +**`template`** — preset string or custom template name (see `api-sdk.md`). + +For image files, use `"ReadBarcodes_ReadRateFirst"` (accuracy-first) unless speed matters more. + +--- + +## CapturedResult (from `capture()`) + +```ts +const result = await cvRouter.capture(file, "ReadBarcodes_ReadRateFirst"); + +// Access barcode results — preferred approach +const barcodeItems = result.decodedBarcodesResult?.barcodeResultItems ?? []; + +// Alternative: iterate result.items and filter by type +for (let item of result.items) { + if (item.type !== EnumCapturedResultItemType.CRIT_BARCODE) continue; + const barcodeItem = item as BarcodeResultItem; // TypeScript + console.log(barcodeItem.formatString, barcodeItem.text); +} + +// Error info +result.errorCode // 0 = success +result.errorString // description +``` + +--- + +## BarcodeResultItem Properties + +```ts +item.text // string — decoded barcode content +item.formatString // string — e.g., "QR_CODE", "CODE_128", "EAN_13", "PDF_417" +item.format // number — numeric format enum (EnumBarcodeFormat) +item.location // Quadrilateral — four corner points of the barcode +item.location.points // [Point, Point, Point, Point] +item.confidence // number — confidence score (0–100) +``` + +--- + +## Handling No Results + +Always check for empty results to give feedback to the user: + +```js +const items = result.decodedBarcodesResult?.barcodeResultItems ?? []; +if (!items.length) { + outputDiv.innerText = "No barcode found"; + return; +} +``` + +--- + +## File Input Pattern (Plain HTML) + +```html + + +
+ + +``` + +--- + +## Multiple File Decoding + +```js +document.querySelector("#input-file").addEventListener("change", async function (e) { + const files = [...e.target.files]; + this.value = ""; + resultsDiv.innerText = ""; + + const { cvRouter } = await pInit; + + for (let file of files) { + if (files.length > 1) resultsDiv.innerText += `\n${file.name}:\n`; + + const result = await cvRouter.capture(file, "ReadBarcodes_ReadRateFirst"); + const items = result.decodedBarcodesResult?.barcodeResultItems ?? []; + + if (!items.length) { + resultsDiv.innerText += "No barcode found\n"; + continue; + } + for (let item of items) { + resultsDiv.innerText += `${item.formatString}: ${item.text}\n`; + } + } +}); +``` + +--- + +## Creating `CaptureVisionRouter` Once vs Per-Request + +**Create once, reuse for every decode** — router creation is expensive: + +```js +// Good: create once, use many times +const pInit = CaptureVisionRouter.createInstance(); + +async function decodeFile(file) { + const cvRouter = await pInit; + return cvRouter.capture(file, "ReadBarcodes_ReadRateFirst"); +} +``` + +**In React/Vue:** use a ref to hold the pending promise and check `isDestroyed` before use: + +```ts +// React +let pCvRouter = useRef | null>(null); +const cvRouter = await (pCvRouter.current = pCvRouter.current || CaptureVisionRouter.createInstance()); + +// Vue +let pCvRouter: Promise; +const cvRouter = await (pCvRouter = pCvRouter || CaptureVisionRouter.createInstance()); +``` + +--- + +## Disposal (Image Capture) + +For image-only workflows (no camera), dispose the router in component teardown: + +```ts +// React +useEffect(() => { + return () => { + isDestroyed.current = true; + pCvRouter.current?.then(r => r.dispose()).catch(() => {}); + }; +}, []); + +// Vue +onBeforeUnmount(async () => { + isDestroyed = true; + if (pCvRouter) { + try { (await pCvRouter).dispose(); } catch (_) {} + } +}); +``` + +--- + +## Accepted Image Formats + +The SDK accepts the same formats as the browser's ``: + +``` +.jpg, .jpeg, .png, .bmp, .gif, .svg, .webp, .ico +``` + +Use `accept=".jpg,.jpeg,.icon,.gif,.svg,.webp,.png,.bmp"` on file inputs (matches existing samples). diff --git a/.github/skills/dbr-js-sample-creator/references/api-parsing.md b/.github/skills/dbr-js-sample-creator/references/api-parsing.md new file mode 100644 index 00000000..f2b253fa --- /dev/null +++ b/.github/skills/dbr-js-sample-creator/references/api-parsing.md @@ -0,0 +1,192 @@ +# DBR JavaScript Sample Creator — API Reference: Code Parsing (GS1, Driver's License, VIN) + +The `dynamsoft-code-parser-bundle` or `dynamsoft-barcode-reader-bundle` includes a **CodeParser** +module that extracts structured fields from barcode content — e.g., GS1 Application Identifiers, +AAMVA driver's license fields, or VIN components. + +--- + +## When to Use CodeParser + +Use CodeParser when you need to **parse the decoded barcode text** into structured fields: + +| Use case | Spec to load | Template | +|---|---|---| +| GS1 barcodes (retail, logistics) | `"GS1_AI"` | `"ReadAndParseGS1"` or custom | +| US/Canada driver's license (PDF417) | `"AAMVA_DL_ID"` | `"ReadAndParseDriversLicense"` or custom | +| South Africa driver's license | `"SOUTH_AFRICA_DL"` | custom | +| Vehicle Identification Number | `"VIN"` | `"ReadAndParseVIN"` or custom | + +--- + +## Loading Parser Specifications + +Load specs **before** creating the CodeParser instance. This is typically done alongside +`CoreModule.loadWasm()` in your initialization: + +```js +// UMD +Dynamsoft.DCP.CodeParserModule.loadSpec("GS1_AI"); + +// ES module / npm +import { CodeParserModule } from "dynamsoft-barcode-reader-bundle"; +CodeParserModule.loadSpec("GS1_AI"); + +// Load multiple specs at once +CodeParserModule.loadSpec(["AAMVA_DL_ID", "VIN"]); +``` + +In `dynamsoft.config.ts` for framework projects: + +```ts +import { CoreModule, LicenseManager, CodeParserModule } from "dynamsoft-barcode-reader-bundle"; + +CoreModule.engineResourcePaths.rootDirectory = "https://cdn.jsdelivr.net/npm/"; +LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"); + +CoreModule.loadWasm(); +CodeParserModule.loadSpec("GS1_AI"); +``` + +--- + +## CodeParser API + +```ts +// Create a parser instance +const parser = await CodeParser.createInstance(); +// (UMD: await Dynamsoft.DCP.CodeParser.createInstance()) + +// Parse barcode content — accepts string, Uint8Array, or number[] +const parsedItem = await parser.parse(barcodeItem.bytes); +// or: await parser.parse(barcodeItem.text); + +// Access parsed result +parsedItem.codeType // string — e.g., "GS1_AI", "AAMVA_DL_ID", "VIN" +parsedItem.jsonString // string — full parsed result as JSON +parsedItem.getFieldValue("fieldName") // string — get a specific field value + +// Dispose when done +parser.dispose(); +``` + +### Parsing from Bytes vs Text + +- **`barcodeItem.bytes`** (Uint8Array) — preferred; preserves raw data including non-printable + characters and GS separators. +- **`barcodeItem.text`** (string) — works for most cases but may lose binary data. + +--- + +## GS1 Parsing Pattern (UMD) + +```js +Dynamsoft.License.LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"); +Dynamsoft.DCP.CodeParserModule.loadSpec("GS1_AI"); + +(async () => { + const cameraView = await Dynamsoft.DCE.CameraView.createInstance(); + const cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(cameraView); + const cvRouter = await Dynamsoft.CVR.CaptureVisionRouter.createInstance(); + const parser = await Dynamsoft.DCP.CodeParser.createInstance(); + + cvRouter.setInput(cameraEnhancer); + + const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver(); + resultReceiver.onDecodedBarcodesReceived = async (result) => { + if (!result.barcodeResultItems.length) return; + + for (let item of result.barcodeResultItems) { + try { + const parsed = await parser.parse(item.bytes); + console.log("Code type:", parsed.codeType); + console.log("Parsed JSON:", parsed.jsonString); + + // Access individual fields + const parsedObj = JSON.parse(parsed.jsonString); + for (let field of Object.keys(parsedObj)) { + console.log(`${field}: ${parsedObj[field]}`); + } + } catch (ex) { + // Not a parseable format — display raw text + console.log("Raw:", item.formatString, item.text); + } + } + }; + await cvRouter.addResultReceiver(resultReceiver); + + document.querySelector("#camera-container").append(cameraView.getUIElement()); + await cameraEnhancer.open(); + await cvRouter.startCapturing("ReadBarcodes_SpeedFirst"); +})(); +``` + +--- + +## Driver's License Parsing Pattern + +```js +Dynamsoft.DCP.CodeParserModule.loadSpec("AAMVA_DL_ID"); + +// ... (camera setup same as above) + +resultReceiver.onDecodedBarcodesReceived = async (result) => { + if (!result.barcodeResultItems.length) return; + + for (let item of result.barcodeResultItems) { + // Driver's licenses are typically PDF417 barcodes + if (item.formatString !== "PDF_417") continue; + + try { + const parsed = await parser.parse(item.bytes); + const data = JSON.parse(parsed.jsonString); + + // Common AAMVA fields + console.log("Name:", data.lastName, data.firstName); + console.log("DOB:", data.dateOfBirth); + console.log("License #:", data.licenseNumber); + console.log("Expiry:", data.expirationDate); + console.log("State:", data.jurisdictionCode); + } catch (ex) { + console.log("Parse failed — raw text:", item.text); + } + } +}; +``` + +--- + +## Using onParsedResultsReceived (Alternative) + +Instead of manually calling `parser.parse()`, you can use a template that includes parsing +and receive parsed results through `onParsedResultsReceived`: + +```js +const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver(); + +// Barcode results +resultReceiver.onDecodedBarcodesReceived = (result) => { + for (let item of result.barcodeResultItems) { + console.log("Barcode:", item.formatString, item.text); + } +}; + +// Parsed results (from templates that include parsing) +resultReceiver.onParsedResultsReceived = (result) => { + if (!result.parsedResultItems?.length) return; + for (let item of result.parsedResultItems) { + console.log("Parsed:", item.codeType, item.jsonString); + } +}; + +await cvRouter.addResultReceiver(resultReceiver); +``` + +--- + +## Disposal + +```js +parser?.dispose(); +// Plus the usual cvRouter and cameraEnhancer disposal +``` diff --git a/.github/skills/dbr-js-sample-creator/references/api-sdk.md b/.github/skills/dbr-js-sample-creator/references/api-sdk.md new file mode 100644 index 00000000..2d4abc91 --- /dev/null +++ b/.github/skills/dbr-js-sample-creator/references/api-sdk.md @@ -0,0 +1,304 @@ +# DBR JavaScript Sample Creator — API Reference: SDK Loading & Core + +**SDK version:** `dynamsoft-barcode-reader-bundle@11.4.2001` + +--- + +## Loading the SDK + +### UMD Bundle (Plain HTML, no build step) + +```html + + + + + +``` + +All classes are available as globals under the `Dynamsoft` namespace after the script loads. + +### ES Module Bundle (HTML with ` + + +

Hello World (Scan Barcode via Camera)

+
+
+ + + + + + +``` + +--- + +## Pattern 2: Scan a Single Barcode (stop-on-first) + +Source: `scan-a-single-barcode.html` + +Key differences from Pattern 1: +- Shows a scan button; camera is hidden until button clicked. +- Stops after the first barcode is found and alerts the text. +- Uses `"ReadSingleBarcode"` template. + +```html + +
+ + +``` + +--- + +## Pattern 3: Read Barcodes from Image File + +Source: `read-an-image.html` + +```html + + +
+ + +``` + +--- + +## Pattern 4: ES6 Module (import from CDN .mjs) + +Source: `frameworks/es6/es6.html` + +```html + +``` + +--- + +## Pattern 5: Custom JSON Template (Scan QR Codes Only) + +Source: `scenarios/scan-qr-code/` + +Load a custom template JSON and use it by name: + +```js +Dynamsoft.License.LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"); +(async () => { + try { + const cameraView = await Dynamsoft.DCE.CameraView.createInstance(); + const cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance(cameraView); + const cvRouter = await Dynamsoft.CVR.CaptureVisionRouter.createInstance(); + + cvRouter.setInput(cameraEnhancer); + + const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver(); + resultReceiver.onDecodedBarcodesReceived = (result) => { + for (let item of result.barcodeResultItems) { + console.log(item.formatString, item.text); + } + }; + await cvRouter.addResultReceiver(resultReceiver); + + // Load custom template from JSON file, then use the template name from the JSON + await cvRouter.initSettings("./ReadQR.json"); + + document.querySelector(".barcode-scanner-view").append(cameraView.getUIElement()); + await cameraEnhancer.open(); + await cvRouter.startCapturing("ReadQR"); // name matches the template in ReadQR.json + } catch (ex) { + alert(ex.message || ex); + } +})(); +``` + +Minimal custom JSON template to read only QR codes: +```json +{ + "CaptureVisionTemplates": [ + { "Name": "ReadQR", "ImageROIProcessingNameArray": ["ROI_QR"], "Timeout": 1000 } + ], + "TargetROIDefOptions": [ + { "Name": "ROI_QR", "TaskSettingNameArray": ["Task_QR"] } + ], + "BarcodeReaderTaskSettingOptions": [ + { "Name": "Task_QR", "BarcodeFormatIds": ["BF_QR_CODE"], "ExpectedBarcodesCount": 1 } + ] +} +``` + +--- + +## Pattern 6: React VideoCapture (npm, TypeScript) + +Source: `frameworks/react/src/components/VideoCapture/VideoCapture.tsx` + +See full code in `references/api-frameworks.md` → "React → VideoCapture Component". + +**Critical React-specific rules:** +- Use `useEffect` with a cleanup return function. +- Set `isDestroyed = true` in the cleanup, check it after every `await`. +- Create a `pInit` Promise before the async IIFE; resolve it at the end of the IIFE. +- In cleanup, `await pInit` before calling `dispose()` to avoid disposing mid-init. +- Use `useRef` for the camera container DOM element. + +--- + +## Pattern 7: React ImageCapture (npm, TypeScript) + +Source: `frameworks/react/src/components/ImageCapture/ImageCapture.tsx` + +See full code in `references/api-frameworks.md` → "React → ImageCapture Component". + +**Critical points:** +- Use a `useRef | null>(null)` to lazily create and cache the router. +- Reset `this.value = ""` on the file input so the same file can be re-selected. +- Use `EnumCapturedResultItemType.CRIT_BARCODE` to filter `result.items`. + +--- + +## Pattern 8: Vue 3 VideoCapture (npm, TypeScript) + +Source: `frameworks/vue/src/components/VideoCapture.vue` + +See full code in `references/api-frameworks.md` → "Vue 3 → VideoCapture Component". + +**Key Vue differences vs React:** +- `onMounted` replaces `useEffect`, `onBeforeUnmount` replaces the cleanup return. +- Module-level `let isDestroyed = false` (not a ref) — works because Vue components are instances. +- `resultText` is a `ref("")` — access/set via `.value`. +- Camera container is a template ref: `const cameraViewContainer = ref(null)`. +- Access DOM via `cameraViewContainer.value!.append(...)`. + +--- + +## Common Mistakes to Avoid + +| Mistake | Fix | +|---|---| +| Calling `await LicenseManager.initLicense(...)` | Remove `await` — it's synchronous | +| Creating `CaptureVisionRouter` inside event handlers | Create once at init, reuse | +| Forgetting `isDestroyed` check after `await` in React | Add check after every `await` in component | +| Using `document.querySelector` inside Vue/React components | Use refs instead | +| Not resetting `e.target.value = ""` on file input | Reset so same file can be re-selected | +| Not calling `cameraView.getUIElement()` and appending to DOM | Required before `open()` | +| Calling `startCapturing` before `setInput` | Always `setInput` first | +| Forgetting `CoreModule.engineResourcePaths.rootDirectory` in npm/ES module projects | Add to `dynamsoft.config.ts` | diff --git a/README.md b/README.md index 81601dcd..46f88512 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,36 @@ The repository includes two main sample directories: --- +## Vibe Coding with AI Agents + +This repository ships an **AI coding skill** that gives GitHub Copilot, Claude, and Codex deep knowledge of the Dynamsoft Barcode Reader JavaScript SDK — so you can describe what you want and get working code instantly. + +### How It Works + +The skill lives in: + +``` +.github/skills/dbr-js-sample-creator/ ← GitHub Copilot (auto-discovered) +.claude/skills/dbr-js-sample-creator/ ← Claude Code (auto-discovered) +.codex/skills/dbr-js-sample-creator/ ← OpenAI Codex (auto-discovered) +``` + +No installation or configuration needed — just clone the repo and open it in your AI-enabled editor. + +### Example Prompts + +Open your AI agent (e.g. GitHub Copilot Chat in agent mode) and try: + +- *"Create a plain HTML barcode scanner that reads from camera and shows results"* +- *"Build a React component that scans barcodes with proper cleanup"* +- *"Make a Vue 3 page that decodes barcodes from an uploaded image"* +- *"Write an ES6 module barcode scanner using the CDN .mjs bundle"* +- *"Scan QR codes only using a custom JSON template"* + +The agent will automatically load the skill and generate correct, production-ready code using the right SDK version, license key, API patterns, and framework conventions — without you needing to read any documentation. + +--- + ## Documentation For the developer guide and full API reference of Dynamsoft Barcode Reader JavaScript library, please check out the [documentation](https://www.dynamsoft.com/barcode-reader/docs/web/programming/javascript/?ver=11.4.2001&utm_source=sampleReadme). diff --git a/scenarios/scan-and-search/index.css b/scenarios/scan-and-search/index.css index cbbce166..2507e411 100644 --- a/scenarios/scan-and-search/index.css +++ b/scenarios/scan-and-search/index.css @@ -12,32 +12,130 @@ body { align-items: center; flex-direction: column; min-height: 100vh; - padding: 10px; + padding: 20px 10px; margin: 0; } -#container { - display: flex; - align-items: center; +/* ── Camera view (always visible, inline) ────────────────────────────────── */ + +#barcode-scanner-view { + position: relative; width: 100%; max-width: 400px; + height: 240px; + margin-bottom: 8px; + border-radius: 8px; + overflow: hidden; + background: #111; } -#scan-btn { - width: 48px; - height: 48px; - background-color: #ff5000; - border: none; - border-radius: 6px 0 0 6px; +/* Transparent overlay that catches clicks for play/pause */ +#camera-toggle-area { + position: absolute; + inset: 0; display: flex; align-items: center; justify-content: center; cursor: pointer; - transition: background-color 0.3s ease; + z-index: 10; } -#scan-btn:hover { - background-color: #e54c00; +/* Play/pause icon — always visible when paused, shown on hover when playing */ +#play-pause-icon { + width: 52px; + height: 52px; + border-radius: 50%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; +} + +/* Loading spinner — shown while SDK initialises */ +#loading-spinner { + display: none; + width: 48px; + height: 48px; + border: 4px solid rgba(255, 255, 255, 0.85); + border-right-color: transparent; + border-radius: 50%; + animation: spin 0.9s linear infinite; + pointer-events: none; +} + +#camera-toggle-area.loading { + cursor: default; +} + +#camera-toggle-area.loading #loading-spinner { + display: block; +} + +#camera-toggle-area.loading #play-pause-icon { + display: none; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Pause symbol (two bars) — shown when playing */ +#play-pause-icon::before, +#play-pause-icon::after { + content: ""; + display: block; + width: 5px; + height: 18px; + background: #fff; + border-radius: 2px; + position: absolute; +} + +#play-pause-icon::before { transform: translateX(-5px); } +#play-pause-icon::after { transform: translateX(5px); } + +/* Play symbol (triangle) — shown when paused */ +#camera-toggle-area.paused #play-pause-icon::before, +#camera-toggle-area.paused #play-pause-icon::after { + content: none; +} + +#camera-toggle-area.paused #play-pause-icon { + opacity: 1; + background: rgba(0, 0, 0, 0.55); + /* Triangle via border trick */ + position: relative; +} + +#camera-toggle-area.paused #play-pause-icon::before { + content: ""; + display: block; + width: 0; + height: 0; + border-style: solid; + border-width: 10px 0 10px 18px; + border-color: transparent transparent transparent #fff; + background: none; + border-radius: 0; + transform: translateX(3px); + position: static; +} + +/* Show pause icon on hover when playing */ +#camera-toggle-area:not(.paused):hover #play-pause-icon { + opacity: 1; +} + +/* ── Input bar + submit button ───────────────────────────────────────────── */ + +#container { + display: flex; + align-items: center; + width: 100%; + max-width: 400px; } #text-input { @@ -46,22 +144,28 @@ body { padding: 0 12px; font-size: 16px; border: 1px solid #ccc; - border-left: none; border-right: none; + border-radius: 6px 0 0 6px; box-sizing: border-box; } +#text-input:focus { + outline: none; + border-color: #ff5000; +} + #search-btn { width: 48px; height: 48px; background-color: #ff5000; + color: #fff; border: none; border-radius: 0 6px 6px 0; display: flex; align-items: center; justify-content: center; cursor: pointer; - transition: background-color 0.3s ease; + transition: background-color 0.2s; } #search-btn:hover { @@ -89,76 +193,302 @@ body { transform-origin: center; } -.scan-icon { - position: relative; - width: 20px; - height: 20px; +h1 { + text-align: center; + margin-bottom: 6px; } -.scan-icon::before { - content: ""; - position: absolute; - left: 0; - right: 0; - top: 50%; - height: 2px; - background-color: white; - transform: translateY(-50%); +h2 { + text-align: center; + font-size: 14px; + font-weight: normal; + color: #666; + margin-bottom: 20px; + max-width: 400px; } -.corner { - position: absolute; - width: 5px; - height: 5px; - border: 2px solid white; -} +/* ── Filter toggle row ───────────────────────────────────────────────────── */ -.tl { - top: 0; - left: 0; - border-right: none; - border-bottom: none; +#filter-row { + display: flex; + justify-content: flex-end; + width: 100%; + max-width: 400px; + margin-top: 8px; } -.tr { - top: 0; - right: 0; - border-left: none; - border-bottom: none; +#filter-toggle-btn { + display: flex; + align-items: center; + gap: 5px; + padding: 5px 12px; + border: 1px solid #ddd; + border-radius: 20px; + background: #f8f8f8; + font-size: 13px; + color: #555; + cursor: pointer; + transition: background 0.2s, border-color 0.2s, color 0.2s; } -.bl { - bottom: 0; - left: 0; - border-right: none; - border-top: none; +#filter-toggle-btn:hover { + background: #f0f0f0; + border-color: #bbb; } -.br { - bottom: 0; - right: 0; - border-left: none; - border-top: none; +#filter-toggle-btn.open { + background: #fff3ee; + border-color: #ff5000; + color: #ff5000; } -h2 { - margin: 20px 0; +#filter-badge { + color: #ff5000; + font-size: 10px; } -#barcode-scanner-view { - position: fixed; - left: 0; - top: 0; +/* ── Filter panel ────────────────────────────────────────────────────────── */ + +#filters-panel { width: 100%; - height: 100vh; - display: none; - background-color: rgba(0, 0, 0, 0.8); - z-index: 9999; + max-width: 400px; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 14px 16px; + background: #fafafa; + margin-top: 6px; } +.filter-section { + display: flex; + flex-direction: column; + gap: 8px; + padding: 12px 0; +} + +.filter-section:first-child { + padding-top: 0; +} + +.filter-section:last-child { + padding-bottom: 0; +} + +.filter-section + .filter-section { + border-top: 1px solid #ebebeb; +} + +.filter-section-title { + font-size: 11px; + font-weight: 700; + color: #888; + text-transform: uppercase; + letter-spacing: 0.6px; +} + +.filter-hint { + font-size: 11px; + color: #bbb; + margin-top: -4px; +} + +/* ── Format presets ──────────────────────────────────────────────────────── */ + +.preset-group { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.preset-btn { + padding: 4px 10px; + border: 1px solid #ddd; + border-radius: 4px; + background: #fff; + font-size: 12px; + color: #555; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, color 0.15s; +} + +.preset-btn:hover { + background: #f5f5f5; + border-color: #bbb; +} + +.preset-btn.active { + background: #ff5000; + border-color: #ff5000; + color: #fff; +} + +/* ── Custom formats grid ─────────────────────────────────────────────────── */ + +#custom-formats-wrapper { + margin-top: 4px; +} + +#custom-formats-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); + gap: 6px; +} + +.format-checkbox-label { + display: flex; + align-items: center; + gap: 5px; + font-size: 12px; + color: #444; + cursor: pointer; +} + +.format-checkbox-label input[type="checkbox"] { + accent-color: #ff5000; + width: 14px; + height: 14px; + cursor: pointer; + flex-shrink: 0; +} + +/* ── Length filter ───────────────────────────────────────────────────────── */ + +.length-inputs { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.length-label { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + color: #555; +} + +.length-label input[type="number"] { + width: 70px; + height: 32px; + padding: 0 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 13px; + text-align: center; + -moz-appearance: textfield; +} + +.length-label input[type="number"]::-webkit-outer-spin-button, +.length-label input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; +} + +.length-label input[type="number"]:focus { + outline: none; + border-color: #ff5000; +} + +.length-dash { + color: #bbb; + font-size: 16px; +} + +.length-unit { + font-size: 13px; + color: #999; +} + +/* ── Keyword rules ───────────────────────────────────────────────────────── */ + +#keyword-rules-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.rule-row { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.rule-type-select { + height: 32px; + padding: 0 6px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 12px; + color: #444; + background: #fff; + cursor: pointer; + flex-shrink: 0; +} + +.rule-type-select:focus { + outline: none; + border-color: #ff5000; +} + +.rule-value-input { + flex: 1; + min-width: 80px; + height: 32px; + padding: 0 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 13px; +} + +.rule-value-input:focus { + outline: none; + border-color: #ff5000; +} + +.rule-remove-btn { + width: 28px; + height: 28px; + border: none; + background: none; + color: #bbb; + font-size: 14px; + cursor: pointer; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: background 0.15s, color 0.15s; +} + +.rule-remove-btn:hover { + background: #fef0f0; + color: #c00; +} + +.add-rule-btn { + align-self: flex-start; + padding: 5px 12px; + border: 1px dashed #ddd; + border-radius: 4px; + background: #fff; + font-size: 12px; + color: #777; + cursor: pointer; + transition: border-color 0.15s, color 0.15s; +} + +.add-rule-btn:hover { + border-color: #ff5000; + color: #ff5000; +} + +/* ── Result textarea ─────────────────────────────────────────────────────── */ + textarea { width: 100%; - max-width: 380px; + max-width: 400px; height: 300px; margin-top: 20px; padding: 10px; @@ -174,12 +504,11 @@ textarea:focus { outline: none; } -@media (max-width: 480px) { +/* ── Responsive ──────────────────────────────────────────────────────────── */ - #scan-btn, - #search-btn { - width: 40px; - height: 40px; +@media (max-width: 480px) { + #barcode-scanner-view { + height: 200px; } #text-input { @@ -187,8 +516,21 @@ textarea:focus { font-size: 14px; } + #search-btn { + width: 40px; + height: 40px; + } + textarea { height: 200px; font-size: 14px; } + + .rule-row { + flex-wrap: wrap; + } + + .rule-type-select { + width: 100%; + } } \ No newline at end of file diff --git a/scenarios/scan-and-search/index.html b/scenarios/scan-and-search/index.html index a08d3291..935a2e0d 100644 --- a/scenarios/scan-and-search/index.html +++ b/scenarios/scan-and-search/index.html @@ -14,16 +14,74 @@

📦 Instant Product Information Lookup

-

Enter a UPC, EAN, or ISBN number to retrieve accurate product data from a specific database

-
- +
+ + + + + +
-