/** * Copyright (c) Microblink Ltd. All rights reserved. */ import { Component, Element, Event, EventEmitter, Host, h, Method, Prop, State, Listen } from '@stencil/core'; import { CameraEntry, CameraExperienceState, CameraExperienceTimeoutDurations, Code, MultiSideImageRecognitionConfiguration, MultiSideImageType, EventReady, EventScanError, EventScanSuccess, FeedbackCode, FeedbackMessage, ImageRecognitionConfiguration, ImageRecognitionType, RecognitionEvent, RecognitionStatus, VideoRecognitionConfiguration, SDKError } from '../../../utils/data-structures'; import * as ErrorTypes from '../../../utils/error-structures'; import { CheckConclusion, SdkService } from '../../../utils/sdk.service'; import * as BlinkIDImageCaptureSDK from '../../../../../es/blinkid-imagecapture-sdk'; import { TranslationService } from '../../../utils/translation.service'; import * as DeviceHelpers from '../../../utils/device.helpers'; import * as GenericHelpers from '../../../utils/generic.helpers'; import * as Utils from './mb-component.utils'; @Component({ tag: 'mb-component', styleUrl: 'mb-component.scss', shadow: true, }) export class MbComponent { private screens: { [key: string]: HTMLMbScreenElement | null } = { action: null, error: null, loading: null, processing: null } private overlays: { [key: string]: HTMLMbOverlayElement | null } = { camera: null, draganddrop: null, processing: null, modal: null, deviceselection: null, deviceselectionmobile: null } private cameraExperience!: HTMLMbCameraExperienceElement; private dragAndDropZone!: HTMLDivElement; private errorMessage!: HTMLParagraphElement; private scanFromCameraButton!: HTMLMbButtonElement; private scanFromImageButton!: HTMLMbButtonElement; private scanFromImageInput!: HTMLInputElement; private videoElement!: HTMLVideoElement; private licenseExperienceModal!: HTMLMbModalElement; private scanReset: boolean = false; private detectionSuccessLock = false; private isBackSide = false; private initialBodyOverflowValue: string; private cameraChangeInProgress: boolean = false; private blocked: boolean = false; private multiSideGalleryOpened = false; private imageRecognitionType: ImageRecognitionType; private imageBoxFirst: HTMLMbImageBoxElement; private imageBoxSecond: HTMLMbImageBoxElement; private galleryImageFirstFile: File | null = null; private galleryImageSecondFile: File | null = null; private multiSideScanFromImageButton: HTMLMbButtonClassicElement; private isCameraActive: boolean = false; @State() galleryExperienceModalErrorWindowVisible: boolean = false; @State() clearIsCameraActive: boolean = false; @State() apiProcessStatusVisible: boolean = false; @State() apiProcessStatusState: 'ERROR' | 'LOADING' | 'NONE' | 'SUCCESS' = 'NONE'; /** * Host element as variable for manipulation (CSS in this case) */ @Element() hostEl: HTMLElement; /** * See description in public component. */ @Prop() allowHelloMessage: boolean = true; /** * See description in public component. */ @Prop() engineLocation: string = ''; /** * See description in public component. */ @Prop() workerLocation: string = ''; /** * See description in public component. */ @Prop() licenseKey: string; /** * See description in public component. */ @Prop({ mutable: true }) wasmType: string | null; /** * See description in public component. */ @Prop({ mutable: true }) recognizers: Array; /** * See description in public component. */ @Prop({ mutable: true }) recognizerOptions: { [key: string]: any }; /** * See description in public component. */ @Prop() recognitionTimeout: number; /** * See description in public component. */ @Prop() recognitionPauseTimeout: number; /** * See description in public component. */ @Prop() cameraExperienceStateDurations: CameraExperienceTimeoutDurations = null; /** * See description in public component. */ @Prop() includeSuccessFrame: boolean = false; /** * See description in public component. */ @Prop() enableDrag: boolean = true; /** * See description in public component. */ @Prop() hideLoadingAndErrorUi: boolean = false; /** * See description in public component. */ @Prop() rtl: boolean = false; /** * See description in public component. */ @Prop() scanFromCamera: boolean = true; /** * See description in public component. */ @Prop() scanFromImage: boolean = true; /** * See description in public component. */ @Prop() thoroughScanFromImage: boolean = false; /** * See description in public component. */ @Prop() galleryOverlayType: 'FULLSCREEN' | 'INLINE' = 'INLINE'; /** * See description in public component. */ @Prop() galleryDropType: 'FULLSCREEN' | 'INLINE' = 'INLINE'; /** * See description in public component. */ @Prop() showActionLabels: boolean = false; /** * See description in public component. */ @Prop() showModalWindows: boolean = false; /** * See description in public component. */ @Prop() showCameraFeedbackBarcodeMessage: boolean = false; /** * See description in public component. */ @Prop() showScanningLine: boolean = false; /** * See description in public component. */ @Prop() iconCameraDefault: string = 'data:image/svg+xml;utf8,'; /** * See description in public component. */ @Prop() iconCameraActive: string = 'data:image/svg+xml;utf8,'; /** * See description in public component. */ @Prop() iconGalleryDefault: string = 'data:image/svg+xml;utf8,'; /** * See description in public component. */ @Prop() iconDragAndDropGalleryDefault: string = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNCA4QzE0IDcuNDQ3NzIgMTQuNDQ3NyA3IDE1IDdIMTUuMDFDMTUuNTYyMyA3IDE2LjAxIDcuNDQ3NzIgMTYuMDEgOEMxNi4wMSA4LjU1MjI4IDE1LjU2MjMgOSAxNS4wMSA5SDE1QzE0LjQ0NzcgOSAxNCA4LjU1MjI4IDE0IDhaIiBmaWxsPSIjMDA2MkYyIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNyA1QzUuODk1NDMgNSA1IDUuODk1NDMgNSA3VjE3QzUgMTguMTA0NiA1Ljg5NTQzIDE5IDcgMTlIMTdDMTguMTA0NiAxOSAxOSAxOC4xMDQ2IDE5IDE3VjdDMTkgNS44OTU0MyAxOC4xMDQ2IDUgMTcgNUg3Wk0zIDdDMyA0Ljc5MDg2IDQuNzkwODYgMyA3IDNIMTdDMTkuMjA5MSAzIDIxIDQuNzkwODYgMjEgN1YxN0MyMSAxOS4yMDkxIDE5LjIwOTEgMjEgMTcgMjFIN0M0Ljc5MDg2IDIxIDMgMTkuMjA5MSAzIDE3VjdaIiBmaWxsPSIjMDA2MkYyIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNOC42OTk2NiAxMS43MTQ1TDQuNzA3MTEgMTUuNzA3MUM0LjMxNjU4IDE2LjA5NzYgMy42ODM0MiAxNi4wOTc2IDMuMjkyODkgMTUuNzA3MUMyLjkwMjM3IDE1LjMxNjUgMi45MDIzNyAxNC42ODM0IDMuMjkyODkgMTQuMjkyOEw3LjI5Mjg5IDEwLjI5MjhMNy4zMDY2MiAxMC4yNzk0QzcuOTA5IDkuNjk5NzQgOC42NjQxOSA5LjMzMDA4IDkuNSA5LjMzMDA4QzEwLjMzNTggOS4zMzAwOCAxMS4wOTEgOS42OTk3NCAxMS42OTM0IDEwLjI3OTRMMTEuNzA3MSAxMC4yOTI4TDE2LjcwNzEgMTUuMjkyOEMxNy4wOTc2IDE1LjY4MzQgMTcuMDk3NiAxNi4zMTY1IDE2LjcwNzEgMTYuNzA3MUMxNi4zMTY2IDE3LjA5NzYgMTUuNjgzNCAxNy4wOTc2IDE1LjI5MjkgMTYuNzA3MUwxMC4zMDAzIDExLjcxNDVDOS45OTMxIDExLjQyMTIgOS43MTU5NCAxMS4zMzAxIDkuNSAxMS4zMzAxQzkuMjg0MDYgMTEuMzMwMSA5LjAwNjkgMTEuNDIxMiA4LjY5OTY2IDExLjcxNDVaIiBmaWxsPSIjMDA2MkYyIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTUuNjk5NyAxMy43MTQ1TDE0LjcwNzEgMTQuNzA3MUMxNC4zMTY2IDE1LjA5NzYgMTMuNjgzNCAxNS4wOTc2IDEzLjI5MjkgMTQuNzA3MUMxMi45MDI0IDE0LjMxNjUgMTIuOTAyNCAxMy42ODM0IDEzLjI5MjkgMTMuMjkyOEwxNC4yOTI5IDEyLjI5MjhMMTQuMzA2NiAxMi4yNzk0QzE0LjkwOSAxMS42OTk3IDE1LjY2NDIgMTEuMzMwMSAxNi41IDExLjMzMDFDMTcuMzM1OCAxMS4zMzAxIDE4LjA5MSAxMS42OTk3IDE4LjY5MzQgMTIuMjc5NEwxOC43MDcxIDEyLjI5MjhMMjAuNzA3MSAxNC4yOTI4QzIxLjA5NzYgMTQuNjgzNCAyMS4wOTc2IDE1LjMxNjUgMjAuNzA3MSAxNS43MDcxQzIwLjMxNjYgMTYuMDk3NiAxOS42ODM0IDE2LjA5NzYgMTkuMjkyOSAxNS43MDcxTDE3LjMwMDMgMTMuNzE0NUMxNi45OTMxIDEzLjQyMTIgMTYuNzE1OSAxMy4zMzAxIDE2LjUgMTMuMzMwMUMxNi4yODQxIDEzLjMzMDEgMTYuMDA2OSAxMy40MjEyIDE1LjY5OTcgMTMuNzE0NVoiIGZpbGw9IiMwMDYyRjIiLz4KPC9zdmc+Cg=='; /** * See description in public component. */ @Prop() iconDragAndDropWarningDefault: string = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMiA4QzEyLjU1MjMgOCAxMyA4LjQ0NzcyIDEzIDlWMTFDMTMgMTEuNTUyMyAxMi41NTIzIDEyIDEyIDEyQzExLjQ0NzcgMTIgMTEgMTEuNTUyMyAxMSAxMVY5QzExIDguNDQ3NzIgMTEuNDQ3NyA4IDEyIDhaTTEyIDE0QzEyLjU1MjMgMTQgMTMgMTQuNDQ3NyAxMyAxNVYxNS4wMUMxMyAxNS41NjIzIDEyLjU1MjMgMTYuMDEgMTIgMTYuMDFDMTEuNDQ3NyAxNi4wMSAxMSAxNS41NjIzIDExIDE1LjAxVjE1QzExIDE0LjQ0NzcgMTEuNDQ3NyAxNCAxMiAxNFoiIGZpbGw9IiNFMTFENDgiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMC40NzY0IDIuMzgzOTdDMTAuOTM4MSAyLjExMTgxIDExLjQ2NDIgMS45NjgyNiAxMi4wMDAxIDEuOTY4MjZDMTIuNTM1OSAxLjk2ODI2IDEzLjA2MjEgMi4xMTE4MSAxMy41MjM3IDIuMzgzOTdDMTMuOTgzMSAyLjY1NDg1IDE0LjM2MiAzLjA0MzMgMTQuNjIxNCAzLjUwOTI1TDIxLjYxODMgMTUuNzUzOUMyMS42NDA0IDE1Ljc5MjUgMjEuNjU5OCAxNS44MzI1IDIxLjY3NjUgMTUuODczN0MyMS44NTY2IDE2LjMxNzEgMjEuOTI4IDE2Ljc5NzEgMjEuODg0OCAxNy4yNzM3QzIxLjg0MTYgMTcuNzUwMiAyMS42ODUgMTguMjA5NiAyMS40MjgxIDE4LjYxMzNDMjEuMTcxMSAxOS4wMTcgMjAuODIxNCAxOS4zNTM0IDIwLjQwOCAxOS41OTQ0QzE5Ljk5NDUgMTkuODM1NCAxOS41Mjk0IDE5Ljk3NDEgMTkuMDUxNSAxOS45OTg3QzE5LjAzNDQgMTkuOTk5NiAxOS4wMTcyIDIwIDE5LjAwMDEgMjBINS4wNzAwNUM1LjA1ODU3IDIwIDUuMDQ3MTQgMTkuOTk5OCA1LjAzNTc1IDE5Ljk5OTRDNS4wMDY5NiAyMC4wMDA0IDQuOTc3ODggMjAuMDAwMiA0Ljk0ODU3IDE5Ljk5ODdDNC40NzA2NiAxOS45NzQxIDQuMDA1NTggMTkuODM1NCAzLjU5MjE2IDE5LjU5NDRDMy4xNzg3MyAxOS4zNTM0IDIuODI4OTYgMTkuMDE3IDIuNTcyMDQgMTguNjEzM0MyLjMxNTEzIDE4LjIwOTYgMi4xNTg1MiAxNy43NTAyIDIuMTE1MjkgMTcuMjczN0MyLjA3MjA3IDE2Ljc5NzEgMi4xNDM0OCAxNi4zMTcxIDIuMzIzNTcgMTUuODczN0MyLjM0MDMgMTUuODMyNSAyLjM1OTc1IDE1Ljc5MjUgMi4zODE4MSAxNS43NTM5TDkuMzc4NzQgMy41MDkyNUM5LjYzODA4IDMuMDQzMyAxMC4wMTcgMi42NTQ4NSAxMC40NzY0IDIuMzgzOTdaTTUuMDM3NjcgMTguMDAwNUM1LjA0ODQyIDE4LjAwMDIgNS4wNTkyMiAxOCA1LjA3MDA1IDE4SDE4Ljk2OTlDMTkuMTIxNyAxNy45ODg5IDE5LjI2OTEgMTcuOTQzMyAxOS40MDA3IDE3Ljg2NjZDMTkuNTM4NSAxNy43ODYzIDE5LjY1NTEgMTcuNjc0MSAxOS43NDA3IDE3LjUzOTVDMTkuODI2NCAxNy40MDUgMTkuODc4NiAxNy4yNTE5IDE5Ljg5MyAxNy4wOTNDMTkuOTA1NyAxNi45NTI1IDE5Ljg4ODYgMTYuODExMiAxOS44NDMgMTYuNjc4MkwxMi44NzUgNC40ODQxOEMxMi43ODg1IDQuMzI3ODggMTIuNjYxOCA0LjE5NzU1IDEyLjUwNzkgNC4xMDY4M0MxMi4zNTQxIDQuMDE2MTEgMTIuMTc4NyAzLjk2ODI2IDEyLjAwMDEgMy45NjgyNkMxMS44MjE0IDMuOTY4MjYgMTEuNjQ2MSA0LjAxNjExIDExLjQ5MjIgNC4xMDY4M0MxMS4zMzgzIDQuMTk3NTUgMTEuMjExNSA0LjMyNzg0IDExLjEyNTEgNC40ODQxNEwxMS4xMTg0IDQuNDk2Mkw0LjE1NzE0IDE2LjY3ODJDNC4xMTE1MSAxNi44MTEyIDQuMDk0MzggMTYuOTUyNSA0LjEwNzEyIDE3LjA5M0M0LjEyMTUyIDE3LjI1MTkgNC4xNzM3MyAxNy40MDUgNC4yNTkzNyAxNy41Mzk1QzQuMzQ1MDEgMTcuNjc0MSA0LjQ2MTYgMTcuNzg2MyA0LjU5OTQgMTcuODY2NkM0LjczMzIxIDE3Ljk0NDYgNC44ODMyNCAxNy45OTA0IDUuMDM3NjcgMTguMDAwNVoiIGZpbGw9IiNFMTFENDgiLz4KPC9zdmc+Cg=='; /** * See description in public component. */ @Prop() iconGalleryActive: string = 'data:image/svg+xml;utf8,'; /** * See description in public component. */ @Prop() iconInvalidFormat: string = 'data:image/svg+xml;utf8,'; /** * See description in public component. */ @Prop() iconSpinnerScreenLoading: string; /** * See description in public component. */ @Prop() iconSpinnerFromGalleryExperience: string; /** * See description in public component. */ @Prop() iconGalleryScanningCompleted: string; /** * Instance of SdkService passed from root component. */ @Prop() sdkService: SdkService; /** * Instance of TranslationService passed from root component. */ @Prop() translationService: TranslationService; /** * Camera device ID passed from root component. */ @Prop() cameraId: string | null = null; /** * Event containing boolean which used to check whether component is blocked. */ @Event() block: EventEmitter; /** * See event 'fatalError' in public component. */ @Event() fatalError: EventEmitter; /** * See event 'ready' in public component. */ @Event() ready: EventEmitter; /** * See event 'scanError' in public component. */ @Event() scanError: EventEmitter; /** * See event 'scanSuccess' in public component. */ @Event() scanSuccess: EventEmitter; /** * Event containing FeedbackMessage which can be passed to MbFeedback component. */ @Event() feedback: EventEmitter; /** * See event 'cameraScanStarted' in public component. */ @Event() cameraScanStarted: EventEmitter; /** * See event 'imageScanStarted' in public component. */ @Event() imageScanStarted: EventEmitter; /** * See event 'scanAborted' in public component. */ @Event() scanAborted: EventEmitter; /** * Emitted when camera stream becomes active. */ @Event() setIsCameraActive: EventEmitter; componentDidLoad() { // Set `exportparts` attribute on root `mb-component` element to enable ::part() CSS customization GenericHelpers.setWebComponentParts(this.hostEl); const parts = GenericHelpers.getWebComponentParts(this.hostEl.shadowRoot); const exportedParts = GenericHelpers.getWebComponentExportedParts(this.hostEl.shadowRoot); this.hostEl.setAttribute('exportparts', parts.concat(exportedParts).join(', ')); this.init(); } componentDidUpdate() { this.init(); } disconnectedCallback() { this.sdkService?.stopRecognition(); } @Listen('keyup', { target: 'window' }) handleKeyUp(ev: KeyboardEvent) { if (ev.key === 'Escape' || ev.code === 'Escape') { if (this.overlays.camera.visible && this.isCameraActive) { this.abortScan(); this.handleSetIsCameraActive(false); this.clearIsCameraActive = true; } } } private handleSetIsCameraActive(isCameraActive: boolean) { this.isCameraActive = isCameraActive; this.clearIsCameraActive = false; } /** * Starts camera scan using camera overlay with usage instructions. */ @Method() async startCameraScan() { this.startScanFromCamera(); } /** * Starts image scan, emits results from provided file. * * @param file File to scan */ @Method() async startImageScan(file: File) { this.startScanFromImage(file); } /** * Starts multi-side image scan, emits results from provided files. * * @param firstFile File to scan as first image * @param secondFile File to scan as second image */ @Method() async startMultiSideImageScan(firstFile: File, secondFile: File) { this.startScanFromImageMultiSide(firstFile, secondFile) } /** * Method is exposed outside which allow us to control UI state from parent component. * * In case of state `ERROR` and if `showModalWindows` is set to `true`, modal window * with error message will be displayed. */ @Method() async setUiState(state: 'ERROR' | 'LOADING' | 'NONE' | 'SUCCESS') { window.setTimeout(() => { if (this.overlays.camera.visible) { if (state === 'ERROR' && !this.showModalWindows) { this.apiProcessStatusState = 'NONE'; this.apiProcessStatusVisible = false; this.stopRecognition(); return; } this.apiProcessStatusState = state; this.apiProcessStatusVisible = true; if (state !== 'ERROR') { this.cameraExperience.classList.add('is-muted'); } else { this.cameraExperience.classList.add('is-error'); } this.cameraExperience.apiState = state; } else if (this.overlays.processing.visible) { if (state === 'ERROR') { if (this.showModalWindows) { this.galleryExperienceModalErrorWindowVisible = true; } else { this.galleryExperienceModalErrorWindowVisible = false; this.stopRecognition(); } } } if (state === 'SUCCESS') { window.setTimeout(() => this.stopRecognition(), 400); } if (state === 'ERROR') { this.hideScanFromImageUi(false); this.clearInputImages(); } }, 400); } async closeApiProcessStatus(restart: boolean = false): Promise { window.setTimeout(() => { this.apiProcessStatusVisible = false; this.apiProcessStatusState = 'NONE'; this.cameraExperience.classList.remove('is-muted'); this.cameraExperience.classList.remove('is-error'); }, 600); if (restart) { await this.checkInputProperties() .then(() => this.sdkService.resumeRecognition()) .then(() => { window.setTimeout(() => this.cameraExperience.apiState = '', 400); this.isBackSide = false; this.cameraExperience.setState(CameraExperienceState.Default, this.isBackSide, true); }); } } private async init() { if (!this.hideLoadingAndErrorUi) { this.showScreen('loading'); this.showOverlay(''); } if (this.blocked) { return; } const internetIsAvailable = navigator.onLine; if (!internetIsAvailable) { this.setFatalError( new SDKError({ code: ErrorTypes.ErrorCodes.InternetNotAvailable, message: this.translationService.i('check-internet-connection').toString() }) ); return; } const hasMandatoryProperties = await this.checkInputProperties(); if (!hasMandatoryProperties) { return; } const hasMandatoryCapabilities = await DeviceHelpers.checkMandatoryCapabilites(); if (!hasMandatoryCapabilities) { this.setFatalError(new SDKError(ErrorTypes.componentErrors.browserNotSupported)); return; } this.blocked = true; this.block.emit(true); const initEvent: EventReady | SDKError = await this.sdkService.initialize(this.licenseKey, { allowHelloMessage: this.allowHelloMessage, engineLocation: this.engineLocation, workerLocation: this.workerLocation, wasmType: Utils.getSDKWasmType(this.wasmType) }); this.cameraExperience.showOverlay = this.sdkService.showOverlay; if (initEvent instanceof SDKError) { this.setFatalError(initEvent); return; } if (this.showActionLabels) { this.scanFromCameraButton.label = this.translationService.i('action-message-camera').toString(); this.scanFromImageButton.label = this.translationService.i('action-message-image').toString(); } if (this.scanFromCamera) { this.scanFromCameraButton.visible = true; const hasVideoDevices = await DeviceHelpers.hasVideoDevices(); this.scanFromCameraButton.disabled = !hasVideoDevices; if (!hasVideoDevices) { this.feedback.emit({ code: FeedbackCode.CameraDisabled, state: 'FEEDBACK_INFO', message: this.translationService.i('camera-disabled').toString() }); if (this.showActionLabels) { this.scanFromCameraButton.label = this.translationService.i('action-message-camera-disabled').toString(); } } } if (this.scanFromImage) { this.scanFromImageButton.visible = true; const imageScanIsAvailable = this.sdkService.isScanFromImageAvailable(this.recognizers, this.recognizerOptions); this.scanFromImageButton.disabled = !imageScanIsAvailable; if (imageScanIsAvailable) { this.imageRecognitionType = this.sdkService.getScanFromImageType(this.recognizers, this.recognizerOptions); if (this.imageRecognitionType === ImageRecognitionType.SingleSide) { this.screens.processing.setAttribute('data-type', 'single-sinde'); } if (this.imageRecognitionType === ImageRecognitionType.MultiSide) { this.screens.processing.setAttribute('data-type', 'multi-side'); } } else { if (this.showActionLabels) { this.scanFromImageButton.label = this.translationService.i('action-message-image-not-supported').toString(); } } } this.ready.emit(initEvent); this.blocked = false; this.block.emit(false); this.showScreen('action'); if (this.enableDrag) { this.setDragAndDrop(); } } private async flipCameraAction(): Promise { await this.sdkService.flipCamera(); const cameraFlipped = await this.sdkService.isCameraFlipped(); this.cameraExperience.setCameraFlipState(cameraFlipped); } private async changeCameraDevice(camera: CameraEntry) { if (this.cameraChangeInProgress) { return; } this.cameraChangeInProgress = true; await this.sdkService.changeCameraDevice(camera.details); this.cameraChangeInProgress = false; } private async checkInputProperties(): Promise { if (!this.licenseKey) { this.setFatalError(new SDKError(BlinkIDImageCaptureSDK.sdkErrors.licenseKeyMissing)); return false; } // Recognizers const conclusion: CheckConclusion = this.sdkService.checkRecognizers(this.recognizers); if (!conclusion.status) { const fatalError = new SDKError({ code: ErrorTypes.ErrorCodes.InvalidRecognizers, message: conclusion.message }); this.setFatalError(fatalError); return false; } this.cameraExperience.type = this.sdkService.getDesiredCameraExperience(this.recognizers, this.recognizerOptions); return true; } private async openDeviceModal() { this.startScanFromCamera(); } private async startScanFromCamera() { const configuration: VideoRecognitionConfiguration = { recognizers: this.recognizers, successFrame: this.includeSuccessFrame, cameraFeed: this.videoElement, cameraId: this.cameraId }; if (this.recognizerOptions && Object.keys(this.recognizerOptions).length > 0) { configuration.recognizerOptions = this.recognizerOptions; } if (this.recognitionTimeout && typeof this.recognitionTimeout === 'number') { configuration.recognitionTimeout = this.recognitionTimeout; } this.isBackSide = false; const eventHandler = (recognitionEvent: RecognitionEvent) => { switch (recognitionEvent.status) { case RecognitionStatus.Preparing: this.feedback.emit({ code: FeedbackCode.ScanStarted, state: 'FEEDBACK_OK', message: '' }); this.showOverlay('camera'); this.cameraExperience.setState(CameraExperienceState.Default); break; case RecognitionStatus.Ready: this.cameraExperience.setActiveCamera(this.sdkService.videoRecognizer.deviceId); break; case RecognitionStatus.Processing: // Just keep working break; case RecognitionStatus.EmptyResultState: if (!recognitionEvent.data.initiatedByUser) { this.scanError.emit({ code: Code.EmptyResult, fatal: false, message: 'Could not extract information from video feed!', recognizerName: recognitionEvent.data.recognizerName }); this.feedback.emit({ code: FeedbackCode.ScanUnsuccessful, state: 'FEEDBACK_ERROR', message: this.translationService.i('feedback-scan-unsuccessful').toString() }); } this.showOverlay(''); break; case RecognitionStatus.UnknownError: // Do nothing, RecognitionStatus.EmptyResultState will handle negative outcome break; case RecognitionStatus.DetectionFailed: this.cameraExperience.setState(CameraExperienceState.Default, this.isBackSide); this.detectionSuccessLock = false; break; case RecognitionStatus.DetectionStatusChange: // Use this event if information about card location is required break; case RecognitionStatus.DetectionStatusFail: this.cameraExperience.setState(CameraExperienceState.Default, this.isBackSide); break; case RecognitionStatus.DetectionStatusSuccess: this.detectionSuccessLock = true; window.setTimeout(() => { if (this.detectionSuccessLock) { this.cameraExperience.setState(CameraExperienceState.Detection); this.scanReset = false; } }, 100); break; case RecognitionStatus.DetectionStatusCameraTooHigh: this.cameraExperience.setState(CameraExperienceState.MoveCloser) .then(() => { this.cameraExperience.setState(CameraExperienceState.Default, this.isBackSide); }); break; case RecognitionStatus.DetectionStatusCameraAtAngle: this.cameraExperience.setState(CameraExperienceState.AdjustAngle) .then(() => { this.cameraExperience.setState(CameraExperienceState.Default, this.isBackSide); }); break; case RecognitionStatus.DetectionStatusCameraTooNear: case RecognitionStatus.DetectionStatusDocumentTooCloseToEdge: case RecognitionStatus.DetectionStatusPartial: this.cameraExperience.setState(CameraExperienceState.MoveFarther) .then(() => { this.cameraExperience.setState(CameraExperienceState.Default, this.isBackSide); }); break; case RecognitionStatus.DocumentClassified: this.cameraExperience.setState(CameraExperienceState.Classification); break; case RecognitionStatus.OnFirstSideResult: this.sdkService.videoRecognizer.pauseRecognition(); window.setTimeout(async () => { await this.sdkService.videoRecognizer.resumeRecognition(false); }, this.recognitionPauseTimeout); this.cameraExperience.setState(CameraExperienceState.Done, false, true) .then(() => { this.cameraExperience.setState(CameraExperienceState.Flip, this.isBackSide, true) .then(() => { if (!this.scanReset) { this.isBackSide = true; this.cameraExperience.setState( CameraExperienceState.Default, this.isBackSide ); } }); }) break; case RecognitionStatus.ScanSuccessful: /* Which recognizer is it? ImageCapture or some other? * * ImageCapture has the 'imageCapture' flag set to true, we do not want to close camera overlay after image * acquisition process is finished. Cause maybe backend service will failed and we can press retry to resume * with the same video recognizer and try again */ if (!recognitionEvent.data.imageCapture) { this.cameraExperience.setState(CameraExperienceState.DoneAll, false, true) .then(() => { this.cameraExperience.resetState(); this.cameraExperience.classList.add('hide'); this.scanSuccess.emit(recognitionEvent.data?.result); this.feedback.emit({ code: FeedbackCode.ScanSuccessful, state: 'FEEDBACK_OK', message: '' }); this.showOverlay(''); }); } else { const resultIsValid = recognitionEvent.data.result.recognizer.processingStatus === 0 && recognitionEvent.data.result.recognizer.state === 2; if (resultIsValid) { this.scanSuccess.emit(recognitionEvent.data?.result); this.feedback.emit({ code: FeedbackCode.ScanSuccessful, state: 'FEEDBACK_OK', message: '' }); } else if (!recognitionEvent.data.initiatedByUser) { this.scanError.emit({ code: Code.EmptyResult, fatal: true, message: 'Could not extract information from video feed!', recognizerName: recognitionEvent.data.recognizerName }); } } break; case RecognitionStatus.CameraNotAllowed: this.scanError.emit({ code: Code.CameraNotAllowed, fatal: true, message: 'Cannot access camera!', recognizerName: '' }); this.feedback.emit({ code: FeedbackCode.CameraNotAllowed, state: 'FEEDBACK_ERROR', message: this.translationService.i('camera-not-allowed').toString() }); window.setTimeout(() => { this.scanFromCameraButton.disabled = true; if (this.showActionLabels) { this.scanFromCameraButton.label = this.translationService.i('action-message-camera-not-allowed').toString(); } }, 10); this.showOverlay(''); break; case RecognitionStatus.CameraInUse: this.scanError.emit({ code: Code.CameraInUse, fatal: true, message: 'Camera already in use!', recognizerName: '' }); this.feedback.emit({ code: FeedbackCode.CameraInUse, state: 'FEEDBACK_ERROR', message: this.translationService.i('camera-in-use').toString() }); window.setTimeout(() => { this.scanFromCameraButton.disabled = true; if (this.showActionLabels) { this.scanFromCameraButton.label = this.translationService.i('action-message-camera-in-use').toString(); } }, 10); this.showOverlay(''); break; case RecognitionStatus.NoSupportForMediaDevices: case RecognitionStatus.CameraNotFound: case RecognitionStatus.UnableToAccessCamera: this.scanError.emit({ code: Code.CameraGenericError, fatal: true, message: `There was a problem while accessing camera ${recognitionEvent.status}`, recognizerName: '' }); this.feedback.emit({ code: FeedbackCode.CameraGenericError, state: 'FEEDBACK_ERROR', message: this.translationService.i('camera-generic-error').toString() }); window.setTimeout(() => { this.scanFromCameraButton.disabled = true; if (this.showActionLabels) { this.scanFromCameraButton.label = this.translationService.i('action-message-camera-disabled').toString(); } }, 10); this.showOverlay(''); break; default: // console.warn('Unhandled video recognition status:', recognitionEvent.status); } }; try { this.cameraExperience.classList.remove('hide'); this.cameraScanStarted.emit(); void this.cameraExperience.populateCameraDevices(); await this.sdkService.scanFromCamera(configuration, eventHandler); const cameraFlipped = this.sdkService.isCameraFlipped(); this.cameraExperience.setCameraFlipState(cameraFlipped); } catch (error) { this.handleScanError(error); this.showOverlay(''); } } private async startScanFromImage(file?: File) { const configuration: ImageRecognitionConfiguration = { recognizers: this.recognizers, file: file || this.scanFromImageInput.files[0] }; if (this.recognizerOptions && Object.keys(this.recognizerOptions).length > 0) { configuration.recognizerOptions = this.recognizerOptions; } const eventHandler = (recognitionEvent: RecognitionEvent) => { switch (recognitionEvent.status) { case RecognitionStatus.Preparing: this.feedback.emit({ code: FeedbackCode.ScanStarted, state: 'FEEDBACK_OK', message: '' }); this.showScanFromImageUi(); break; case RecognitionStatus.Processing: // Just keep working break; case RecognitionStatus.NoImageFileFound: this.scanError.emit({ code: Code.NoImageFileFound, fatal: true, message: 'No image file was provided to SDK service!', recognizerName: '' }); this.feedback.emit({ code: FeedbackCode.ScanUnsuccessful, state: 'FEEDBACK_ERROR', message: this.translationService.i('feedback-scan-unsuccessful').toString() }); this.hideScanFromImageUi(false); this.clearInputImages(); break; case RecognitionStatus.DetectionFailed: // Do nothing, RecognitionStatus.EmptyResultState will handle negative outcome this.clearInputImages(); break; case RecognitionStatus.EmptyResultState: this.scanError.emit({ code: Code.EmptyResult, fatal: false, message: 'Could not extract information from image!', recognizerName: recognitionEvent.data.recognizerName }); this.feedback.emit({ code: FeedbackCode.ScanUnsuccessful, state: 'FEEDBACK_ERROR', message: this.translationService.i('feedback-scan-unsuccessful').toString() }); this.hideScanFromImageUi(false); this.clearInputImages(); break; case RecognitionStatus.UnknownError: // Do nothing, RecognitionStatus.EmptyResultState will handle negative outcome this.clearInputImages(); break; case RecognitionStatus.ScanSuccessful: this.scanSuccess.emit(recognitionEvent.data); this.feedback.emit({ code: FeedbackCode.ScanSuccessful, state: 'FEEDBACK_OK', message: '' }); this.clearInputImages(); if (!recognitionEvent.data.imageCapture) { this.hideScanFromImageUi(true); } break; default: //console.warn('Unhandled image recognition status:', recognitionEvent.status); } }; try { this.imageScanStarted.emit(); if (this.thoroughScanFromImage) { configuration.thoroughScan = true; } await this.sdkService.scanFromImage(configuration, eventHandler); } catch (error) { this.handleScanError(error); this.hideScanFromImageUi(false); } } private async startScanFromImageMultiSide(firstFile?: File, secondFile?: File) { const configuration: MultiSideImageRecognitionConfiguration = { recognizers: this.recognizers, firstFile: firstFile || this.galleryImageFirstFile, secondFile: secondFile || this.galleryImageSecondFile }; if (this.recognizerOptions) { configuration.recognizerOptions = this.recognizerOptions; } const eventHandler = (recognitionEvent: RecognitionEvent) => { switch (recognitionEvent.status) { case RecognitionStatus.Preparing: this.showScanFromImageUi(); this.feedback.emit({ code: FeedbackCode.ScanStarted, state: 'FEEDBACK_OK', message: '' }); break; case RecognitionStatus.Ready: this.cameraExperience.setActiveCamera(this.sdkService.videoRecognizer.deviceId); break; case RecognitionStatus.Processing: // Just keep working break; case RecognitionStatus.NoFirstImageFileFound: this.scanError.emit({ code: Code.NoFirstImageFileFound, fatal: true, message: 'First image file is missing!', recognizerName: '' }); this.feedback.emit({ code: FeedbackCode.ScanUnsuccessful, state: 'FEEDBACK_ERROR', message: this.translationService.i('feedback-scan-unsuccessful').toString() }); this.hideScanFromImageUi(false); this.clearInputImages(); break; case RecognitionStatus.NoSecondImageFileFound: this.scanError.emit({ code: Code.NoSecondImageFileFound, fatal: true, message: 'Second image file is missing!', recognizerName: '' }); this.feedback.emit({ code: FeedbackCode.ScanUnsuccessful, state: 'FEEDBACK_ERROR', message: this.translationService.i('feedback-scan-unsuccessful').toString() }); this.hideScanFromImageUi(false); this.clearInputImages(); break; case RecognitionStatus.DetectionFailed: // Do nothing, RecognitionStatus.EmptyResultState will handle negative outcome this.clearInputImages(); break; case RecognitionStatus.EmptyResultState: this.scanError.emit({ code: Code.EmptyResult, fatal: false, message: 'Could not extract information from image!', recognizerName: recognitionEvent.data.recognizerName }); this.feedback.emit({ code: FeedbackCode.ScanUnsuccessful, state: 'FEEDBACK_ERROR', message: this.translationService.i('feedback-scan-unsuccessful').toString() }); this.hideScanFromImageUi(false); this.clearInputImages(); break; case RecognitionStatus.UnknownError: // Do nothing, RecognitionStatus.EmptyResultState will handle negative outcome this.clearInputImages(); break; case RecognitionStatus.ScanSuccessful: this.scanSuccess.emit(recognitionEvent.data); this.feedback.emit({ code: FeedbackCode.ScanSuccessful, state: 'FEEDBACK_OK', message: '' }); this.clearInputImages(); if (!recognitionEvent.data.imageCapture) { this.hideScanFromImageUi(true); } break; default: //console.warn('Unhandled image recognition status:', recognitionEvent.status); } }; try { this.imageScanStarted.emit(); if (this.thoroughScanFromImage) { configuration.thoroughScan = true; } await this.sdkService.scanFromImageMultiSide(configuration, eventHandler); } catch (error) { this.handleScanError(error); this.hideScanFromImageUi(false); } } private handleScanError(error: any) { const isAvailable = navigator.onLine; if (!isAvailable) { const fatalError = new SDKError({ code: ErrorTypes.ErrorCodes.InternetNotAvailable, message: this.translationService.i('check-internet-connection').toString() }); this.setFatalError(fatalError); this.showLicenseInfoModal( this.translationService.i('check-internet-connection').toString() ); return; } if (error?.code === BlinkIDImageCaptureSDK.ErrorCodes.LICENSE_UNLOCK_ERROR) { this.setFatalError(new SDKError(ErrorTypes.componentErrors.licenseError, error)); this.showLicenseInfoModal(error); } else { this.scanError.emit({ code: Code.GenericScanError, fatal: true, message: 'There was a problem during scan action.', recognizerName: '', details: error }); this.feedback.emit({ code: FeedbackCode.GenericScanError, state: 'FEEDBACK_ERROR', message: this.translationService.i('feedback-error-generic').toString() }); this.showOverlay(''); } } private showLicenseInfoModal(error: any): void { if (typeof error === 'string') { this.licenseExperienceModal.content = error; } else { if (error.type === 'NETWORK_ERROR') { this.licenseExperienceModal.content = this.translationService.i('network-error').toString(); } else { this.licenseExperienceModal.content = this.translationService.i('scanning-not-available').toString(); } } this.showOverlay('modal'); } private showScreen(screenName: string) { for (const screenKey in this.screens) { if (this.screens[screenKey]) { this.screens[screenKey].visible = screenName === screenKey; } } } private showOverlay(overlayName: string) { if (overlayName === 'camera') { this.initialBodyOverflowValue = document.body.style.overflow; document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = this.initialBodyOverflowValue; } for (const overlayKey in this.overlays) { if (this.overlays[overlayKey]) { this.overlays[overlayKey].visible = overlayName === overlayKey; } } } private setDragAndDrop() { const dropTarget = this.galleryDropType === 'FULLSCREEN' ? window : this.hostEl; const lockTimeout = 3000; let lockDragAndDrop = false; if (this.galleryDropType === 'INLINE') { this.overlays.draganddrop.classList.add('inline'); } const closeOverlay = () => { if (lockDragAndDrop) { window.setTimeout(() => { this.hostEl.style.borderStyle = 'solid'; this.overlays.draganddrop.classList.add('hidden') this.showOverlay(''); window.setTimeout(() => { this.overlays.draganddrop.classList.remove('hidden') this.showScreen('action'); this.hostEl.style.borderStyle = 'solid'; }, 500); }, lockTimeout); } else { this.showOverlay(''); window.setTimeout(() => { this.showScreen('action'); this.hostEl.style.borderStyle = 'solid'; }, 500); } } dropTarget.addEventListener('dragenter', (ev: any) => { ev.stopPropagation(); ev.preventDefault(); if (!this.scanFromImageButton.visible) { return; } this.hostEl.style.borderStyle = 'none'; }); dropTarget.addEventListener('dragover', (ev: any) => { ev.stopPropagation(); ev.preventDefault(); if (!this.scanFromImageButton.visible) { return; } this.hostEl.style.borderStyle = 'none'; this.overlays.draganddrop.classList.remove('error'); this.overlays.draganddrop.querySelector('img').src = this.iconDragAndDropGalleryDefault; this.overlays.draganddrop.querySelector('p').innerText = this.translationService.i('drop-info').toString(); this.showOverlay('draganddrop'); }); this.dragAndDropZone.addEventListener('dragleave', (ev: any) => { ev.stopPropagation(); ev.preventDefault(); if (!this.scanFromImageButton.visible) { return; } closeOverlay(); }); this.dragAndDropZone.addEventListener('drop', (ev: any) => { ev.stopPropagation(); ev.preventDefault(); if (!this.scanFromImageButton.visible) { return; } if (GenericHelpers.hasSupportedImageFiles(ev.dataTransfer.files)) { this.startScanFromImage(ev.dataTransfer.files[0]); } else { this.overlays.draganddrop.classList.add('error'); this.overlays.draganddrop.querySelector('p').innerText = this.translationService.i('drop-error').toString(); this.overlays.draganddrop.querySelector('img').src = this.iconDragAndDropWarningDefault; lockDragAndDrop = true; window.setTimeout(() => { lockDragAndDrop = false }, lockTimeout); } closeOverlay(); }); } private setFatalError(error: SDKError) { this.fatalError.emit(error); if (this.hideLoadingAndErrorUi) { return; } if (error.details) { switch (error.details?.code) { case BlinkIDImageCaptureSDK.ErrorCodes.LICENSE_UNLOCK_ERROR: const licenseErrorType = error.details?.type; switch (licenseErrorType) { case BlinkIDImageCaptureSDK.LicenseErrorType.NetworkError: this.errorMessage.innerText = this.translationService.i('network-error').toString(); break; default: this.errorMessage.innerText = this.translationService.i('scanning-not-available').toString(); } break; default: // Do nothing } } else { this.errorMessage.innerText = error.message; } this.showScreen('error'); this.showOverlay(''); } private abortScan() { this.scanAborted.emit(); this.stopRecognition(); } private stopRecognition() { this.cameraExperience.classList.add('hide'); this.sdkService.stopRecognition(); this.scanReset = true; window.setTimeout(() => { this.cameraExperience.setState(CameraExperienceState.Default, false, true); this.cameraExperience.apiState = ''; }, 500); this.showOverlay(''); this.closeApiProcessStatus(); } private closeGalleryExperienceModal() { this.galleryExperienceModalErrorWindowVisible = false; this.stopRecognition(); } private onFromImageClicked(): void { if (this.imageRecognitionType === ImageRecognitionType.SingleSide) { this.scanFromImageInput.click(); } if (this.imageRecognitionType === ImageRecognitionType.MultiSide) { if (this.multiSideGalleryOpened) { this.closeMultiSideGalleryUpload(); } else { this.openMultiSideGalleryUpload(); } } } private clearInputImages(): void { if (this.imageRecognitionType === ImageRecognitionType.SingleSide) { this.scanFromImageInput.value = ''; } if (this.imageRecognitionType === ImageRecognitionType.MultiSide) { this.imageBoxFirst.clear(); this.imageBoxSecond.clear(); } } private openMultiSideGalleryUpload(): void { const dialog = this.screens.action.querySelector('.multi-side-image-upload'); dialog.classList.add('visible'); this.scanFromImageButton.selected = true; this.multiSideGalleryOpened = true; } private closeMultiSideGalleryUpload(): void { const dialog = this.screens.action.querySelector('.multi-side-image-upload'); dialog.classList.remove('visible'); this.scanFromImageButton.selected = false; this.multiSideGalleryOpened = false; } private async onMultiSideImageChange(ev: FileList, imageType: MultiSideImageType) { if (imageType === MultiSideImageType.First) { this.galleryImageFirstFile = GenericHelpers.getImageFile(ev); } if (imageType === MultiSideImageType.Second) { this.galleryImageSecondFile = GenericHelpers.getImageFile(ev); } // Enable scan button only if both images have values this.multiSideScanFromImageButton.disabled = this.galleryImageFirstFile === null || this.galleryImageSecondFile === null; } private showScanFromImageUi(): void { if (this.galleryOverlayType === 'INLINE') { const inProgress = this.screens.processing.querySelector('p.in-progress'); const done = this.screens.processing.querySelector('p.done'); inProgress.classList.add('visible'); done.classList.remove('visible'); this.showScreen('processing'); } if (this.galleryOverlayType === 'FULLSCREEN') { this.showOverlay('processing'); } } private hideScanFromImageUi(success: boolean): void { if (this.galleryOverlayType === 'INLINE') { let timeout = 0; const inProgress = this.screens.processing.querySelector('p.in-progress'); const done = this.screens.processing.querySelector('p.done'); inProgress.classList.remove('visible'); if (success) { done.classList.add('visible'); timeout = 1000; } window.setTimeout(() => this.showScreen('action'), timeout); } if (this.galleryOverlayType === 'FULLSCREEN') { this.showOverlay(''); } } render() { return ( {/* Loading screen */} this.screens.loading = el as HTMLMbScreenElement} > {/* Error Screen */} this.screens.error = el as HTMLMbScreenElement} >

this.errorMessage = el as HTMLParagraphElement}>

{/* Main action screen */} this.screens.action = el as HTMLMbScreenElement} >

{this.translationService.i('action-message').toString()}

this.scanFromCameraButton = el as HTMLMbButtonElement} visible={true} disabled={false} clickHandler={() => this.openDeviceModal()} imageSrcDefault={this.iconCameraDefault} imageSrcActive={this.iconCameraActive} buttonTitle={this.translationService.i('action-alt-camera') as string} > this.scanFromImageInput = el as HTMLInputElement} type="file" accept="image/*" onChange={() => this.scanFromImageInput.value && this.startScanFromImage()} /> this.scanFromImageButton = el as HTMLMbButtonElement} disabled={false} visible={false} selected={false} clickHandler={() => this.onFromImageClicked()} imageSrcDefault={this.iconGalleryDefault} imageSrcActive={this.iconGalleryActive} buttonTitle={this.translationService.i('action-alt-gallery') as string} >
{/* Multi-side image upload */}
this.imageBoxFirst = el as HTMLMbImageBoxElement} box-title={this.translationService.i('process-image-box-first').toString()} anchor-text={this.translationService.i('process-image-box-add').toString()} onImageChange={(ev: CustomEvent) => this.onMultiSideImageChange(ev.detail, MultiSideImageType.First)}> this.imageBoxSecond = el as HTMLMbImageBoxElement} box-title={this.translationService.i('process-image-box-second').toString()} anchor-text={this.translationService.i('process-image-box-add').toString()} onImageChange={(ev: CustomEvent) => this.onMultiSideImageChange(ev.detail, MultiSideImageType.Second)}> this.multiSideScanFromImageButton = el as HTMLMbButtonClassicElement} disabled={true} clickHandler={() => this.startScanFromImageMultiSide()} >{this.translationService.i('process-image-upload-cta').toString()}
{/* Processing screen */} this.screens.processing = el as HTMLMbScreenElement} >

{this.translationService.i('process-image-message-inline').toString()}

{this.translationService.i('process-image-message-inline-done').toString()}

{/* Drag and drop overlay */} this.overlays.draganddrop = el as HTMLMbOverlayElement} >

Whoops, we don't support that image format. Please upload a JPEG or PNG file.

this.dragAndDropZone = el as HTMLDivElement}>
{/* Gallery experience overlay */} this.overlays.processing = el as HTMLMbOverlayElement} >

{this.translationService.i('process-image-message').toString()}

this.closeGalleryExperienceModal()} >
{/* Camera experience overlay */} this.overlays.camera = el as HTMLMbOverlayElement} >
this.cameraExperience = el as HTMLMbCameraExperienceElement} cameraExperienceStateDurations={this.cameraExperienceStateDurations} translationService={this.translationService} showScanningLine={this.showScanningLine} showCameraFeedbackBarcodeMessage={this.showCameraFeedbackBarcodeMessage} clear-is-camera-active={this.clearIsCameraActive} onClose={() => this.abortScan()} onFlipCameraAction={() => this.flipCameraAction()} onSetIsCameraActive={(ev: CustomEvent) => this.handleSetIsCameraActive(ev.detail)} onChangeCameraDevice={(ev: CustomEvent) => this.changeCameraDevice(ev.detail)} class="overlay-camera-element" > this.closeApiProcessStatus(true)} onCloseFromStart={() => this.stopRecognition()} >
this.overlays.modal = el as HTMLMbOverlayElement} > this.licenseExperienceModal = el as HTMLMbModalElement} modalTitle="Error" >
); } }