Nie widziałem żadnych przykładów, które to robią. Czy to nie jest dozwolone w specyfikacji interfejsu API?
Szukam prostego rozwiązania typu „przeciągnij i upuść” do przesłania całego drzewa folderów ze zdjęciami.
html
file-upload
drag-and-drop
Michael
źródło
źródło
input type=file
: stackoverflow.com/questions/9518335/…Odpowiedzi:
Teraz jest to możliwe dzięki 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);
Więcej informacji: https://protonet.info/blog/html5-experiment-drag-drop-of-folders/
źródło
readEntries
że nie zwróci wszystkich wpisów w katalogu. Na podstawie podanego przez Ciebie linku do błędu napisałem pełną odpowiedź: stackoverflow.com/a/53058574/885922Niestety żadna z istniejących odpowiedzi nie jest całkowicie poprawna, ponieważ
readEntries
niekoniecznie zwróci WSZYSTKIE wpisy (plik lub katalog) dla danego katalogu. Jest to część specyfikacji API (patrz sekcja Dokumentacja poniżej).Aby faktycznie pobrać wszystkie pliki, będziemy musieli wywoływać
readEntries
wielokrotnie (dla każdego napotkanego katalogu), dopóki nie zwróci pustej tablicy. Jeśli tego nie zrobimy, pominiemy niektóre pliki / podkatalogi w katalogu, np. W przeglądarce Chrome, zwrócimyreadEntries
maksymalnie 100 wpisów na raz.Używanie Promises (
await
/async
) do wyraźniejszego zademonstrowania prawidłowego użyciareadEntries
(ponieważ jest asynchroniczne) i przeszukiwania wszerz (BFS) do przechodzenia po strukturze katalogów:// 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); } }
Kompletny przykład roboczy na Codepen: https://codepen.io/anon/pen/gBJrOP
FWIW Podniosłem to tylko dlatego, że nie odzyskałem wszystkich plików, których oczekiwałem w katalogu zawierającym 40 000 plików (wiele katalogów zawierających znacznie ponad 100 plików / podkatalogów), używając zaakceptowanej odpowiedzi.
Dokumentacja:
To zachowanie jest udokumentowane w FileSystemDirectoryReader . Fragment z dodanym naciskiem:
Ale żeby być uczciwym, dokumentacja MDN mogłaby to wyjaśnić w innych sekcjach. Dokumentacja readEntries () po prostu zauważa:
Jedyna wzmianka / wskazówka, że potrzeba wielu wywołań, znajduje się w opisie parametru successCallback :
Zapewne API mogłoby być również bardziej intuicyjne, ale jak zauważa dokumentacja: jest to funkcja niestandardowa / eksperymentalna, nie na ścieżce standardów i nie można oczekiwać, że będzie działać we wszystkich przeglądarkach.
Związane z:
readEntries
zwróci maksymalnie 100 wpisów dla katalogu (zweryfikowanego jako Chrome 64).readEntries
dość dobrze wyjaśnia poprawne użycie w tej odpowiedzi (aczkolwiek bez kodu).readEntries
w sposób asynchroniczny bez BFS. Zauważa również, że Firefox zwraca wszystkie wpisy w katalogu (w przeciwieństwie do Chrome), ale nie możemy na tym polegać, biorąc pod uwagę specyfikację.źródło
FileSystemFileEntry
naFile
, za pomocąfile(successCb, failureCb)
metody. Jeśli potrzebujesz również pełnej ścieżki, powinieneś ją pobrać zfileEntry.fullPath
(file.webkitRelativePath
będzie tylko nazwa).Ta funkcja daje obietnicę dotyczącą tablicy wszystkich upuszczonych plików, na przykład
<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) }) }) }
Stosowanie:
dropArea.addEventListener("drop", function(event) { event.preventDefault(); var items = event.dataTransfer.items; getFilesFromWebkitDataTransferItems(items) .then(files => { ... }) }, false);
pakiet npm
https://www.npmjs.com/package/datatransfer-files-promise
przykład użycia: https://github.com/grabantot/datatransfer-files-promise/blob/master/index.html
źródło
function getFilesWebkitDataTransferItems(dataTransfer)
powinno byćfunction getFilesWebkitDataTransferItems(items)
ifor (entr of entries)
powinno byćfor (let entr of entries)
.readEntries
wielokrotnego wywoływania, dopóki nie zwróci pustej tablicy.W tej wiadomości do listy mailingowej HTML 5 Ian Hickson mówi:
(Zobacz także oryginalną propozycję funkcji ). Można więc bezpiecznie założyć, że rozważa przesyłanie folderów za pomocą metody przeciągania i upuszczania również poza zakresem. Najwyraźniej to przeglądarka musi obsługiwać poszczególne pliki.
Przesyłanie folderów wiązałoby się również z innymi trudnościami, które opisał Lars Gunther :
źródło
Teraz możesz przesyłać katalogi za pomocą przeciągania i upuszczania oraz wprowadzania.
<input type='file' webkitdirectory >
oraz do przeciągania i upuszczania (dla przeglądarek webkit).
Obsługa folderów typu „przeciągnij i upuść”.
<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>
Zasoby:
http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available
źródło
Firefox obsługuje teraz przesyłanie folderów od 15 listopada 2016 r. W wersji 50.0: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories
Możesz przeciągać i upuszczać foldery do przeglądarki Firefox lub przeglądać i wybierać folder lokalny do przesłania. Obsługuje również foldery zagnieżdżone w podfolderach.
Oznacza to, że możesz teraz przesyłać foldery za pomocą przeglądarki Chrome, Firefox, Edge lub Opera. Obecnie nie możesz używać Safari ani Internet Explorera.
źródło
Oto pełny przykład korzystania z interfejsu API plików i katalogów :
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
jest obsługiwany przez Chrome 13+, Firefox 50+ i Edge.Źródło: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
źródło
Tylko Chrome obsługuje tę funkcję. Nie miał żadnej przyczepności i prawdopodobnie zostanie usunięty.
Ref: https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries
źródło
readEntries
nie może zostać wywołane, jeślireadEntries
nadal trwa inne wywołanie . Projekt interfejsu DirectoryReader API nie jest najlepszyAKTUALIZACJA: Od 2012 roku wiele się zmieniło, zamiast tego zobacz odpowiedzi powyżej. Zostawiam tę odpowiedź tutaj ze względu na archeologię.
Specyfikacja HTML5 NIE mówi, że wybierając folder do przesłania, przeglądarka powinna przesyłać wszystkie zawarte w nim pliki rekurencyjnie.
Właściwie w Chrome / Chromium możesz przesłać folder, ale kiedy to zrobisz, po prostu przesyła bezsensowny plik 4KB, który reprezentuje katalog. Niektóre aplikacje po stronie serwera, takie jak Alfresco, mogą to wykryć i ostrzec użytkownika, że foldery nie mogą zostać przesłane:
źródło
Niedawno natknąłem się na potrzebę zaimplementowania tego w dwóch moich projektach, więc stworzyłem kilka funkcji narzędziowych, które pomogą w tym.
Tworzy się strukturę danych reprezentującą wszystkie foldery, pliki i relacje między nimi, w ten sposób 👇
{ folders: [ { name: string, folders: Array, files: Array }, /* ... */ ], files: Array }
Podczas gdy drugi po prostu zwraca tablicę wszystkich plików (we wszystkich folderach i podfolderach).
Oto link do pakietu: https://www.npmjs.com/package/file-system-utils
źródło