UNPKG

28.7 kBPlain TextView Raw
1import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observer";
2
3// tslint:disable-next-line: variable-name
4const ResizeObserver: typeof ResizeObserverPolyfill =
5 // tslint:disable-next-line: no-any
6 "ResizeObserver" in window ? /* istanbul ignore next */ (<any>window).ResizeObserver : ResizeObserverPolyfill;
7
8import {
9 cameraImage,
10 laserActiveImage,
11 laserPausedImage,
12 scanditLogoImage,
13 switchCameraImage,
14 toggleTorchImage
15} from "./assets/base64assets";
16import { BarcodePicker } from "./barcodePicker";
17import { BrowserHelper } from "./browserHelper";
18import { Camera } from "./camera";
19import { CameraAccess } from "./cameraAccess";
20import { CameraManager } from "./cameraManager";
21import { ImageSettings } from "./imageSettings";
22import { Scanner } from "./scanner";
23import { ScanSettings } from "./scanSettings";
24import { SearchArea } from "./searchArea";
25
26/**
27 * @hidden
28 */
29export class BarcodePickerGui {
30 public static readonly grandParentElementClassName: string = "scandit scandit-container";
31 public static readonly parentElementClassName: string = "scandit scandit-barcode-picker";
32 public static readonly hiddenClassName: string = "scandit-hidden";
33 public static readonly hiddenOpacityClassName: string = "scandit-hidden-opacity";
34 public static readonly videoElementClassName: string = "scandit-video";
35 public static readonly scanditLogoImageElementClassName: string = "scandit-logo";
36 public static readonly laserContainerElementClassName: string = "scandit-laser";
37 public static readonly viewfinderElementClassName: string = "scandit-viewfinder";
38 public static readonly cameraSwitcherElementClassName: string = "scandit-camera-switcher";
39 public static readonly torchTogglerElementClassName: string = "scandit-torch-toggle";
40 public static readonly cameraUploadElementClassName: string = "scandit-camera-upload";
41 public static readonly flashColorClassName: string = "scandit-flash-color";
42 public static readonly flashWhiteClassName: string = "scandit-flash-white";
43 public static readonly flashWhiteInsetClassName: string = "scandit-flash-white-inset";
44 public static readonly opacityPulseClassName: string = "scandit-opacity-pulse";
45 public static readonly mirroredClassName: string = "mirrored";
46 public static readonly pausedClassName: string = "paused";
47
48 public readonly videoElement: HTMLVideoElement;
49 public readonly cameraSwitcherElement: HTMLImageElement;
50 public readonly torchTogglerElement: HTMLImageElement;
51
52 private readonly scanner: Scanner;
53 private readonly singleImageMode: boolean;
54 private readonly grandParentElement: HTMLDivElement;
55 private readonly parentElement: HTMLDivElement;
56 private readonly laserContainerElement: HTMLDivElement;
57 private readonly laserActiveImageElement: HTMLImageElement;
58 private readonly laserPausedImageElement: HTMLImageElement;
59 private readonly viewfinderElement: HTMLDivElement;
60 private readonly cameraUploadElement: HTMLDivElement;
61 private readonly cameraUploadInputElement: HTMLInputElement;
62 private readonly cameraUploadLabelElement: HTMLLabelElement;
63 private readonly cameraUploadProgressElement: HTMLDivElement;
64 private readonly videoImageCanvasContext: CanvasRenderingContext2D;
65 private readonly visibilityListener: EventListenerOrEventListenerObject;
66 private readonly videoResizeListener: EventListenerOrEventListenerObject;
67 private readonly newScanSettingsListener: (scanSettings: ScanSettings) => void;
68 private readonly licenseFeaturesReadyListener: (licenseFeatures: object) => void;
69 private readonly resizeObserver: ResizeObserverPolyfill;
70 private readonly cameraUploadCallback: () => Promise<void>;
71 private readonly mirrorImageOverrides: Map<string, boolean>;
72
73 private cameraManager?: CameraManager;
74 private originElement: HTMLElement;
75 private scanningPaused: boolean;
76 private visible: boolean;
77 private guiStyle: BarcodePicker.GuiStyle;
78 private videoFit: BarcodePicker.ObjectFit;
79 private customLaserArea?: SearchArea;
80 private customViewfinderArea?: SearchArea;
81
82 public constructor(options: {
83 scanner: Scanner;
84 originElement: HTMLElement;
85 singleImageMode: boolean;
86 scanningPaused: boolean;
87 visible: boolean;
88 guiStyle: BarcodePicker.GuiStyle;
89 videoFit: BarcodePicker.ObjectFit;
90 hideLogo: boolean;
91 laserArea?: SearchArea;
92 viewfinderArea?: SearchArea;
93 cameraUploadCallback(): Promise<void>;
94 }) {
95 this.scanner = options.scanner;
96 this.originElement = options.originElement;
97 this.singleImageMode = options.singleImageMode;
98 this.scanningPaused = options.scanningPaused;
99 this.cameraUploadCallback = options.cameraUploadCallback;
100 this.mirrorImageOverrides = new Map<string, boolean>();
101
102 this.grandParentElement = document.createElement("div");
103 this.grandParentElement.className = BarcodePickerGui.grandParentElementClassName;
104 this.originElement.appendChild(this.grandParentElement);
105 this.parentElement = document.createElement("div");
106 this.parentElement.className = BarcodePickerGui.parentElementClassName;
107 this.grandParentElement.appendChild(this.parentElement);
108
109 this.videoImageCanvasContext = <CanvasRenderingContext2D>document.createElement("canvas").getContext("2d");
110
111 this.videoElement = document.createElement("video");
112 this.cameraSwitcherElement = document.createElement("img");
113 this.torchTogglerElement = document.createElement("img");
114 this.laserContainerElement = document.createElement("div");
115 this.laserActiveImageElement = document.createElement("img");
116 this.laserPausedImageElement = document.createElement("img");
117 this.viewfinderElement = document.createElement("div");
118
119 if (options.singleImageMode) {
120 this.cameraUploadElement = document.createElement("div");
121 this.cameraUploadInputElement = document.createElement("input");
122 this.cameraUploadLabelElement = document.createElement("label");
123 this.cameraUploadProgressElement = document.createElement("div");
124
125 this.setupCameraUploadGuiAssets();
126
127 this.guiStyle = BarcodePicker.GuiStyle.NONE;
128 } else {
129 this.setupVideoElement();
130 this.setupCameraSwitcher();
131 this.setupTorchToggler();
132 this.setupFullGuiAssets();
133 this.setGuiStyle(options.guiStyle);
134 this.setVideoFit(options.videoFit);
135 this.setLaserArea(options.laserArea);
136 this.setViewfinderArea(options.viewfinderArea);
137
138 this.visibilityListener = this.checkAndRecoverPlayback.bind(this);
139 document.addEventListener("visibilitychange", this.visibilityListener);
140
141 this.newScanSettingsListener = this.handleNewScanSettings.bind(this);
142 this.scanner.onNewScanSettings(this.newScanSettingsListener);
143 this.handleNewScanSettings();
144
145 this.videoResizeListener = this.handleVideoResize.bind(this);
146 this.videoElement.addEventListener("resize", this.videoResizeListener);
147 }
148
149 if (options.hideLogo) {
150 this.licenseFeaturesReadyListener = this.showScanditLogo.bind(this, options.hideLogo);
151 this.scanner.onLicenseFeaturesReady(this.licenseFeaturesReadyListener);
152 } else {
153 this.showScanditLogo(false);
154 }
155
156 this.resize();
157 this.resizeObserver = new ResizeObserver(() => {
158 this.resize();
159 });
160 this.resizeObserver.observe(this.originElement);
161
162 this.setVisible(options.visible);
163 }
164
165 public destroy(): void {
166 if (this.visibilityListener != null) {
167 document.removeEventListener("visibilitychange", this.visibilityListener);
168 }
169 if (this.newScanSettingsListener != null) {
170 this.scanner.removeListener("newScanSettings", this.newScanSettingsListener);
171 }
172 if (this.videoResizeListener != null) {
173 document.removeEventListener("resize", this.videoResizeListener);
174 }
175 if (this.licenseFeaturesReadyListener != null) {
176 this.scanner.removeListener("licenseFeaturesReady", this.licenseFeaturesReadyListener);
177 }
178 this.resizeObserver.disconnect();
179 this.grandParentElement.remove();
180 this.originElement.classList.remove(BarcodePickerGui.hiddenClassName);
181 }
182
183 public setCameraManager(cameraManager: CameraManager): void {
184 this.cameraManager = cameraManager;
185 }
186
187 public pauseScanning(): void {
188 this.scanningPaused = true;
189 this.laserActiveImageElement.classList.add(BarcodePickerGui.hiddenOpacityClassName);
190 this.laserPausedImageElement.classList.remove(BarcodePickerGui.hiddenOpacityClassName);
191 this.viewfinderElement.classList.add(BarcodePickerGui.pausedClassName);
192 }
193
194 public resumeScanning(): void {
195 this.scanningPaused = false;
196 this.laserPausedImageElement.classList.add(BarcodePickerGui.hiddenOpacityClassName);
197 this.laserActiveImageElement.classList.remove(BarcodePickerGui.hiddenOpacityClassName);
198 this.viewfinderElement.classList.remove(BarcodePickerGui.pausedClassName);
199 }
200
201 public isVisible(): boolean {
202 return this.visible;
203 }
204
205 public setVisible(visible: boolean): void {
206 const browserName: string | undefined = BrowserHelper.userAgentInfo.getBrowser().name;
207 if (browserName != null && browserName.includes("Safari") && this.visible != null && !this.visible && visible) {
208 // Safari behaves very weirdly when displaying the video element again after being hidden:
209 // it undetectably reuses video frames "buffered" from the video just before it was hidden.
210 // We do this to avoid reusing old data
211 this.videoElement.pause();
212 this.videoElement.currentTime = 0;
213 this.videoElement.load();
214 this.playVideo();
215 }
216
217 this.visible = visible;
218
219 if (visible) {
220 this.originElement.classList.remove(BarcodePickerGui.hiddenClassName);
221 if (this.guiStyle === BarcodePicker.GuiStyle.LASER) {
222 this.laserActiveImageElement.classList.remove(BarcodePickerGui.flashColorClassName);
223 } else if (this.guiStyle === BarcodePicker.GuiStyle.VIEWFINDER) {
224 this.viewfinderElement.classList.remove(BarcodePickerGui.flashWhiteClassName);
225 }
226 } else {
227 this.originElement.classList.add(BarcodePickerGui.hiddenClassName);
228 }
229 }
230
231 public isMirrorImageEnabled(): boolean {
232 if (
233 this.cameraManager != null &&
234 this.cameraManager.selectedCamera != null &&
235 this.cameraManager.activeCamera != null
236 ) {
237 const mirrorImageOverride: boolean | undefined = this.mirrorImageOverrides.get(
238 this.cameraManager.activeCamera.deviceId + this.cameraManager.activeCamera.label
239 );
240 if (mirrorImageOverride != null) {
241 return mirrorImageOverride;
242 } else {
243 return this.cameraManager.activeCamera.cameraType === Camera.Type.FRONT;
244 }
245 } else {
246 return false;
247 }
248 }
249
250 public setMirrorImageEnabled(enabled: boolean, override: boolean): void {
251 if (this.cameraManager != null && this.cameraManager.selectedCamera != null) {
252 if (enabled) {
253 this.videoElement.classList.add(BarcodePickerGui.mirroredClassName);
254 } else {
255 this.videoElement.classList.remove(BarcodePickerGui.mirroredClassName);
256 }
257
258 if (override) {
259 this.mirrorImageOverrides.set(
260 this.cameraManager.selectedCamera.deviceId + this.cameraManager.selectedCamera.label,
261 enabled
262 );
263 }
264 }
265 }
266
267 public setGuiStyle(guiStyle: BarcodePicker.GuiStyle): void {
268 if (this.singleImageMode) {
269 return;
270 }
271
272 switch (guiStyle) {
273 case BarcodePicker.GuiStyle.LASER:
274 this.guiStyle = guiStyle;
275 this.laserContainerElement.classList.remove(BarcodePickerGui.hiddenClassName);
276 this.viewfinderElement.classList.add(BarcodePickerGui.hiddenClassName);
277 break;
278 case BarcodePicker.GuiStyle.VIEWFINDER:
279 this.guiStyle = guiStyle;
280 this.laserContainerElement.classList.add(BarcodePickerGui.hiddenClassName);
281 this.viewfinderElement.classList.remove(BarcodePickerGui.hiddenClassName);
282 break;
283 case BarcodePicker.GuiStyle.NONE:
284 default:
285 this.guiStyle = BarcodePicker.GuiStyle.NONE;
286 this.laserContainerElement.classList.add(BarcodePickerGui.hiddenClassName);
287 this.viewfinderElement.classList.add(BarcodePickerGui.hiddenClassName);
288 break;
289 }
290 }
291
292 public setLaserArea(area?: SearchArea): void {
293 this.customLaserArea = area;
294 if (area == null) {
295 area = this.scanner.getScanSettings().getSearchArea();
296 }
297 const borderPercentage: number = 0.025;
298 const usablePercentage: number = 1 - borderPercentage * 2;
299 this.laserContainerElement.style.left = `${(borderPercentage + area.x * usablePercentage) * 100}%`;
300 this.laserContainerElement.style.width = `${area.width * usablePercentage * 100}%`;
301 this.laserContainerElement.style.top = `${(borderPercentage + area.y * usablePercentage) * 100}%`;
302 this.laserContainerElement.style.height = `${area.height * usablePercentage * 100}%`;
303 }
304
305 public setViewfinderArea(area?: SearchArea): void {
306 this.customViewfinderArea = area;
307 if (area == null) {
308 area = this.scanner.getScanSettings().getSearchArea();
309 }
310 const borderPercentage: number = 0.025;
311 const usablePercentage: number = 1 - borderPercentage * 2;
312 this.viewfinderElement.style.left = `${(borderPercentage + area.x * usablePercentage) * 100}%`;
313 this.viewfinderElement.style.width = `${area.width * usablePercentage * 100}%`;
314 this.viewfinderElement.style.top = `${(borderPercentage + area.y * usablePercentage) * 100}%`;
315 this.viewfinderElement.style.height = `${area.height * usablePercentage * 100}%`;
316 }
317
318 public setVideoFit(objectFit: BarcodePicker.ObjectFit): void {
319 if (this.singleImageMode) {
320 return;
321 }
322
323 this.videoFit = objectFit;
324
325 if (objectFit === BarcodePicker.ObjectFit.COVER) {
326 this.videoElement.style.objectFit = "cover";
327 this.videoElement.dataset.objectFit = "cover"; // used by "objectFitPolyfill" library
328 } else {
329 this.videoElement.style.objectFit = "contain";
330 this.videoElement.dataset.objectFit = "contain"; // used by "objectFitPolyfill" library
331
332 this.scanner.applyScanSettings(
333 this.scanner.getScanSettings().setBaseSearchArea({ x: 0, y: 0, width: 1.0, height: 1.0 })
334 );
335 }
336
337 this.resize();
338 }
339
340 public reassignOriginElement(originElement: HTMLElement): void {
341 if (!this.visible) {
342 this.originElement.classList.remove(BarcodePickerGui.hiddenClassName);
343 originElement.classList.add(BarcodePickerGui.hiddenClassName);
344 }
345
346 originElement.appendChild(this.grandParentElement);
347 this.checkAndRecoverPlayback();
348 this.resize();
349 this.resizeObserver.disconnect();
350 this.resizeObserver.observe(originElement);
351
352 this.originElement = originElement;
353 }
354
355 public flashGUI(): void {
356 if (this.guiStyle === BarcodePicker.GuiStyle.LASER) {
357 this.flashLaser();
358 } else if (this.guiStyle === BarcodePicker.GuiStyle.VIEWFINDER) {
359 this.flashViewfinder();
360 }
361 }
362
363 public getVideoImageData(): Uint8ClampedArray | undefined {
364 if (!this.singleImageMode) {
365 // This could happen in very weird situations and should be temporary
366 if (
367 this.videoElement.readyState !== 4 ||
368 this.videoImageCanvasContext.canvas.width <= 2 ||
369 this.videoImageCanvasContext.canvas.height <= 2
370 ) {
371 return undefined;
372 }
373
374 this.videoImageCanvasContext.drawImage(this.videoElement, 0, 0);
375 }
376
377 return this.videoImageCanvasContext.getImageData(
378 0,
379 0,
380 this.videoImageCanvasContext.canvas.width,
381 this.videoImageCanvasContext.canvas.height
382 ).data;
383 }
384
385 public getVideoCurrentTime(): number {
386 return this.videoElement.currentTime;
387 }
388
389 public setCameraSwitcherVisible(visible: boolean): void {
390 if (visible) {
391 this.cameraSwitcherElement.classList.remove(BarcodePickerGui.hiddenClassName);
392 } else {
393 this.cameraSwitcherElement.classList.add(BarcodePickerGui.hiddenClassName);
394 }
395 }
396
397 public setTorchTogglerVisible(visible: boolean): void {
398 if (visible) {
399 this.torchTogglerElement.classList.remove(BarcodePickerGui.hiddenClassName);
400 } else {
401 this.torchTogglerElement.classList.add(BarcodePickerGui.hiddenClassName);
402 }
403 }
404
405 public playVideo(): void {
406 const playPromise: Promise<void> = this.videoElement.play();
407 if (playPromise != null) {
408 playPromise.catch(
409 /* istanbul ignore next */ () => {
410 // Can sometimes cause an incorrect rejection (all is good, ignore).
411 }
412 );
413 }
414 }
415
416 private setCameraUploadGuiAvailable(available: boolean): void {
417 if (available) {
418 this.cameraUploadProgressElement.classList.add(BarcodePickerGui.flashWhiteInsetClassName);
419 this.cameraUploadElement.classList.remove(BarcodePickerGui.opacityPulseClassName);
420 } else {
421 this.cameraUploadProgressElement.classList.remove(BarcodePickerGui.flashWhiteInsetClassName);
422 this.cameraUploadElement.classList.add(BarcodePickerGui.opacityPulseClassName);
423 }
424 }
425
426 private setupVideoElement(): void {
427 this.videoElement.setAttribute("autoplay", "autoplay");
428 this.videoElement.setAttribute("playsinline", "true");
429 this.videoElement.setAttribute("muted", "muted");
430 this.videoElement.className = BarcodePickerGui.videoElementClassName;
431 this.parentElement.appendChild(this.videoElement);
432 }
433
434 private setupCameraUploadGuiAssets(): void {
435 this.cameraUploadElement.className = BarcodePickerGui.cameraUploadElementClassName;
436 this.parentElement.appendChild(this.cameraUploadElement);
437 this.cameraUploadInputElement.type = "file";
438 this.cameraUploadInputElement.accept = "image/*";
439 this.cameraUploadInputElement.setAttribute("capture", "environment");
440 this.cameraUploadInputElement.addEventListener("change", this.cameraUploadFile.bind(this));
441 this.cameraUploadInputElement.addEventListener(
442 "click",
443 /* istanbul ignore next */ event => {
444 if (this.scanningPaused || this.scanner.isBusyProcessing()) {
445 event.preventDefault();
446 }
447 }
448 );
449 this.cameraUploadLabelElement.appendChild(this.cameraUploadInputElement);
450 this.cameraUploadElement.appendChild(this.cameraUploadLabelElement);
451 const cameraUploadImageElement: HTMLImageElement = document.createElement("img");
452 cameraUploadImageElement.src = cameraImage;
453 this.cameraUploadLabelElement.appendChild(cameraUploadImageElement);
454 const cameraUploadTextElement: HTMLDivElement = document.createElement("div");
455 cameraUploadTextElement.innerText = "Scan from Camera";
456 this.cameraUploadLabelElement.appendChild(cameraUploadTextElement);
457
458 this.cameraUploadProgressElement.classList.add("radial-progress");
459 this.cameraUploadElement.appendChild(this.cameraUploadProgressElement);
460 }
461
462 private setupFullGuiAssets(): void {
463 this.laserActiveImageElement.src = laserActiveImage;
464 this.laserContainerElement.appendChild(this.laserActiveImageElement);
465
466 this.laserPausedImageElement.src = laserPausedImage;
467 this.laserContainerElement.appendChild(this.laserPausedImageElement);
468
469 this.laserContainerElement.className = BarcodePickerGui.laserContainerElementClassName;
470 this.parentElement.appendChild(this.laserContainerElement);
471
472 this.viewfinderElement.className = BarcodePickerGui.viewfinderElementClassName;
473 this.parentElement.appendChild(this.viewfinderElement);
474
475 // Show inactive GUI, as for now the scanner isn't ready yet
476 this.laserActiveImageElement.classList.add(BarcodePickerGui.hiddenOpacityClassName);
477 this.laserPausedImageElement.classList.remove(BarcodePickerGui.hiddenOpacityClassName);
478 this.viewfinderElement.classList.add(BarcodePickerGui.pausedClassName);
479 }
480
481 private flashLaser(): void {
482 this.laserActiveImageElement.classList.remove(BarcodePickerGui.flashColorClassName);
483 // tslint:disable-next-line:no-unused-expression
484 this.laserActiveImageElement.offsetHeight; // NOSONAR // Trigger reflow to restart animation
485 this.laserActiveImageElement.classList.add(BarcodePickerGui.flashColorClassName);
486 }
487
488 private flashViewfinder(): void {
489 this.viewfinderElement.classList.remove(BarcodePickerGui.flashWhiteClassName);
490 // tslint:disable-next-line:no-unused-expression
491 this.viewfinderElement.offsetHeight; // NOSONAR // Trigger reflow to restart animation
492 this.viewfinderElement.classList.add(BarcodePickerGui.flashWhiteClassName);
493 }
494
495 private resize(): void {
496 if (this.singleImageMode) {
497 this.resizeCameraUpload();
498 } else {
499 this.resizeVideo();
500 }
501 }
502
503 private resizeCameraUpload(): void {
504 const width: number = this.originElement.clientWidth;
505 const height: number = this.originElement.clientHeight;
506
507 this.cameraUploadLabelElement.style.transform = `scale(${Math.min(1, width / 500, height / 300)})`;
508 this.cameraUploadProgressElement.style.transform = `scale(${Math.min(1, width / 500, height / 300)})`;
509 }
510
511 private resizeVideo(): void {
512 if (this.videoElement.videoWidth <= 2 || this.videoElement.videoHeight <= 2) {
513 return;
514 }
515
516 this.parentElement.style.maxWidth = null;
517 this.parentElement.style.maxHeight = null;
518
519 let width: number = this.originElement.clientWidth;
520 let height: number = this.originElement.clientHeight;
521
522 const videoRatio: number = this.videoElement.videoWidth / this.videoElement.videoHeight;
523
524 if (this.videoFit === BarcodePicker.ObjectFit.COVER) {
525 let widthPercentage: number = 1;
526 let heightPercentage: number = 1;
527
528 if (videoRatio < width / height) {
529 heightPercentage = Math.min(1, height / (width / videoRatio));
530 } else {
531 widthPercentage = Math.min(1, width / (height * videoRatio));
532 }
533
534 this.scanner.applyScanSettings(
535 this.scanner.getScanSettings().setBaseSearchArea({
536 x: (1 - widthPercentage) / 2,
537 y: (1 - heightPercentage) / 2,
538 width: widthPercentage,
539 height: heightPercentage
540 })
541 );
542
543 return;
544 }
545
546 if (videoRatio > width / height) {
547 height = width / videoRatio;
548 } else {
549 width = height * videoRatio;
550 }
551
552 this.parentElement.style.maxWidth = `${Math.ceil(width)}px`;
553 this.parentElement.style.maxHeight = `${Math.ceil(height)}px`;
554
555 window.objectFitPolyfill(this.videoElement);
556 }
557
558 private checkAndRecoverPlayback(): void {
559 if (
560 this.cameraManager != null &&
561 this.cameraManager.activeCamera != null &&
562 this.videoElement != null &&
563 this.videoElement.srcObject != null
564 ) {
565 if (!(<MediaStream>this.videoElement.srcObject).active) {
566 this.cameraManager.reinitializeCamera();
567 } else {
568 this.playVideo();
569 }
570 }
571 }
572
573 private updateCameraUploadProgress(progressPercentageValue: string): void {
574 this.cameraUploadProgressElement.setAttribute("data-progress", progressPercentageValue);
575 }
576
577 private async cameraUploadImageLoad(image: HTMLImageElement): Promise<void> {
578 this.updateCameraUploadProgress("100");
579
580 let resizedImageWidth: number;
581 let resizedImageHeight: number;
582 const resizedImageSizeLimit: number = 1440;
583 if (image.naturalWidth <= resizedImageSizeLimit && image.naturalHeight <= resizedImageSizeLimit) {
584 resizedImageWidth = image.naturalWidth;
585 resizedImageHeight = image.naturalHeight;
586 } else {
587 if (image.naturalWidth > image.naturalHeight) {
588 resizedImageWidth = resizedImageSizeLimit;
589 resizedImageHeight = Math.round((image.naturalHeight / image.naturalWidth) * resizedImageSizeLimit);
590 } else {
591 resizedImageWidth = Math.round((image.naturalWidth / image.naturalHeight) * resizedImageSizeLimit);
592 resizedImageHeight = resizedImageSizeLimit;
593 }
594 }
595
596 await this.cameraUploadFileProcess(image, resizedImageWidth, resizedImageHeight);
597 }
598
599 private async cameraUploadFileProcess(image: HTMLImageElement, width: number, height: number): Promise<void> {
600 this.videoImageCanvasContext.canvas.width = width;
601 this.videoImageCanvasContext.canvas.height = height;
602
603 this.videoImageCanvasContext.drawImage(image, 0, 0, width, height);
604 this.scanner.applyImageSettings({
605 width,
606 height,
607 format: ImageSettings.Format.RGBA_8U
608 });
609
610 this.setCameraUploadGuiAvailable(false);
611
612 await this.cameraUploadCallback();
613 this.setCameraUploadGuiAvailable(true);
614 }
615
616 private cameraUploadFile(): void {
617 const files: FileList | null = this.cameraUploadInputElement.files;
618 if (files != null && files.length !== 0) {
619 const image: HTMLImageElement = new Image();
620 const fileReader: FileReader = new FileReader();
621 fileReader.onload = () => {
622 this.cameraUploadInputElement.value = "";
623 if (fileReader.result != null) {
624 image.onload = this.cameraUploadImageLoad.bind(this, image);
625 image.onprogress = event2 => {
626 if (event2.lengthComputable) {
627 const progress: number = Math.round((event2.loaded / event2.total) * 20) * 5;
628 if (progress <= 100) {
629 this.updateCameraUploadProgress(progress.toString());
630 }
631 }
632 };
633 image.src = <string>fileReader.result;
634 }
635 };
636 this.updateCameraUploadProgress("0");
637 fileReader.readAsDataURL(files[0]);
638 }
639 }
640
641 private setupCameraSwitcher(): void {
642 this.cameraSwitcherElement.src = switchCameraImage;
643 this.cameraSwitcherElement.className = BarcodePickerGui.cameraSwitcherElementClassName;
644 this.cameraSwitcherElement.classList.add(BarcodePickerGui.hiddenClassName);
645 this.parentElement.appendChild(this.cameraSwitcherElement);
646 ["touchstart", "mousedown"].forEach(eventName => {
647 this.cameraSwitcherElement.addEventListener(eventName, async event => {
648 if (this.cameraManager != null) {
649 const cameraManager: CameraManager = this.cameraManager;
650 event.preventDefault();
651 try {
652 const cameras: Camera[] = await CameraAccess.getCameras();
653 const newCameraIndex: number =
654 (cameras.findIndex(camera => {
655 return (
656 camera.deviceId ===
657 (cameraManager.activeCamera == null ? camera.deviceId : cameraManager.activeCamera.deviceId)
658 );
659 }) +
660 1) %
661 cameras.length;
662 cameraManager
663 .initializeCameraWithSettings(cameras[newCameraIndex], cameraManager.activeCameraSettings)
664 .catch(console.error);
665 } catch (error) {
666 console.error(error);
667 }
668 }
669 });
670 });
671 }
672
673 private setupTorchToggler(): void {
674 this.torchTogglerElement.src = toggleTorchImage;
675 this.torchTogglerElement.className = BarcodePickerGui.torchTogglerElementClassName;
676 this.torchTogglerElement.classList.add(BarcodePickerGui.hiddenClassName);
677 this.parentElement.appendChild(this.torchTogglerElement);
678 ["touchstart", "mousedown"].forEach(eventName => {
679 this.torchTogglerElement.addEventListener(eventName, event => {
680 if (this.cameraManager != null) {
681 event.preventDefault();
682 this.cameraManager.toggleTorch();
683 }
684 });
685 });
686 }
687
688 private showScanditLogo(hideLogo: boolean, licenseFeatures?: { hiddenScanditLogoAllowed?: boolean }): void {
689 if (
690 hideLogo &&
691 licenseFeatures != null &&
692 licenseFeatures.hiddenScanditLogoAllowed != null &&
693 licenseFeatures.hiddenScanditLogoAllowed
694 ) {
695 return;
696 }
697
698 const scanditLogoImageElement: HTMLImageElement = document.createElement("img");
699 scanditLogoImageElement.src = scanditLogoImage;
700 scanditLogoImageElement.className = BarcodePickerGui.scanditLogoImageElementClassName;
701 this.parentElement.appendChild(scanditLogoImageElement);
702 }
703
704 private handleNewScanSettings(): void {
705 if (this.customLaserArea == null) {
706 this.setLaserArea();
707 }
708 if (this.customViewfinderArea == null) {
709 this.setViewfinderArea();
710 }
711 }
712
713 private handleVideoResize(): void {
714 if (
715 this.videoImageCanvasContext.canvas.width === this.videoElement.videoWidth &&
716 this.videoImageCanvasContext.canvas.height === this.videoElement.videoHeight
717 ) {
718 return;
719 }
720
721 this.videoImageCanvasContext.canvas.width = this.videoElement.videoWidth;
722 this.videoImageCanvasContext.canvas.height = this.videoElement.videoHeight;
723
724 this.scanner.applyImageSettings({
725 width: this.videoElement.videoWidth,
726 height: this.videoElement.videoHeight,
727 format: ImageSettings.Format.RGBA_8U
728 });
729
730 this.resize();
731 }
732}