UNPKG

threepipe

Version:

A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.

219 lines 7.94 kB
/** * Fork of: https://github.com/donmccurdy/simple-dropzone updated: Mar 2021 * The MIT License (MIT) * Copyright (c) 2018 Don McCurdy * * Changes: * Convert to typescript. * webkitRelativePath for file input select. * Removed unzip and dependency(done in importer). * * Watches an element for file drops, parses to create a filemap hierarchy, * and emits the result. */ export class Dropzone { get inputEl() { return this._inputEl; } get el() { return this._el; } constructor(el, inputEl, listeners) { this._el = el; this._inputEl = inputEl; this._listeners = { drop: [], dropstart: [], droperror: [], }; this._onDragover = this._onDragover.bind(this); this._onDrop = this._onDrop.bind(this); this._onSelect = this._onSelect.bind(this); el?.addEventListener('dragover', this._onDragover, false); el?.addEventListener('drop', this._onDrop, false); inputEl?.addEventListener('change', this._onSelect); listeners && Object.entries(listeners).forEach(([e, c]) => c && this.on(e, c)); } on(type, callback) { this._listeners[type].push(callback); return this; } _emit(type, data) { this._listeners[type] .forEach((callback) => callback(data)); return this; } /** * Destroys the instance. */ destroy() { const el = this._el; const inputEl = this._inputEl; el?.removeEventListener('dragover', this._onDragover); el?.removeEventListener('drop', this._onDrop); inputEl?.removeEventListener('change', this._onSelect); } /** * References (and horror): * - https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/items * - https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/files * - https://code.flickr.net/2012/12/10/drag-n-drop/ * - https://stackoverflow.com/q/44842247/1314762 * */ _onDrop(e) { e.stopPropagation(); e.preventDefault(); this._emit('dropstart'); const files = Array.from(e.dataTransfer?.files || []); const items = Array.from(e.dataTransfer?.items || []); if (files.length === 0 && items.length === 0) { this._fail('Required drag-and-drop APIs are not supported in this browser.'); return; } // Prefer .items, which allow folder traversal if necessary. if (Dropzone.USE_DATA_TRANSFER_ITEMS && items.length > 0) { const entries = items.map((item) => item.webkitGetAsEntry()); // if (entries[0].name.match(/\.zip$/)) { // this._loadZip(items[0].getAsFile()) // } else { this._loadNextEntry(new Map(), entries, e); // } return; } // Fall back to .files, since folders can't be traversed. // if (files.length === 1 && files[0].name.match(/\.zip$/)) { // this._loadZip(files[0]) // } this._emit('drop', { nativeEvent: e, files: new Map(files.map((file) => { file.filePath = file.name; return [file.filePath, file]; })), }); } /** * @param {Event} e */ _onDragover(e) { e.stopPropagation(); e.preventDefault(); e.dataTransfer && (e.dataTransfer.dropEffect = 'copy'); // Explicitly show this is a copy. } _onSelect(e) { if (!this._inputEl) { console.warn('Invalid Dropzone event ', e); return; } this._emit('dropstart'); // HTML file inputs do not seem to support folders, so assume this is a flat file list. const files = [].slice.call(this._inputEl.files ?? new FileList()); // Automatically decompress a zip archive if it is the only file given. // if (files.length === 1 && this._isZip(files[0])) { // this._loadZip(files[0]) // return // } const fileMap = new Map(); files.forEach((file) => { file.filePath = file.webkitRelativePath || file.name; fileMap.set(file.filePath, file); }); this._emit('drop', { files: fileMap, nativeEvent: e }); } /** * Iterates through a list of FileSystemEntry objects, creates the fileMap * tree, and emits the result. * @param fileMap * @param {Array<FileSystemEntry>} entries * @param e */ _loadNextEntry(fileMap, entries, e) { const entry = entries.pop(); if (!entry) { this._emit('drop', { files: fileMap, nativeEvent: e }); return; } if (entry.isFile) { entry.file((file) => { file.filePath = entry.fullPath; fileMap.set(entry.fullPath, file); this._loadNextEntry(fileMap, entries, e); }, (err) => console.error('Could not load file: %s', entry.fullPath, err, 'Try setting Dropzone.USE_DATA_TRANSFER_ITEMS to false.')); } else if (entry.isDirectory) { // readEntries() must be called repeatedly until it stops returning results. // https://www.w3.org/TR/2012/WD-file-system-api-20120417/#the-directoryreader-interface // https://bugs.chromium.org/p/chromium/issues/detail?id=378883 const reader = entry.createReader(); const readerCallback = (newEntries) => { if (newEntries.length) { entries = entries.concat(newEntries); // eslint-disable-next-line @typescript-eslint/no-unsafe-call reader.readEntries(readerCallback); } else { this._loadNextEntry(fileMap, entries, e); } }; // eslint-disable-next-line @typescript-eslint/no-unsafe-call reader.readEntries(readerCallback); } else { console.warn('Unknown asset type: ' + entry.fullPath); this._loadNextEntry(fileMap, entries, e); } } // /** // * Inflates a File in .ZIP format, creates the fileMap tree, and emits the // * result. // * @param {File} file // */ // _loadZip(file) { // const pending = [] // const fileMap = new Map() // const archive = new fs.FS() // // const traverse = (node) => { // if (node.directory) { // node.children.forEach(traverse) // } else if (node.name[0] !== '.') { // pending.push(new Promise((resolve) => { // node.getData(new zip.BlobWriter(), (blob) => { // blob.name = node.name // fileMap.set(node.getFullname(), blob) // resolve() // }) // })) // } // } // // archive.importBlob(file, () => { // traverse(archive.root) // Promise.all(pending).then(() => { // this._emit('drop', {files: fileMap, archive: file}) // }) // }) // } // /** // * @param {File} file // * @return {Boolean} // */ // _isZip(file) { // return file.type === 'application/zip' || file.name.match(/\.zip$/) // } /** * @throws */ _fail(message) { this._emit('droperror', { message: message }); } } /** * Use dataTransfer.items when available instead of dataTransfer.files (when files are dropped) * * Set to false to use dataTransfer.files only. * This is useful for environments where files cannot be read from FileSystemEntry like in figma plugins/widgets. */ Dropzone.USE_DATA_TRANSFER_ITEMS = true; //# sourceMappingURL=Dropzone.js.map