@umbraco-ui/uui-file-dropzone
Version:
Umbraco UI file-dropzone component.
293 lines (289 loc) • 8.72 kB
JavaScript
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 };