import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, ViewChild } from "@angular/core"; import { BarcodePicker, Camera, CameraSettings, ScanResult, ScanSettings, SearchArea, SingleImageModeSettings } from "scandit-sdk"; import { ScanditSdkService } from "./scandit-sdk.service"; /** * Scandit SDK Barcode Picker Component handling camera input and scanning operations. */ @Component({ selector: "scandit-sdk-barcode-picker", template: '
', styles: [":host { display: inline-block }", "#scanditsdkbarcodepickercontainer { width: 100%; height:100%; }"] }) export class ScanditSdkBarcodePickerComponent implements OnInit, OnDestroy, OnChanges { /** * Handle scanned barcodes via a passed ScanResult object * * See https://docs.scandit.com/stable/web/interfaces/scanresult.html * and https://docs.scandit.com/stable/web/classes/barcodepicker.html#onscan */ @Output() public scan: EventEmitter = new EventEmitter(); /** * Handle processed frames * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#onprocessframe */ @Output() public processFrame: EventEmitter = new EventEmitter(); /** * Handle submitted frames * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#onsubmitframe */ @Output() public submitFrame: EventEmitter = new EventEmitter(); /** * Handle errors while scanning * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#onscanerror */ @Output() public error: EventEmitter = new EventEmitter(); /** * Emitted when the component is initialized. * * At this moment, the picker is still being created and configured, use `(ready)` to get notified when * the picker is ready. */ @Output() public init: EventEmitter = new EventEmitter(); /** * Emitted when the picker is ready. * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#onready */ @Output() public ready: EventEmitter = new EventEmitter(); /** * Whether there the camera should be accessed. * * Set this to false initially to create the picker without camera access. * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#accesscamera */ @Input("accessCamera") private set shouldAccessCamera(newValue: boolean) { this._shouldAccessCamera = newValue; this.setShouldAccessCamera(); } /** * The active camera * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#setactivecamera */ @Input() private set camera(newValue: Camera) { this._camera = newValue; this.setCameraAndSettings(); } /** * The camera settings * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#applycamerasettings */ @Input() private set cameraSettings(newValue: CameraSettings) { this._cameraSettings = newValue; this.setCameraSettings(); } /** * The initial camera type (can only be set at creation) * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#create */ @Input() private set cameraType(newValue: Camera.Type) { this._cameraType = newValue; } /** * Whether to enable the camera switcher button */ @Input() private set enableCameraSwitcher(newValue: boolean) { this._enableCameraSwitcher = newValue; this.setEnableCameraSwitcher(); } /** * Whether to enable the torch toggle button */ @Input() private set enableTorchToggle(newValue: boolean) { this._enableTorchToggle = newValue; this.setEnableTorchToggle(); } /** * Set the zoom level of the device * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#setzoom */ @Input() private set zoom(newValue: number) { this._zoom = newValue; this.setZoomLevel(); } /** * Whether to enable pinch to zoom on the camera preview */ @Input() private set enablePinchToZoom(newValue: boolean) { this._enablePinchToZoom = newValue; this.setEnablePinchToZoom(); } /** * Whether to enable camera's video mirroring along the vertical axis */ @Input() private set mirrorImage(newValue: boolean) { this._enableMirrorImage = newValue; this.setImageMirrorEnabled(); } /** * Whether to enable tap to focus on the camera preview */ @Input() private set enableTapToFocus(newValue: boolean) { this._enableTapToFocus = newValue; this.setEnableTapToFocus(); } /** * Which GUI style to use for the barcode picker * * See https://docs.scandit.com/stable/web/enums/barcodepicker.guistyle.html * and https://docs.scandit.com/stable/web/classes/barcodepicker.html#setguistyle */ @Input() private set guiStyle(newValue: BarcodePicker.GuiStyle) { this._guiStyle = newValue; this.setGuiStyle(); } /** * Set the area of the laser displayed when the GUI style is set to laser. * This affects UI only. * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#setlaserarea */ @Input() private set laserArea(newValue: SearchArea) { this._laserArea = newValue; this.setLaserArea(); } /** * Set the area of the viewfinder when the GUI style is set to viewfinder. * This affects UI only. * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#setviewfinderarea */ @Input() private set viewfinderArea(newValue: SearchArea) { this._viewfinderArea = newValue; this.setViewfinderArea(); } /** * Whether the internal scanner should be paused or not * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#pausescanning */ @Input() private set paused(newValue: boolean) { this._paused = newValue; this.setPaused(); } /** * Whether there should be a sound after scanning * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#setplaysoundonscanenabled */ @Input() private set playSoundOnScan(newValue: boolean) { this._playSoundOnScan = newValue; this.setPlaySoundOnScan(); } /** * Whether the device should vibrate after scanning * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#setvibrateonscanenabled */ @Input() private set vibrateOnScan(newValue: boolean) { this._vibrateOnScan = newValue; this.setVibrateOnScan(); } /** * The scan settings to be used for the internal scanner * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#applyscansettings */ @Input() private set scanSettings(newValue: ScanSettings) { this._scanSettings = newValue; this.setScanSettings(); } /** * The target scanning FPS * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#settargetscanningfps */ @Input() private set targetScanningFPS(newValue: number) { this._targetScanningFPS = newValue; this.setTargetScanningFPS(); } /** * The fit of the camera preview element * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#setvideofit */ @Input() private set videoFit(newValue: BarcodePicker.ObjectFit) { this._videoFit = newValue; this.setVideoFit(); } /** * Whether the camera preview is visible * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#setvisible */ @Input() private set visible(newValue: boolean) { this._visible = newValue; this.setVisible(); } /** * The "single image mode" settings (can only be set at creation) * * See https://docs.scandit.com/stable/web/classes/barcodepicker.html#create */ @Input() private set singleImageModeSettings(newValue: SingleImageModeSettings) { this._singleImageModeSettings = newValue; } /** * BarcodePicker reference */ private picker: BarcodePicker; /** * Private variable of `shouldAccessCamera` */ private _shouldAccessCamera: boolean = true; /** * Private variable of `camera` */ private _camera: Camera; /** * Private variable of `cameraSettings` */ private _cameraSettings: CameraSettings; /** * Private variable of `cameraType` */ private _cameraType: Camera.Type; /** * Private variable of `enableCameraSwitcher` */ private _enableCameraSwitcher: boolean; /** * Private variable of `enableTorchToggle` */ private _enableTorchToggle: boolean; /** * Private variable of `setZoomLevel` */ private _zoom: number; /** * Private variable of `enablePinchToZoom` */ private _enablePinchToZoom: boolean; /** * Private variable of `mirrorImage` */ private _enableMirrorImage: boolean = false; /** * Private variable of `enableTapToFocus` */ private _enableTapToFocus: boolean; /** * Private variable of `guiStyle` */ private _guiStyle: BarcodePicker.GuiStyle; /** * Private variable of `laserArea` */ private _laserArea: SearchArea | undefined; /** * Private variable of `viewfinderArea` */ private _viewfinderArea: SearchArea | undefined; /** * Private variable of `paused` */ private _paused: boolean; /** * Private variable of `playSoundOnScan` */ private _playSoundOnScan: boolean; /** * Private variable of `vibrateOnScan` */ private _vibrateOnScan: boolean; /** * Private variable of `scanSettings` */ private _scanSettings: ScanSettings; /** * Private variable of `targetScanningFPS` */ private _targetScanningFPS: number; /** * Private variable of `videoFit` */ private _videoFit: BarcodePicker.ObjectFit; /** * Private variable of `visible` */ private _visible: boolean; /** * Private variable of `singleImageModeSettings` */ private _singleImageModeSettings: SingleImageModeSettings; /** * Reference to the element containing the picker */ @ViewChild("scanditsdkbarcodepickercontainer", { static: true }) private readonly pickerContainer: any; /** * A queue of operations to be executed * * As soon as a picker is available, there could be changes to the @Input properties, * which trigger operations to be added to this queue. * * As operations on the picker are mostly promises, this queue is executed serially * and should include promise factory functions. */ private readonly operationQueue: (() => Promise)[] = []; /** * If the component is initialized yet or not */ private isInitialized: boolean = false; /** * Creates an instance of ScanditSdkBarcodePickerComponent. * @param scanditSdkService Dependency Injection for scanditSdkService */ constructor(private readonly scanditSdkService: ScanditSdkService) {} /** * Angular Lifecycle hook, on component initialization, create the picker, * set some callback handlers and set the proper states */ ngOnInit(): void { const containerElement: HTMLElement = this.pickerContainer.nativeElement; this.init.emit(); this.scanditSdkService .createPicker(containerElement, { accessCamera: this._shouldAccessCamera, camera: this._camera, cameraSettings: this._cameraSettings, cameraType: this._cameraType, enableCameraSwitcher: this._enableCameraSwitcher, enablePinchToZoom: this._enablePinchToZoom, enableTapToFocus: this._enableTapToFocus, enableTorchToggle: this._enableTorchToggle, guiStyle: this._guiStyle, laserArea: this._laserArea, playSoundOnScan: this._playSoundOnScan, scanningPaused: this._paused, scanSettings: this._scanSettings, singleImageModeSettings: this._singleImageModeSettings, targetScanningFPS: this._targetScanningFPS, vibrateOnScan: this._vibrateOnScan, videoFit: this._videoFit, viewfinderArea: this._viewfinderArea, visible: this._visible, }) .then(picker => { this.picker = picker; this.executeOperationQueue(); this.picker.on("scan", scanResult => { this.scan.emit(scanResult); }); this.picker.on("processFrame", scanResult => { this.processFrame.emit(scanResult); }); this.picker.on("submitFrame", scanResult => { this.submitFrame.emit(scanResult); }); this.picker.on("ready", () => { this.ready.emit(); }); this.picker.on("scanError", error => { this.error.emit(error); }); }); this.isInitialized = true; } /** * Angular Lifecycle hook to destroy the picker when the component is destroyed */ public ngOnDestroy(): void { if (this.picker != null) { this.picker.destroy(); } else { this.addToQueue(() => { return Promise.resolve(this.ngOnDestroy()); }); } } /** * Angular Lifecycle hook to execute the operation queue after any Input property changes */ public ngOnChanges(): void { if (this.picker != null) { this.executeOperationQueue(); } } /** * Handler to create the operation after an input change */ private setShouldAccessCamera(): void { if (this._shouldAccessCamera) { this.addToQueue(() => { return this.picker.accessCamera(); }); } else { this.addToQueue(() => { return Promise.resolve((this.paused = true)); }); } } /** * Handler to create the operation after an input change */ private setCameraAndSettings(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setActiveCamera(this._camera, this._cameraSettings)); }); } /** * Handler to create the operation after an input change */ private setCameraSettings(): void { this.addToQueue(() => { return this.picker.applyCameraSettings(this._cameraSettings); }); } /** * Handler to create the operation after an input change */ private setEnableCameraSwitcher(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setCameraSwitcherEnabled(this._enableCameraSwitcher)); }); } /** * Handler to create the operation after an input change */ private setEnableTorchToggle(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setTorchToggleEnabled(this._enableTorchToggle)); }); } /** * Handler to create the operation after an input change */ private setZoomLevel(): void { this.addToQueue(() => { return this.picker.setZoom(this._zoom); }); } /** * Handler to create the operation after an input change */ private setEnablePinchToZoom(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setPinchToZoomEnabled(this._enablePinchToZoom)); }); } /** * Handler to create the operation after an input change */ private setImageMirrorEnabled(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setMirrorImageEnabled(this._enableMirrorImage)); }); } /** * Handler to create the operation after an input change */ private setEnableTapToFocus(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setTapToFocusEnabled(this._enableTapToFocus)); }); } /** * Handler to create the operation after an input change */ private setGuiStyle(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setGuiStyle(this._guiStyle)); }); } /** * Handler to create the operation after an input change */ private setLaserArea(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setLaserArea(this._laserArea)); }); } /** * Handler to create the operation after an input change */ private setViewfinderArea(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setViewfinderArea(this._viewfinderArea)); }); } /** * Handler to create the operation after an input change */ private setPaused(): void { if (this._paused) { this.addToQueue(() => { return Promise.resolve(this.picker.pauseScanning(!this._shouldAccessCamera)); }); } else { this.addToQueue(() => { return this.picker.resumeScanning(); }); } } /** * Handler to create the operation after an input change */ private setPlaySoundOnScan(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setPlaySoundOnScanEnabled(this._playSoundOnScan)); }); } /** * Handler to create the operation after an input change */ private setVibrateOnScan(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setVibrateOnScanEnabled(this._vibrateOnScan)); }); } /** * Handler to create the operation after an input change */ private setScanSettings(): void { this.addToQueue(() => { return Promise.resolve(this.picker.applyScanSettings(this._scanSettings)); }); } /** * Handler to create the operation after an input change */ private setTargetScanningFPS(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setTargetScanningFPS(this._targetScanningFPS)); }); } /** * Handler to create the operation after an input change */ private setVideoFit(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setVideoFit(this._videoFit)); }); } /** * Handler to create the operation after an input change */ private setVisible(): void { this.addToQueue(() => { return Promise.resolve(this.picker.setVisible(this._visible)); }); } /** * Add an operation to be executed to the queue * * @param operation A promise factory function */ private addToQueue(operation: () => Promise): void { if (this.isInitialized) { this.operationQueue.push(operation); } } /** * Handler to create the operation after an input change * @returns A promise resolving when the operation queue was executed */ private executeOperationQueue(): Promise { return this.operationQueue .reduce((promise, func) => { return promise.then(() => { return func().then(() => { const index: number = this.operationQueue.indexOf(func); if (index > -1) { this.operationQueue.splice(index, 1); } }); }); }, Promise.resolve()) .then(() => { if (this.operationQueue.length > 0) { return this.executeOperationQueue(); } }); } }