/** * @fileoverview * File for file selection UI handling. * * @author mebjas * * The word "QR Code" is registered trademark of DENSO WAVE INCORPORATED * http://www.denso-wave.com/qrcode/faqpatent-e.html */ import {Html5QrcodeScannerStrings} from "../../strings"; import { BaseUiElementFactory, PublicUiElementIdAndClasses } from "./base"; /** * Interface for callback when a file is selected by user using the button. */ export type OnFileSelected = (file: File) => void; /** UI class for file selection handling. */ export class FileSelectionUi { private readonly fileBasedScanRegion: HTMLDivElement; private readonly fileScanInput: HTMLInputElement; private readonly fileSelectionButton: HTMLButtonElement; /** Creates object and renders. */ private constructor( parentElement: HTMLDivElement, showOnRender: boolean, onFileSelected: OnFileSelected) { this.fileBasedScanRegion = this.createFileBasedScanRegion(); this.fileBasedScanRegion.style.display = showOnRender ? "block" : "none"; parentElement.appendChild(this.fileBasedScanRegion); let fileScanLabel = document.createElement("label"); fileScanLabel.setAttribute("for", this.getFileScanInputId()); fileScanLabel.style.display = "inline-block"; this.fileBasedScanRegion.appendChild(fileScanLabel); this.fileSelectionButton = BaseUiElementFactory.createElement( "button", PublicUiElementIdAndClasses.FILE_SELECTION_BUTTON_ID); this.setInitialValueToButton(); // Bind click events with the label element. this.fileSelectionButton.addEventListener("click", (_) => { fileScanLabel.click(); }); fileScanLabel.append(this.fileSelectionButton); this.fileScanInput = BaseUiElementFactory.createElement( "input", this.getFileScanInputId()); this.fileScanInput.type = "file"; this.fileScanInput.accept = "image/*"; this.fileScanInput.style.display = "none"; fileScanLabel.appendChild(this.fileScanInput); let $this = this; /*eslint complexity: ["error", 5]*/ this.fileScanInput.addEventListener("change", (e: Event) => { if (e == null || e.target == null) { return; } let target: HTMLInputElement = e.target as HTMLInputElement; if (target.files && target.files.length === 0) { return; } let fileList: FileList = target.files!; const file: File = fileList[0]; let fileName = file.name; $this.setImageNameToButton(fileName); onFileSelected(file); }); // Render drag and drop label let dragAndDropMessage = this.createDragAndDropMessage(); this.fileBasedScanRegion.appendChild(dragAndDropMessage); this.fileBasedScanRegion.addEventListener("dragenter", function(event) { $this.fileBasedScanRegion.style.border = $this.fileBasedScanRegionActiveBorder(); event.stopPropagation(); event.preventDefault(); }); this.fileBasedScanRegion.addEventListener("dragleave", function(event) { $this.fileBasedScanRegion.style.border = $this.fileBasedScanRegionDefaultBorder(); event.stopPropagation(); event.preventDefault(); }); this.fileBasedScanRegion.addEventListener("dragover", function(event) { $this.fileBasedScanRegion.style.border = $this.fileBasedScanRegionActiveBorder(); event.stopPropagation(); event.preventDefault(); }); /*eslint complexity: ["error", 10]*/ this.fileBasedScanRegion.addEventListener("drop", function(event) { event.stopPropagation(); event.preventDefault(); $this.fileBasedScanRegion.style.border = $this.fileBasedScanRegionDefaultBorder(); var dataTransfer = event.dataTransfer; if (dataTransfer) { let files = dataTransfer.files; if (!files || files.length === 0) { return; } let isAnyFileImage = false; for (let i = 0; i < files.length; ++i) { let file = files.item(i); if (!file) { continue; } let imageType = /image.*/; // Only process images. if (!file.type.match(imageType)) { continue; } isAnyFileImage = true; let fileName = file.name; $this.setImageNameToButton(fileName); onFileSelected(file); dragAndDropMessage.innerText = Html5QrcodeScannerStrings.dragAndDropMessage(); break; } // None of the files were images. if (!isAnyFileImage) { dragAndDropMessage.innerText = Html5QrcodeScannerStrings .dragAndDropMessageOnlyImages(); } } }); } //#region Public APIs. /** Hide the file selection UI. */ public hide() { this.fileBasedScanRegion.style.display = "none"; this.fileScanInput.disabled = true; } /** Show the file selection UI. */ public show() { this.fileBasedScanRegion.style.display = "block"; this.fileScanInput.disabled = false; } /** Returns {@code true} if UI container is displayed. */ public isShowing(): boolean { return this.fileBasedScanRegion.style.display === "block"; } /** Reset the file selection value */ public resetValue() { this.fileScanInput.value = ""; this.setInitialValueToButton(); } //#endregion //#region private APIs private createFileBasedScanRegion(): HTMLDivElement { let fileBasedScanRegion = document.createElement("div"); fileBasedScanRegion.style.textAlign = "center"; fileBasedScanRegion.style.margin = "auto"; fileBasedScanRegion.style.width = "80%"; fileBasedScanRegion.style.maxWidth = "600px"; fileBasedScanRegion.style.border = this.fileBasedScanRegionDefaultBorder(); fileBasedScanRegion.style.padding = "10px"; fileBasedScanRegion.style.marginBottom = "10px"; return fileBasedScanRegion; } private fileBasedScanRegionDefaultBorder() { return "6px dashed #ebebeb"; } /** Border when a file is being dragged over the file scan region. */ private fileBasedScanRegionActiveBorder() { return "6px dashed rgb(153 151 151)"; } private createDragAndDropMessage(): HTMLDivElement { let dragAndDropMessage = document.createElement("div"); dragAndDropMessage.innerText = Html5QrcodeScannerStrings.dragAndDropMessage(); dragAndDropMessage.style.fontWeight = "400"; return dragAndDropMessage; } private setImageNameToButton(imageFileName: string) { const MAX_CHARS = 20; if (imageFileName.length > MAX_CHARS) { // Strip first 8 // Strip last 8 // Add 4 dots let start8Chars = imageFileName.substring(0, 8); let length = imageFileName.length; let last8Chars = imageFileName.substring(length - 8, length); imageFileName = `${start8Chars}....${last8Chars}`; } let newText = Html5QrcodeScannerStrings.fileSelectionChooseAnother() + " - " + imageFileName; this.fileSelectionButton.innerText = newText; } private setInitialValueToButton() { let initialText = Html5QrcodeScannerStrings.fileSelectionChooseImage() + " - " + Html5QrcodeScannerStrings.fileSelectionNoImageSelected(); this.fileSelectionButton.innerText = initialText; } private getFileScanInputId(): string { return "html5-qrcode-private-filescan-input"; } //#endregion /** * Creates a file selection UI and renders. * * @param parentElement parent div element to render the UI to. * @param showOnRender if {@code true}, the UI will be shown upon render * else hidden. * @param onFileSelected callback to be called when file selection changes. * * @returns Instance of {@code FileSelectionUi}. */ public static create( parentElement: HTMLDivElement, showOnRender: boolean, onFileSelected: OnFileSelected): FileSelectionUi { let button = new FileSelectionUi( parentElement, showOnRender, onFileSelected); return button; } }