Разрешает ли HTML5 выгрузку папок или дерева папок путем перетаскивания? - PullRequest
67 голосов
/ 28 августа 2010

Я не видел ни одного примера, который делает это. Разве это не разрешено в спецификации API?

Я ищу простое перетаскиваемое решение для загрузки всего дерева папок с фотографиями.

Ответы [ 9 ]

66 голосов
/ 10 июля 2012

Теперь возможно благодаря 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/

13 голосов
/ 29 августа 2010

В это сообщение в список рассылки HTML 5 Ян Хиксон говорит:

HTML5 теперь должен загружать много файлов на один раз. Браузеры могут позволить пользователям выбрать несколько файлов одновременно, в том числе через несколько каталогов; это немного выходит за рамки спецификации.

(См. Также оригинальное предложение .) Поэтому можно предположить, что он считает, что загрузка папок с помощью перетаскивания также выходит за рамки. Очевидно, что браузер должен обслуживать отдельные файлы.

Загрузка папок также будет иметь некоторые другие трудности, как описано Lars Gunther :

Это [...] предложение должно иметь два проверяет (если это вообще возможно):

  1. Максимальный размер, чтобы кто-то не мог загрузить полный каталог из нескольких сто несжатых необработанных изображений ...

  2. Фильтрация, даже если атрибут accept пропущен. Метаданные Mac OS и миниатюры Windows и т. д. должны быть опущено. Все скрытые файлы и по умолчанию каталоги должны быть исключен.

12 голосов
/ 30 октября 2018

К сожалению, ни один из существующих ответов не является полностью правильным, потому что readEntries не обязательно вернет ALL (файл или каталог) записи для данного каталога. Это часть спецификации 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 также может быть более интуитивно понятным, но, как отмечается в документации: это нестандартная / экспериментальная функция, не соответствующая стандартам, и от нее нельзя ожидать, что она будет работать во всех браузерах.

Похожие:

  • johnozbay comments , что в Chrome readEntries вернет не более 100 записей для каталога (проверено как Chrome 64).
  • Xan достаточно хорошо объясняет правильное использование readEntries в этом ответе (хотя и без кода).
  • Ответ Пабло Баррии Уренды правильно вызывает readEntries в асинхронном режиме без BFS. Он также отмечает, что Firefox возвращает все записи в каталоге (в отличие от Chrome), но мы не можем полагаться на это, учитывая спецификацию.
9 голосов
/ 24 июля 2012

Теперь вы можете загружать каталоги с помощью перетаскивания и ввода.

<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

8 голосов
/ 05 июля 2017

Эта функция даст вам обещание для массива всех удаленных файлов, например <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);

npm пакет

https://www.npmjs.com/package/datatransfer-files-promise

пример использования: https://github.com/grabantot/datatransfer-files-promise/blob/master/index.html

8 голосов
/ 19 ноября 2016

Firefox теперь поддерживает загрузку папок по состоянию на 15 ноября 2016 г. в версии 50.0: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories

Вы можете перетаскивать папки в Firefox или просматривать и выбирать локальную папку для загрузки.Он также поддерживает папки, вложенные в подпапки.

Это означает, что теперь вы можете использовать Chrome, Firefox, Edge или Opera для загрузки папок.В настоящее время вы не можете использовать Safari или Internet Explorer.

2 голосов
/ 14 сентября 2018

Вот полный пример того, как использовать 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

2 голосов
/ 12 июня 2012

В спецификации HTML5 НЕ сказано, что при выборе папки для загрузки браузер должен загружать все содержащиеся в ней файлы рекурсивно.

На самом деле в Chrome / Chromium вы можете загружать папку, но когда вы это делаете, он просто загружает бессмысленный файл размером 4 КБ, который представляет каталог.Некоторые серверные приложения, такие как Alfresco , могут обнаружить это и предупредить пользователя о невозможности загрузки папок:

The following cannot be uploaded because they are either folders or are zero bytes in size: undefined

1 голос
/ 03 февраля 2016

Разрешает ли HTML5 выгрузку папок или дерева папок путем перетаскивания?

Только Chrome поддерживает эту функцию.У него не было тяги, и он, вероятно, будет удален.

Ссылка: https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries

...