No he visto ningún ejemplo que haga esto. ¿No está permitido en las especificaciones de la API?
Estoy buscando una solución fácil de arrastrar y soltar para cargar un árbol de carpetas completo de fotos.
No he visto ningún ejemplo que haga esto. ¿No está permitido en las especificaciones de la API?
Estoy buscando una solución fácil de arrastrar y soltar para cargar un árbol de carpetas completo de fotos.
Respuestas:
Ahora es posible gracias a Chrome> = 21.
function traverseFileTree(item, path) {
path = path || "";
if (item.isFile) {
// Get file
item.file(function(file) {
console.log("File:", path + file.name);
});
} else if (item.isDirectory) {
// Get folder contents
var dirReader = item.createReader();
dirReader.readEntries(function(entries) {
for (var i=0; i<entries.length; i++) {
traverseFileTree(entries[i], path + item.name + "/");
}
});
}
}
dropArea.addEventListener("drop", function(event) {
event.preventDefault();
var items = event.dataTransfer.items;
for (var i=0; i<items.length; i++) {
// webkitGetAsEntry is where the magic happens
var item = items[i].webkitGetAsEntry();
if (item) {
traverseFileTree(item);
}
}
}, false);
Más información: https://protonet.info/blog/html5-experiment-drag-drop-of-folders/
readEntries
que no devolverá todos los enteros en un directorio. Según el enlace de error que proporcionó, escribí una respuesta completa: stackoverflow.com/a/53058574/885922
Desafortunadamente, ninguna de las respuestas existentes es completamente correcta porque readEntries
no necesariamente devolverá TODAS las entradas (archivo o directorio) para un directorio determinado. Esto es parte de la especificación API (consulte la sección de documentación a continuación).
Para obtener todos los archivos, necesitaremos llamar readEntries
repetidamente (para cada directorio que encontremos) hasta que devuelva una matriz vacía. Si no lo hacemos, perderemos algunos archivos / subdirectorios en un directorio, por ejemplo, en Chrome, readEntries
solo devolverá un máximo de 100 entradas a la vez.
Usando Promises ( await
/ async
) para demostrar más claramente el uso correcto de readEntries
(ya que es asincrónico) y la búsqueda en amplitud (BFS) para recorrer la estructura del directorio:
// Drop handler function to get all files
async function getAllFileEntries(dataTransferItemList) {
let fileEntries = [];
// Use BFS to traverse entire directory/file structure
let queue = [];
// Unfortunately dataTransferItemList is not iterable i.e. no forEach
for (let i = 0; i < dataTransferItemList.length; i++) {
queue.push(dataTransferItemList[i].webkitGetAsEntry());
}
while (queue.length > 0) {
let entry = queue.shift();
if (entry.isFile) {
fileEntries.push(entry);
} else if (entry.isDirectory) {
queue.push(...await readAllDirectoryEntries(entry.createReader()));
}
}
return fileEntries;
}
// Get all the entries (files or sub-directories) in a directory
// by calling readEntries until it returns empty array
async function readAllDirectoryEntries(directoryReader) {
let entries = [];
let readEntries = await readEntriesPromise(directoryReader);
while (readEntries.length > 0) {
entries.push(...readEntries);
readEntries = await readEntriesPromise(directoryReader);
}
return entries;
}
// Wrap readEntries in a promise to make working with readEntries easier
// readEntries will return only some of the entries in a directory
// e.g. Chrome returns at most 100 entries at a time
async function readEntriesPromise(directoryReader) {
try {
return await new Promise((resolve, reject) => {
directoryReader.readEntries(resolve, reject);
});
} catch (err) {
console.log(err);
}
}
Ejemplo de trabajo completo en Codepen: https://codepen.io/anon/pen/gBJrOP
FWIW Solo recogí esto porque no estaba recuperando todos los archivos que esperaba en un directorio que contiene 40,000 archivos (muchos directorios que contienen más de 100 archivos / subdirectorios) cuando usé la respuesta aceptada.
Documentación:
Este comportamiento está documentado en FileSystemDirectoryReader . Extracto con énfasis agregado:
readEntries ()
Devuelve una matriz que contiene algunas entradas del directorio . Cada elemento de la matriz es un objeto basado en FileSystemEntry, generalmente FileSystemFileEntry o FileSystemDirectoryEntry.
Pero para ser justos, la documentación de MDN podría aclarar esto en otras secciones. La documentación readEntries () simplemente señala:
El método readEntries () recupera las entradas del directorio dentro del directorio que se está leyendo y las entrega en una matriz a la función de devolución de llamada proporcionada
Y la única mención / sugerencia de que se necesitan varias llamadas está en la descripción del parámetro successCallback :
Si no quedan archivos, o si ya ha llamado readEntries () en este FileSystemDirectoryReader, la matriz está vacía.
Podría decirse que la API también podría ser más intuitiva, pero como señala la documentación: es una característica no estándar / experimental, no está en una pista de estándares y no se puede esperar que funcione para todos los navegadores.
Relacionado:
readEntries
devolverá como máximo 100 entradas para un directorio (verificado como Chrome 64).readEntries
bastante bien en esta respuesta (aunque sin código).readEntries
de forma asincrónica sin BFS. También señala que Firefox devuelve todas las entradas en un directorio (a diferencia de Chrome), pero no podemos confiar en esto dada la especificación.FileSystemFileEntry
a File
, a través del file(successCb, failureCb)
método. Si también necesita la ruta completa, debe tomarla de fileEntry.fullPath
( file.webkitRelativePath
será solo el nombre).
Esta función le dará una promesa para una matriz de todos los archivos caídos, como <input type="file"/>.files
:
function getFilesWebkitDataTransferItems(dataTransferItems) {
function traverseFileTreePromise(item, path='') {
return new Promise( resolve => {
if (item.isFile) {
item.file(file => {
file.filepath = path + file.name //save full path
files.push(file)
resolve(file)
})
} else if (item.isDirectory) {
let dirReader = item.createReader()
dirReader.readEntries(entries => {
let entriesPromises = []
for (let entr of entries)
entriesPromises.push(traverseFileTreePromise(entr, path + item.name + "/"))
resolve(Promise.all(entriesPromises))
})
}
})
}
let files = []
return new Promise((resolve, reject) => {
let entriesPromises = []
for (let it of dataTransferItems)
entriesPromises.push(traverseFileTreePromise(it.webkitGetAsEntry()))
Promise.all(entriesPromises)
.then(entries => {
//console.log(entries)
resolve(files)
})
})
}
dropArea.addEventListener("drop", function(event) {
event.preventDefault();
var items = event.dataTransfer.items;
getFilesFromWebkitDataTransferItems(items)
.then(files => {
...
})
}, false);
https://www.npmjs.com/package/datatransfer-files-promise
ejemplo de uso: https://github.com/grabantot/datatransfer-files-promise/blob/master/index.html
function getFilesWebkitDataTransferItems(dataTransfer)
debería ser function getFilesWebkitDataTransferItems(items)
y for (entr of entries)
debería ser for (let entr of entries)
.
readEntries
repetidamente hasta que devuelve una matriz vacía.
En este mensaje a la lista de correo HTML 5, Ian Hickson dice:
HTML5 ahora tiene que cargar muchos archivos a la vez. Los navegadores podrían permitir a los usuarios seleccionar varios archivos a la vez, incluso en varios directorios; eso está un poco fuera del alcance de la especificación.
(Consulte también la propuesta de función original ). Por lo tanto, es seguro asumir que él considera que cargar carpetas usando arrastrar y soltar también está fuera de alcance. Aparentemente, depende del navegador entregar archivos individuales.
La carga de carpetas también tendría algunas otras dificultades, como lo describe Lars Gunther :
Esta propuesta […] debe tener dos comprobaciones (si es factible):
Tamaño máximo, para evitar que alguien cargue un directorio completo de varios cientos de imágenes en bruto sin comprimir ...
Filtrado incluso si se omite el atributo de aceptación. Se deben omitir los metadatos de Mac OS y las miniaturas de Windows, etc. Todos los archivos y directorios ocultos deben excluirse de forma predeterminada.
Ahora puede cargar directorios con arrastrar y soltar e ingresar.
<input type='file' webkitdirectory >
y para arrastrar y soltar (para navegadores webkit).
Manejo de carpetas de arrastrar y soltar.
<div id="dropzone"></div>
<script>
var dropzone = document.getElementById('dropzone');
dropzone.ondrop = function(e) {
var length = e.dataTransfer.items.length;
for (var i = 0; i < length; i++) {
var entry = e.dataTransfer.items[i].webkitGetAsEntry();
if (entry.isFile) {
... // do whatever you want
} else if (entry.isDirectory) {
... // do whatever you want
}
}
};
</script>
Recursos:
http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available
Firefox ahora admite la carga de carpetas, a partir del 15 de noviembre de 2016, en la versión 50.0: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories
Puede arrastrar y soltar carpetas en Firefox o puede buscar y seleccionar una carpeta local para cargar. También admite carpetas anidadas en subcarpetas.
Eso significa que ahora puede usar Chrome, Firefox, Edge u Opera para cargar carpetas. No puede utilizar Safari o Internet Explorer en este momento.
A continuación, se muestra un ejemplo completo de cómo utilizar la API de entradas de directorio y archivo :
var dropzone = document.getElementById("dropzone");
var listing = document.getElementById("listing");
function scanAndLogFiles(item, container) {
var elem = document.createElement("li");
elem.innerHTML = item.name;
container.appendChild(elem);
if (item.isDirectory) {
var directoryReader = item.createReader();
var directoryContainer = document.createElement("ul");
container.appendChild(directoryContainer);
directoryReader.readEntries(function(entries) {
entries.forEach(function(entry) {
scanAndLogFiles(entry, directoryContainer);
});
});
}
}
dropzone.addEventListener(
"dragover",
function(event) {
event.preventDefault();
},
false
);
dropzone.addEventListener(
"drop",
function(event) {
var items = event.dataTransfer.items;
event.preventDefault();
listing.innerHTML = "";
for (var i = 0; i < items.length; i++) {
var item = items[i].webkitGetAsEntry();
if (item) {
scanAndLogFiles(item, listing);
}
}
},
false
);
body {
font: 14px "Arial", sans-serif;
}
#dropzone {
text-align: center;
width: 300px;
height: 100px;
margin: 10px;
padding: 10px;
border: 4px dashed red;
border-radius: 10px;
}
#boxtitle {
display: table-cell;
vertical-align: middle;
text-align: center;
color: black;
font: bold 2em "Arial", sans-serif;
width: 300px;
height: 100px;
}
<p>Drag files and/or directories to the box below!</p>
<div id="dropzone">
<div id="boxtitle">
Drop Files Here
</div>
</div>
<h2>Directory tree:</h2>
<ul id="listing"></ul>
webkitGetAsEntry
es compatible con Chrome 13+, Firefox 50+ y Edge.
Fuente: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
¿HTML5 permite la carga mediante arrastrar y soltar de carpetas o un árbol de carpetas?
Solo Chrome admite esta función. No ha tenido tracción y es probable que se retire.
Ref: https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries
readEntries
que no se puede llamar si readEntries
aún se está ejecutando otra llamada de . El diseño de la API de DirectoryReader no es el mejor
ACTUALIZACIÓN: Desde 2012 ha cambiado mucho, consulte las respuestas anteriores. Dejo esta respuesta aquí por el bien de la arqueología.
La especificación HTML5 NO dice que al seleccionar una carpeta para cargar, el navegador debe cargar todos los archivos contenidos de forma recursiva.
En realidad, en Chrome / Chromium, puede cargar una carpeta, pero cuando lo hace, simplemente carga un archivo de 4KB sin sentido, que representa el directorio. Algunas aplicaciones del lado del servidor como Alfresco pueden detectar esto y advertir al usuario que las carpetas no se pueden cargar:
Recientemente me encontré con la necesidad de implementar esto en dos de mis proyectos, así que creé un montón de funciones de utilidad para ayudar con esto.
Uno crea una estructura de datos que representa todas las carpetas, archivos y la relación entre ellos, así 👇
{
folders: [
{
name: string,
folders: Array,
files: Array
},
/* ... */
],
files: Array
}
Mientras que el otro solo devuelve una matriz de todos los archivos (en todas las carpetas y subcarpetas).
Aquí está el enlace al paquete: https://www.npmjs.com/package/file-system-utils
input type=file
: stackoverflow.com/questions/9518335/…