Я не бачив жодного прикладу, що це робить. Це не дозволено в специфікації API?
Я шукаю просте рішення перетягування для завантаження цілого дерева папок фотографій.
Я не бачив жодного прикладу, що це робить. Це не дозволено в специфікації API?
Я шукаю просте рішення перетягування для завантаження цілого дерева папок фотографій.
Відповіді:
Тепер це можливо завдяки 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);
Більше інформації: https://protonet.info/blog/html5-experiment-drag-drop-of-folders/
readEntries
, що не буде повернуто всі записи в каталозі. Грунтуючись на засланні помилки вашого надані, я написав до повної відповіді: stackoverflow.com/a/53058574/885922
На жаль, жодна з існуючих відповідей не є абсолютно правильною, оскільки readEntries
не обов’язково повертає ВСІ записи (файл чи каталог) для даного каталогу. Це частина специфікації API (див. Розділ Документація нижче).
Щоб насправді отримати всі файли, нам потрібно буде телефонувати readEntries
неодноразово (для кожного каталогу, з яким ми стикаємось), поки він не поверне порожній масив. Якщо цього не сталося, ми пропустимо деякі файли / підкаталоги в каталозі, наприклад, у Chrome, readEntries
одночасно повернемо не більше 100 записів.
Використання Promises ( await
/ async
), щоб чіткіше продемонструвати правильне використання readEntries
(оскільки це асинхронно) та пошук по ширині (BFS) для обходу структури каталогів:
// 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);
}
}
Повний робочий приклад на Codepen: https://codepen.io/anon/pen/gBJrOP
FWIW Я взяв це лише тому, що не отримав назад усіх файлів, яких я очікував, у каталог, що містить 40 000 файлів (багато каталогів, що містять значно більше 100 файлів / підкаталогів), коли використовував прийняту відповідь.
Документація:
Ця поведінка задокументована у FileSystemDirectoryReader . Витяг із доданим наголосом:
readEntries ()
Повертає масив, що містить деяку кількість записів каталогу . Кожен елемент масиву є об'єктом на основі FileSystemEntry - зазвичай це або FileSystemFileEntry, або FileSystemDirectoryEntry.
Але справедливості заради слід сказати, що документація MDN може пояснити це в інших розділах. Документація readEntries () просто зазначає:
Метод readEntries () отримує записи каталогу у каталозі, що читається, і доставляє їх у масиві до наданої функції зворотного виклику
І єдина згадка / підказка про необхідність декількох викликів - в описі параметра successCallback :
Якщо файлів не залишилося, або ви вже викликали readEntries () на цьому FileSystemDirectoryReader, масив порожній.
Можливо, API також може бути більш інтуїтивно зрозумілим, але, як зазначається в документації: це нестандартна / експериментальна функція, не на стандартній доріжці, і не можна очікувати, що вона буде працювати для всіх браузерів.
Пов’язані:
readEntries
поверне не більше 100 записів для каталогу (перевірено як Chrome 64).readEntries
У цій відповіді Ксан досить добре пояснює правильне вживання (хоча і без коду).readEntries
асинхронно без BFS. Він також зазначає, що Firefox повертає всі записи в каталог (на відміну від Chrome), але ми не можемо на це покладатися, враховуючи специфікацію.FileSystemFileEntry
на File
, за допомогою file(successCb, failureCb)
методу. Якщо вам також потрібен повний шлях, ви повинні взяти це з fileEntry.fullPath
( file.webkitRelativePath
буде лише ім'я).
Ця функція дасть вам обіцянку для масиву всіх видалених файлів, таких як <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
приклад використання: https://github.com/grabantot/datatransfer-files-promise/blob/master/index.html
function getFilesWebkitDataTransferItems(dataTransfer)
повинно бути function getFilesWebkitDataTransferItems(items)
, і for (entr of entries)
повинно бути for (let entr of entries)
.
readEntries
повторного дзвінка, поки не поверне порожній масив.
У цьому повідомленні до списку розсилки HTML 5 Ян Хіксон говорить:
HTML5 тепер повинен завантажувати багато файлів одночасно. Браузери можуть дозволити користувачам вибирати декілька файлів одночасно, включаючи різні каталоги; це трохи вийшло за межі специфікації.
(Також див. Оригінальну пропозицію функції .) Тож можна припустити, що він вважає, що завантаження папок за допомогою функції перетягування також виходить за рамки. Очевидно, що браузер повинен обслуговувати окремі файли.
Завантаження папок також матиме деякі інші труднощі, як описано Ларсом Гюнтером :
Ця […] пропозиція повинна мати дві перевірки (якщо це взагалі можливо):
Максимальний розмір, щоб хтось не міг завантажувати повний каталог із декількох сотень нестиснених вихідних зображень ...
Фільтрування, навіть якщо атрибут accept опущений. Метадані Mac OS, ескізи Windows тощо слід опустити. Усі приховані файли та каталоги за замовчуванням повинні бути виключені.
Тепер ви можете завантажувати каталоги як перетягуванням, так і введенням.
<input type='file' webkitdirectory >
і для перетягування (для браузерів webkit).
Обробка папок перетягування.
<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>
Ресурси:
http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available
Від 15 листопада 2016 року Firefox підтримує завантаження папок у версії 50: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories
Ви можете перетягувати папки у Firefox або переглядати та вибирати локальну папку для завантаження. Він також підтримує папки, вкладені в підпапки.
Це означає, що тепер ви можете використовувати Chrome, Firefox, Edge або Opera для завантаження папок. Зараз ви не можете користуватися Safari чи Internet Explorer.
Ось повний приклад того, як використовувати API файлів і каталогів API :
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
підтримується Chrome 13+, Firefox 50+ та Edge.
Джерело: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
Чи дозволяє HTML5 перетягування папки або дерево папок?
Цю функцію підтримує лише Chrome. Він не мав жодної тяги і, ймовірно, буде знятий.
Посилання: https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries
readEntries
не можна викликати, якщо ще один виклик readEntries
все ще виконується. Дизайн API DirectoryReader не найкращий
ОНОВЛЕННЯ: З 2012 року багато чого змінилося, натомість див. Відповіді вище. Цю відповідь я залишаю тут заради археології.
Специфікація HTML5 НЕ говорить, що при виборі папки для завантаження браузер повинен рекурсивно завантажувати всі файли, що містяться.
Насправді в Chrome / Chromium ви можете завантажити папку, але коли ви це робите, вона просто завантажує безглуздий файл розміром 4 КБ, який представляє каталог. Деякі додатки на стороні сервера, такі як Alfresco, можуть це виявити та попереджати користувача про те, що папки завантажувати не можна:
Нещодавно натрапив на необхідність реалізувати це у двох своїх проектах, тому я створив купу допоміжних функцій, які допоможуть у цьому.
Створюється структура даних, що представляє всі папки, файли та взаємозв'язок між ними, наприклад 👇
{
folders: [
{
name: string,
folders: Array,
files: Array
},
/* ... */
],
files: Array
}
У той час як інший просто повертає масив усіх файлів (у всіх папках та підпапках).
Ось посилання на пакет: https://www.npmjs.com/package/file-system-utils
input type=file
: stackoverflow.com/questions/9518335 / ...