📝 docs: update README documentation for 13 libraries

This commit is contained in:
Lorenz Hilpert
2025-11-25 14:13:44 +01:00
parent b93e39068c
commit bc1f6a42e6
14 changed files with 3591 additions and 2317 deletions

View File

@@ -1,169 +1,232 @@
# Scanner Library
# @isa/shared/scanner
Enterprise-grade barcode scanning library for ISA-Frontend using the Scandit SDK, providing mobile barcode scanning capabilities for iOS and Android platforms.
## Overview
The `@isa/shared/scanner` library provides enterprise-grade barcode scanning capabilities for the ISA application using the [Scandit SDK](https://www.scandit.com/). It offers a complete, production-ready solution for mobile barcode scanning with comprehensive support for multiple symbologies, platform detection (iOS/Android), ready-state management, and seamless integration with Angular forms and reactive patterns.
The `@isa/shared/scanner` library provides a complete, production-ready solution for mobile barcode scanning with comprehensive support for multiple symbologies, platform detection, ready-state management, and seamless integration with Angular's reactive patterns. Built on the [Scandit SDK](https://www.scandit.com/), it offers ready-to-use components, directives, and services for implementing barcode scanning workflows throughout the ISA application.
**Key Use Cases:**
- Barcode scanning for product lookup and inventory management
- Product lookup and inventory management
- Order processing and verification
- Returns and remission workflows
- Asset tracking and identification
- QR code scanning for digital workflows
**Type:** Shared component library with services, directives, and ready-to-use UI components
**Type:** Shared component library (UI + Services)
## Installation
```ts
import {
ScannerService,
ScannerButtonComponent,
ScannerReadyDirective
} from '@isa/shared/scanner';
```
## Features
- Barcode scanning with support for multiple symbologies:
- EAN8
- EAN13/UPCA
- UPCE
- Code128
- Code39
- Code93
- Interleaved 2 of 5
- QR Code
- Platform detection (iOS/Android support)
- Ready-state detection with conditional rendering
- Form control integration via output bindings
- Configuration through injection tokens
- Error handling for unsupported platforms
- **Multiple Symbologies:** EAN-8, EAN-13/UPC-A, UPC-E, Code 128, Code 39, Code 93, Interleaved 2 of 5, QR Code
- **Platform Detection:** Automatic iOS/Android detection with unsupported platform handling
- **Ready-State Management:** Reactive signals for scanner initialization status
- **Overlay UI:** Full-screen camera overlay with CDK integration
- **Form Integration:** Output bindings for Angular forms
- **Configurable:** Injection tokens for license key, library location, and symbologies
- **TypeScript:** Full type safety with Zod schema validation
- **Logging:** Integrated with `@isa/core/logging` for debugging
- **E2E Testing:** E2E attributes for automated testing (`data-which="scan-button"`)
## Components and Directives
## API Reference
### ScannerButtonComponent
### Services
A button component that integrates with the scanner service to trigger barcode scanning. It implements `OnDestroy` to properly clean up resources.
#### `ScannerService`
Core service that manages barcode scanning functionality using the Scandit SDK.
**Properties:**
- `ready: Signal<boolean>` - Computed signal indicating whether the scanner is initialized and ready to use. Automatically triggers configuration on first access.
**Methods:**
- `configure(): Promise<void>` - Manually configure the Scandit SDK. Called automatically by `open()` and the `ready` signal. Handles platform compatibility checks and license validation.
- `open(options?: ScannerInputs): Promise<string | null>` - Opens the scanner overlay interface and returns a promise that resolves to the scanned barcode value. Returns `null` if the user cancels scanning.
**Configuration Options (`ScannerInputs`):**
```ts
type ScannerInputs = {
symbologies?: Symbology[]; // Override default barcode types
abortSignal?: AbortSignal; // Cancel scanning operation
};
```
**Scanner Status:**
The service tracks initialization through the following states:
- `None` - Initial state, not yet initialized
- `Initializing` - Configuration in progress
- `Ready` - Scanner fully initialized and ready for use
- `Error` - Initialization failed
**Platform Support:**
Only iOS and Android platforms are supported. Attempting to use the scanner on unsupported platforms will log a warning and prevent initialization. The service throws `PlatformNotSupportedError` for unsupported platforms.
**Default Symbologies:**
The scanner recognizes the following barcode formats by default:
- EAN-8
- EAN-13 / UPC-A
- UPC-E
- Code 128
- Code 39
- Code 93
- Interleaved 2 of 5
- QR Code
### Components
#### `ScannerButtonComponent`
A ready-to-use button component that triggers the barcode scanner when clicked. Automatically handles scanner lifecycle and cleanup.
**Selector:** `shared-scanner-button`
**Inputs:**
- `size: InputSignal<IconButtonSize>` - Button size (default: `'large'`)
- `disabled: ModelSignal<boolean>` - Whether the button is disabled (default: `false`)
**Outputs:**
- `scan: OutputEmitterRef<string | null>` - Emits the scanned barcode value or `null` if the user cancels
**Features:**
- Only appears when scanner is ready
- Can be disabled through binding
- Configurable size
- Emits scanned value through output binding
- Includes E2E testing attribute `data-which="scan-button"`
- Only visible when scanner is ready (uses `*sharedScannerReady` directive internally)
- Opens full-screen scanner overlay on click
- Automatically aborts scanning operations on component destroy
- Includes E2E testing attribute: `data-which="scan-button"`
- Primary color styling with scanner icon
**Usage Example:**
**Lifecycle:**
```typescript
import { ScannerButtonComponent } from '@isa/core/scanner';
- Implements `OnDestroy` to abort any in-progress scanning operations
- Uses `AbortController` for proper cleanup
@Component({
selector: 'app-my-component',
template: `
<shared-scanner-button
[disabled]="isDisabled"
[size]="'large'"
(scan)="onScan($event)"
>
</shared-scanner-button>
`,
imports: [ScannerButtonComponent],
standalone: true,
})
export class MyComponent {
isDisabled = false;
#### `ScannerComponent`
onScan(value: string | null) {
console.log('Scanned barcode:', value);
}
Internal component that renders the camera view and handles barcode detection. Used by `ScannerService` via CDK overlay and typically not used directly in application code.
**Selector:** `shared-scanner`
**Inputs:**
- `symbologies: InputSignal<Symbology[]>` - Array of barcode types to detect
**Outputs:**
- `scan: OutputEmitterRef<string>` - Emits detected barcode data
**Features:**
- Full-screen camera view with close button
- Progress indicator during initialization
- Zone-aware event handling for optimal change detection
- Automatic camera lifecycle management (on/off)
- Cleanup on destroy to prevent memory leaks
### Directives
#### `ScannerReadyDirective`
Structural directive that conditionally renders content based on scanner ready state. Similar to `*ngIf`, but specifically tied to scanner initialization status.
**Selector:** `*sharedScannerReady`
**Inputs:**
- `scannerReadyElse: InputSignal<TemplateRef<unknown> | undefined>` - Optional template to show when scanner is not ready (similar to `*ngIf` else template)
**Features:**
- Reactive to scanner status changes using Angular effects
- Supports else template for fallback content
- Automatically updates when scanner becomes ready
- No manual subscription management required
### Injection Tokens
#### `SCANDIT_LICENSE`
Injection token for the Scandit license key.
**Default:** Retrieves from application config at `licence.scandit` path
**Type:** `InjectionToken<string>`
#### `SCANDIT_LIBRARY_LOCATION`
Injection token for the Scandit SDK library location.
**Default:** `/scandit` relative to `document.baseURI`
**Type:** `InjectionToken<string>`
#### `SCANDIT_DEFAULT_SYMBOLOGIES`
Injection token for the default barcode symbologies to scan.
**Default:** Array of common symbologies (EAN, UPC, Code128, Code39, Code93, ITF, QR)
**Type:** `InjectionToken<Symbology[]>`
### Errors
#### `PlatformNotSupportedError`
Thrown when attempting to use the scanner on non-mobile platforms.
```ts
export class PlatformNotSupportedError extends Error {
constructor(); // Message: "ScannerService is only supported on iOS and Android platforms"
}
```
### ScannerReadyDirective
**Behavior:**
- Caught by `ScannerService.configure()` and logged as a warning
- Prevents scanner initialization on unsupported platforms
- Does not crash the application
A structural directive (`*sharedScannerReady`) that conditionally renders its content based on the scanner's ready state. Similar to `*ngIf`, but specifically tied to scanner readiness.
## Usage Examples
**Features:**
### Basic Programmatic Scanning
- Only renders content when the scanner is ready
- Supports an optional else template for when the scanner is not ready
- Uses Angular's effect system for reactive updates
**Usage Example:**
```typescript
import { ScannerReadyDirective } from '@isa/core/scanner';
@Component({
selector: 'app-my-component',
template: `
<div *sharedScannerReady>
<!-- Content only shown when scanner is ready -->
<p>Scanner is ready to use</p>
<button (click)="startScanning()">Scan Now</button>
</div>
<!-- Alternative with else template -->
<div *sharedScannerReady="; else notReady">
<p>Scanner is ready</p>
</div>
<ng-template #notReady>
<p>Scanner is not yet ready</p>
<app-spinner></app-spinner>
</ng-template>
`,
imports: [ScannerReadyDirective],
standalone: true,
})
export class MyComponent {
// Component logic
}
```
### ScannerComponent
Internal component used by ScannerService to render the camera view and process barcode scanning.
**Features:**
- Integrates with Scandit SDK
- Handles camera setup and barcode detection
- Emits scanned values
- Includes a close button to cancel scanning
## Services
### ScannerService
Core service that provides barcode scanning functionality.
**Features:**
- Initializes and configures Scandit SDK
- Checks platform compatibility
- Manages scanner lifecycle
- Provides a reactive `ready` signal
- Handles scanning operations with proper cleanup
**Usage Example:**
```typescript
import { ScannerService, ScannerInputs } from '@isa/core/scanner';
```ts
import { Component, inject } from '@angular/core';
import { ScannerService } from '@isa/shared/scanner';
@Component({
selector: 'app-my-component',
selector: 'app-checkout',
template: `
<button (click)="scan()" [disabled]="!isReady()">Scan Barcode</button>
<div *ngIf="result">Last Scan: {{ result }}</div>
`,
standalone: true,
<button (click)="scanProduct()">Scan Product</button>
@if (scannedCode) {
<p>Scanned: {{ scannedCode }}</p>
}
`
})
export class MyComponent {
private scannerService = inject(ScannerService);
isReady = this.scannerService.ready;
result: string | null = null;
export class CheckoutComponent {
#scannerService = inject(ScannerService);
async scan() {
const options: ScannerInputs = {
// Optional configuration
// symbologies: [...] // Specify barcode types
};
scannedCode: string | null = null;
async scanProduct() {
try {
this.result = await this.scannerService.open(options);
this.scannedCode = await this.#scannerService.open();
if (this.scannedCode) {
console.log('Product code:', this.scannedCode);
// Process the scanned code (e.g., fetch product details)
}
} catch (error) {
console.error('Scanning failed:', error);
}
@@ -171,53 +234,381 @@ export class MyComponent {
}
```
## Configuration
### Using Scanner Button Component
The scanner module uses injection tokens for configuration:
```ts
import { Component } from '@angular/core';
import { ScannerButtonComponent } from '@isa/shared/scanner';
- `SCANDIT_LICENSE` - The Scandit license key (defaults to config value at 'licence.scandit')
- `SCANDIT_LIBRARY_LOCATION` - The location of the Scandit library (defaults to '/scandit' relative to base URI)
- `SCANDIT_DEFAULT_SYMBOLOGIES` - The default barcode symbologies to use
@Component({
selector: 'app-inventory',
imports: [ScannerButtonComponent],
template: `
<h2>Inventory Lookup</h2>
**Custom Configuration Example:**
<shared-scanner-button
[size]="'large'"
[disabled]="isProcessing"
(scan)="onScan($event)"
/>
```typescript
import { SCANDIT_LICENSE, SCANDIT_LIBRARY_LOCATION } from '@isa/core/scanner';
@if (lastScan) {
<div class="scan-result">
<p>Last scanned: {{ lastScan }}</p>
<p>Product: {{ productName }}</p>
</div>
}
`
})
export class InventoryComponent {
lastScan: string | null = null;
productName: string = '';
isProcessing = false;
async onScan(code: string | null) {
if (code) {
this.lastScan = code;
this.isProcessing = true;
await this.processBarcode(code);
this.isProcessing = false;
}
}
private async processBarcode(code: string) {
// Fetch product details from API
const response = await fetch(`/api/products/${code}`);
const product = await response.json();
this.productName = product.name;
}
}
```
### Scanning with Custom Symbologies
```ts
import { Component, inject } from '@angular/core';
import { ScannerService } from '@isa/shared/scanner';
import { Symbology } from 'scandit-web-datacapture-barcode';
@NgModule({
@Component({
selector: 'app-qr-scanner',
template: `
<button (click)="scanQRCode()">Scan QR Code Only</button>
<button (click)="scanEAN()">Scan Product Barcode</button>
`
})
export class QRScannerComponent {
#scannerService = inject(ScannerService);
async scanQRCode() {
// Only scan QR codes
const result = await this.#scannerService.open({
symbologies: [Symbology.QR]
});
if (result) {
this.handleQRCode(result);
}
}
async scanEAN() {
// Only scan EAN barcodes
const result = await this.#scannerService.open({
symbologies: [Symbology.EAN8, Symbology.EAN13UPCA]
});
if (result) {
this.handleProductCode(result);
}
}
private handleQRCode(data: string) {
console.log('QR Code:', data);
}
private handleProductCode(code: string) {
console.log('Product Code:', code);
}
}
```
### Using Scanner Ready Directive
```ts
import { Component } from '@angular/core';
import { ScannerButtonComponent, ScannerReadyDirective } from '@isa/shared/scanner';
@Component({
selector: 'app-product-lookup',
imports: [ScannerButtonComponent, ScannerReadyDirective],
template: `
<div *sharedScannerReady="; else scannerNotReady">
<h2>Scan Product Barcode</h2>
<p>Point your camera at a barcode to scan</p>
<shared-scanner-button (scan)="onScan($event)" />
</div>
<ng-template #scannerNotReady>
<div class="fallback">
<p>Camera scanner is not available on this device</p>
<p>Please enter the barcode manually:</p>
<input
type="text"
placeholder="Enter barcode"
(input)="onManualEntry($event)"
/>
</div>
</ng-template>
`
})
export class ProductLookupComponent {
onScan(code: string | null) {
if (code) {
this.lookupProduct(code);
}
}
onManualEntry(event: Event) {
const input = event.target as HTMLInputElement;
if (input.value.length >= 8) {
this.lookupProduct(input.value);
}
}
private lookupProduct(barcode: string) {
console.log('Looking up product:', barcode);
// Fetch product by barcode
}
}
```
### Cancelling a Scan Operation
```ts
import { Component, inject, OnDestroy } from '@angular/core';
import { ScannerService } from '@isa/shared/scanner';
@Component({
selector: 'app-advanced-scanner',
template: `
<button (click)="startScan()" [disabled]="isScanning">
{{ isScanning ? 'Scanning...' : 'Start Scan' }}
</button>
<button (click)="cancelScan()" [disabled]="!isScanning">
Cancel
</button>
@if (errorMessage) {
<p class="error">{{ errorMessage }}</p>
}
`
})
export class AdvancedScannerComponent implements OnDestroy {
#scannerService = inject(ScannerService);
#abortController = new AbortController();
isScanning = false;
errorMessage = '';
async startScan() {
this.isScanning = true;
this.errorMessage = '';
try {
const result = await this.#scannerService.open({
abortSignal: this.#abortController.signal
});
if (result) {
console.log('Scanned:', result);
} else {
console.log('Scan was cancelled by user');
}
} catch (error) {
this.errorMessage = 'Scan was cancelled or failed';
console.error('Scan error:', error);
} finally {
this.isScanning = false;
}
}
cancelScan() {
this.#abortController.abort();
this.#abortController = new AbortController(); // Create new controller for next scan
}
ngOnDestroy() {
this.#abortController.abort();
}
}
```
### Checking Scanner Readiness
```ts
import { Component, inject, effect } from '@angular/core';
import { ScannerService } from '@isa/shared/scanner';
@Component({
selector: 'app-scanner-status',
template: `
<div class="status-indicator">
@if (scannerReady()) {
<span class="ready">Scanner Ready</span>
} @else {
<span class="initializing">Initializing Scanner...</span>
}
</div>
`
})
export class ScannerStatusComponent {
#scannerService = inject(ScannerService);
scannerReady = this.#scannerService.ready;
constructor() {
effect(() => {
if (this.scannerReady()) {
console.log('Scanner initialized successfully');
// Perform actions when scanner becomes ready
}
});
}
}
```
## Configuration
### Basic Configuration
The library requires a Scandit license key configured in your application config:
```json
{
"licence": {
"scandit": "YOUR_SCANDIT_LICENSE_KEY"
}
}
```
The Scandit SDK library files must be available at `/scandit/` relative to your application's base URI.
### Custom Configuration via Injection Tokens
Override default configuration by providing custom values:
```ts
import {
SCANDIT_LICENSE,
SCANDIT_LIBRARY_LOCATION,
SCANDIT_DEFAULT_SYMBOLOGIES
} from '@isa/shared/scanner';
import { Symbology } from 'scandit-web-datacapture-barcode';
// In your providers array (e.g., app.config.ts):
export const appConfig: ApplicationConfig = {
providers: [
// Custom license key
{
provide: SCANDIT_LICENSE,
useValue: 'YOUR-SCANDIT-LICENSE-KEY',
useValue: 'YOUR-CUSTOM-LICENSE-KEY'
},
// Custom library location
// Custom library location (e.g., CDN)
{
provide: SCANDIT_LIBRARY_LOCATION,
useValue: 'https://cdn.example.com/scandit/',
useValue: 'https://cdn.example.com/scandit/'
},
],
})
export class AppModule {}
// Custom default symbologies (only QR and Code128)
{
provide: SCANDIT_DEFAULT_SYMBOLOGIES,
useValue: [Symbology.QR, Symbology.Code128]
}
]
};
```
## Dependencies
### Core Dependencies
- **@angular/core** - Angular framework (signals, effects, DI)
- **@angular/cdk** - CDK platform detection and overlay
- **scandit-web-datacapture-core** - Scandit SDK core functionality
- **scandit-web-datacapture-barcode** - Scandit barcode scanning module
- **zod** - Configuration schema validation
### ISA Dependencies
- **@isa/core/config** - Application configuration management
- **@isa/core/logging** - Logging utilities with factory pattern
- **@isa/ui/buttons** - Icon button component
- **@isa/icons** - Icon library (scanner and close icons)
## Error Handling
The scanner library includes error handling for various scenarios:
The scanner library provides comprehensive error handling:
- `PlatformNotSupportedError` is thrown when the scanner is used on unsupported platforms
- Configuration errors are logged and propagated
- Aborted scan operations are handled gracefully
### Platform Compatibility
## Requirements
```ts
// Automatically handled by ScannerService
if (!platform.IOS && !platform.ANDROID) {
// Logs warning and prevents initialization
// Does not crash the application
}
```
- The Scandit SDK must be properly installed and configured
- Requires a valid Scandit license key
- Currently supports iOS and Android platforms
### Configuration Errors
```ts
try {
await scannerService.configure();
} catch (error) {
// Configuration errors are logged and set status to 'Error'
console.error('Scanner configuration failed:', error);
}
```
### Scan Operation Errors
```ts
try {
const result = await scannerService.open();
} catch (error) {
// Handle abort or configuration errors
if (error.message === 'Scanner aborted') {
console.log('User cancelled scanning');
} else {
console.error('Scanning failed:', error);
}
}
```
## Architecture Notes
- **Signals & Effects:** Uses Angular signals for reactive state management and effects for side effect handling
- **CDK Overlay:** Integrates with Angular CDK Overlay for full-screen scanner UI with backdrop and positioning
- **Camera Lifecycle:** Automatic camera management (on/off state) with proper cleanup
- **Zone Management:** Zone-aware event handling for optimal change detection performance
- **Standalone:** All components and directives are standalone for easy tree-shaking
- **Logging:** Comprehensive logging with `@isa/core/logging` factory pattern
- **Memory Safety:** Proper cleanup in `OnDestroy` lifecycle hooks to prevent memory leaks
## Platform Requirements
- **Supported Platforms:** iOS and Android only
- **Scandit License:** Valid Scandit SDK license key required
- **Camera Permissions:** Application must request and obtain camera permissions
- **Library Files:** Scandit SDK files must be accessible at configured location
## E2E Testing
The scanner components include E2E testing attributes for easier selection in automated tests:
The scanner components include E2E testing attributes for automated test selection:
- ScannerButton includes `data-which="scan-button"` for E2E test selection
- `ScannerButtonComponent` includes `data-which="scan-button"` attribute
- Use this attribute to locate and interact with the scanner button in E2E tests
```ts
// Example E2E test
await page.click('[data-which="scan-button"]');
```