UNPKG

@umbraco-ui/uui-file-dropzone

Version:

Umbraco UI file-dropzone component.

293 lines (289 loc) 8.72 kB
import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { LitElement, html, css } from 'lit'; import { query, property } from 'lit/decorators.js'; import { UUIEvent } from '@umbraco-ui/uui-base/lib/events'; import { LabelMixin } from '@umbraco-ui/uui-base/lib/mixins'; import '@umbraco-ui/uui-symbol-file-dropzone/lib'; class UUIFileDropzoneEvent extends UUIEvent { static { this.CHANGE = "change"; } constructor(evName, eventInit = {}) { super(evName, { ...{ bubbles: true }, ...eventInit }); } } var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; let UUIFileDropzoneElement = class extends LabelMixin("", LitElement) { constructor() { super(); this._acceptedFileExtensions = []; this._acceptedMimeTypes = []; this._accept = ""; this.disallowFolderUpload = false; this.multiple = false; this.addEventListener("dragenter", this._onDragEnter, false); this.addEventListener("dragleave", this._onDragLeave, false); this.addEventListener("dragover", this._onDragOver, false); this.addEventListener("drop", this._onDrop, false); } set accept(value) { if (value) { const mimetypes = []; const fileextensions = []; value.split(",").forEach((item) => { item = item.trim().toLowerCase(); if (/[a-z]+\/[a-z*]/s.test(item)) { mimetypes.push(item); } else { fileextensions.push(item.replace(/^\./, "")); } }); this._acceptedMimeTypes = mimetypes; this._acceptedFileExtensions = fileextensions; } else { this._acceptedMimeTypes = []; this._acceptedFileExtensions = []; } const old = this._accept; this._accept = value; this.requestUpdate("accept", old); } get accept() { return this._accept; } /** * Opens the native file picker to select a file. * @method browse */ browse() { this._input.click(); } async _getAllEntries(dataTransferItemList) { const queue = [...dataTransferItemList]; const folders = []; const files = []; for (const entry of queue) { if (entry?.kind !== "file") continue; const fileEntry = this._getEntry(entry); if (!fileEntry) continue; if (!fileEntry.isDirectory) { const file = entry.getAsFile(); if (!file) continue; if (this._isAccepted(file)) { files.push(file); } } else if (!this.disallowFolderUpload && this.multiple) { const structure = await this._mkdir(fileEntry); folders.push(structure); } } return { files, folders }; } /** * Get the directory entry from a DataTransferItem. * @remark Supports both WebKit and non-WebKit browsers. */ _getEntry(entry) { let dir = null; if ("webkitGetAsEntry" in entry) { dir = entry.webkitGetAsEntry(); } else if ("getAsEntry" in entry) { dir = entry.getAsEntry(); } return dir; } // Make directory structure async _mkdir(entry) { const reader = entry.createReader(); const folders = []; const files = []; const readEntries = (reader2) => { return new Promise((resolve, reject) => { reader2.readEntries(async (entries) => { if (!entries.length) { resolve(); return; } for (const en of entries) { if (en.isFile) { const file = await this._getAsFile(en); if (this._isAccepted(file)) { files.push(file); } } else if (en.isDirectory) { const directory = await this._mkdir( en ); folders.push(directory); } } await readEntries(reader2); resolve(); }, reject); }); }; await readEntries(reader); const result = { folderName: entry.name, folders, files }; return result; } _isAccepted(file) { if (this._acceptedFileExtensions.length === 0 && this._acceptedMimeTypes.length === 0) { return true; } const fileType = file.type.toLowerCase(); const fileExtension = file.name.split(".").pop(); if (fileExtension && this._acceptedFileExtensions.includes(fileExtension.toLowerCase())) { return true; } for (const mimeType of this._acceptedMimeTypes) { if (fileType === mimeType) { return true; } else if (mimeType.endsWith("/*") && fileType.startsWith(mimeType.replace("*", ""))) { return true; } } return false; } async _getAsFile(fileEntry) { return new Promise((resolve, reject) => fileEntry.file(resolve, reject)); } async _onDrop(e) { e.preventDefault(); this._dropzone.classList.remove("hover"); const items = e.dataTransfer?.items; if (items) { const fileSystemResult = await this._getAllEntries(items); if (this.multiple === false && fileSystemResult.files.length) { fileSystemResult.files = [fileSystemResult.files[0]]; fileSystemResult.folders = []; } if (!fileSystemResult.files.length && !fileSystemResult.folders.length) { return; } this.dispatchEvent( new UUIFileDropzoneEvent(UUIFileDropzoneEvent.CHANGE, { detail: fileSystemResult }) ); } } _onDragOver(e) { e.preventDefault(); } _onDragEnter(e) { this._dropzone.classList.add("hover"); e.preventDefault(); } _onDragLeave(e) { this._dropzone.classList.remove("hover"); e.preventDefault(); } _onFileInputChange() { const files = this._input.files ? Array.from(this._input.files) : []; if (this.multiple === false && files.length > 1) { files.splice(1, files.length - 1); } const allowedFiles = files.filter((file) => this._isAccepted(file)); if (!allowedFiles.length) { return; } this.dispatchEvent( new UUIFileDropzoneEvent(UUIFileDropzoneEvent.CHANGE, { detail: { files: allowedFiles, folders: [] } }) ); } render() { return html` <div id="dropzone"> <uui-symbol-file-dropzone id="symbol"></uui-symbol-file-dropzone> ${this.renderLabel()} <input @click=${(e) => e.stopImmediatePropagation()} id="input" type="file" accept=${this.accept} ?multiple=${this.multiple} @change=${this._onFileInputChange} aria-label="${this.label}" /> <slot></slot> </div> `; } }; UUIFileDropzoneElement.styles = [ css` #dropzone { display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; box-sizing: border-box; width: 100%; height: 100%; padding: var(--uui-size-4,12px); border: 3px solid transparent; margin: -3px; backdrop-filter: blur(2px); } #dropzone.hover { border-color: var(--uui-color-default,#1b264f); } #dropzone.hover::before { content: ''; position: absolute; inset: 0; opacity: 0.2; border-color: var(--uui-color-default,#1b264f); background-color: var(--uui-color-default,#1b264f); } #symbol { color: var(--uui-color-default,#1b264f); max-width: 100%; max-height: 100%; } #input { position: absolute; width: 0px; height: 0px; opacity: 0; display: none; } ` ]; __decorateClass([ query("#input") ], UUIFileDropzoneElement.prototype, "_input", 2); __decorateClass([ query("#dropzone") ], UUIFileDropzoneElement.prototype, "_dropzone", 2); __decorateClass([ property({ type: String }) ], UUIFileDropzoneElement.prototype, "accept", 1); __decorateClass([ property({ type: Boolean, reflect: true, attribute: "disallow-folder-upload" }) ], UUIFileDropzoneElement.prototype, "disallowFolderUpload", 2); __decorateClass([ property({ type: Boolean }) ], UUIFileDropzoneElement.prototype, "multiple", 2); UUIFileDropzoneElement = __decorateClass([ defineElement("uui-file-dropzone") ], UUIFileDropzoneElement); export { UUIFileDropzoneElement, UUIFileDropzoneEvent };