web-dev-qa-db-fra.com

HTML5 permet-il le téléchargement par glisser-déposer de dossiers ou d'une arborescence de dossiers?

Je n'ai vu aucun exemple qui le fasse. N'est-ce pas autorisé dans la spécification API?

Je recherche une solution simple de glisser-déposer pour télécharger une arborescence complète de dossiers de photos.

67
michael

C'est désormais possible grâce à 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);

Plus d'informations: https://protonet.info/blog/html5-experiment-drag-drop-of-folders/

66
Christopher Blum

Malheureusement, aucune des réponses existantes n'est complètement correcte car readEntries ne renverra pas nécessairement [~ # ~] tout [~ # ~] le (fichier ou répertoire) entrées pour un répertoire donné. Cela fait partie de la spécification API (voir la section Documentation ci-dessous).

Pour réellement récupérer tous les fichiers, nous devons appeler readEntries à plusieurs reprises (pour chaque répertoire que nous rencontrons) jusqu'à ce qu'il renvoie un espace vide tableau. Si nous ne le faisons pas, nous manquerons certains fichiers/sous-répertoires dans un répertoire, par exemple dans Chrome, readEntries ne renverra que 100 entrées à la fois.

Utiliser Promises (await/async) pour démontrer plus clairement l'utilisation correcte de readEntries (car il est asynchrone) et BFS pour parcourir la structure du répertoire:

// 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);
  }
}

Exemple de travail complet sur Codepen: https://codepen.io/anon/pen/gBJrOP

FWIW Je n'ai récupéré cela que parce que je ne récupérais pas tous les fichiers que j'attendais dans un répertoire contenant 40 000 fichiers (de nombreux répertoires contenant bien plus de 100 fichiers/sous-répertoires) lorsque j'utilisais la réponse acceptée.

Documentation:

Ce comportement est documenté dans FileSystemDirectoryReader . Extrait avec emphase ajoutée:

readEntries ()
Renvoie un tableau contenant un certain nombre de entrées du répertoire. Chaque élément du tableau est un objet basé sur FileSystemEntry, généralement FileSystemFileEntry ou FileSystemDirectoryEntry.

Mais pour être honnête, la documentation MDN pourrait rendre cela plus clair dans d'autres sections. La documentation readEntries () note simplement:

La méthode readEntries () récupère les entrées de répertoire dans le répertoire en cours de lecture et les remet dans un tableau à la fonction de rappel fournie

Et la seule mention/indication que plusieurs appels sont nécessaires se trouve dans la description du paramètre successCallback :

S'il ne reste aucun fichier ou si vous avez déjà appelé readEntries () sur ce FileSystemDirectoryReader, le tableau est vide.

On peut dire que l'API pourrait également être plus intuitive, mais comme le note la documentation: c'est une fonctionnalité non standard/expérimentale, pas sur une piste standard, et on ne peut pas s'attendre à ce qu'elle fonctionne pour tous les navigateurs.

Connexes:

  • commentaires de johnozbay que sur Chrome, readEntries renverra au plus 100 entrées pour un répertoire (vérifié comme Chrome 64).
  • Xan explique très bien l'utilisation correcte de readEntries dans ce réponse (quoique sans code).
  • réponse de Pablo Barría Urenda appelle correctement readEntries de manière asynchrone sans BFS. Il note également que Firefox renvoie toutes les entrées d'un répertoire (contrairement à Chrome) mais nous ne pouvons pas nous en remettre à cela compte tenu des spécifications.
17
xlm

Dans ce message à la liste de diffusion HTML 5, Ian Hickson dit:

HTML5 doit maintenant télécharger de nombreux fichiers à la fois. Les navigateurs pourraient permettre aux utilisateurs de sélectionner plusieurs fichiers à la fois, y compris dans plusieurs répertoires; c'est un peu hors de portée de la spécification.

(Voir également l'original proposition de fonctionnalité .) Il est donc prudent de supposer qu'il considère le téléchargement de dossiers par glisser-déposer également hors de portée. Apparemment, c'est au navigateur de servir des fichiers individuels.

Le téléchargement de dossiers aurait également d'autres difficultés, comme décrit par Lars Gunther :

Cette proposition […] doit avoir deux contrôles (si elle est réalisable):

  1. Taille maximale, pour empêcher quelqu'un de télécharger un répertoire complet de plusieurs centaines d'images brutes non compressées ...

  2. Filtrage même si l'attribut accept est omis. Les métadonnées Mac OS et les miniatures Windows, etc. doivent être omises. Tous les fichiers et répertoires cachés doivent par défaut être exclus.

13
Marcel Korpel

Vous pouvez maintenant télécharger des répertoires par glisser-déposer et saisie.

<input type='file' webkitdirectory >

et pour le glisser-déposer (pour les navigateurs webkit).

Gestion des dossiers glisser-déposer.

<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>

Ressources:

http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available

9
Konga Raju

Firefox prend désormais en charge le téléchargement de dossiers, à partir du 15 novembre 2016, dans la version 50.0: https://developer.mozilla.org/en-US/Firefox/Releases/50#Files_and_directories

Vous pouvez faire glisser et déposer des dossiers dans Firefox ou vous pouvez parcourir et sélectionner un dossier local à télécharger. Il prend également en charge les dossiers imbriqués dans des sous-dossiers.

Cela signifie que vous pouvez désormais utiliser Chrome, Firefox, Edge ou Opera pour télécharger des dossiers. Vous ne pouvez pas utiliser Safari ou Internet Explorer pour le moment.

8
Dan Roberts

Cette fonction vous donnera une promesse de tableau de tous les fichiers déposés, comme <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)
      })
  })
}

Usage:

dropArea.addEventListener("drop", function(event) {
  event.preventDefault();

  var items = event.dataTransfer.items;
  getFilesFromWebkitDataTransferItems(items)
    .then(files => {
      ...
    })
}, false);

paquet npm

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

exemple d'utilisation: https://github.com/grabantot/datatransfer-files-promise/blob/master/index.html

8
grabantot

La spécification HTML5 ne dit PAS que lors de la sélection d'un dossier à télécharger, le navigateur doit télécharger tous les fichiers contenus de manière récursive.

En fait, dans Chrome/Chromium, vous pouvez télécharger un dossier, mais lorsque vous le faites, il télécharge simplement un fichier 4KB sans signification, qui représente le répertoire. Certaines applications côté serveur comme Alfresco peuvent détecter cela et avertir l'utilisateur que les dossiers ne peuvent pas être téléchargés:

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

2
Nicolas Raoul

Voici un exemple complet de la façon d'utiliser API d'entrées de fichier et de répertoire :

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 est pris en charge par Chrome 13+, Firefox 50+ et Edge.

Source: https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry

2
Paolo Moretti

HTML5 permet-il le téléchargement par glisser-déposer de dossiers ou d'une arborescence de dossiers?

Seul Chrome prend en charge cette fonctionnalité. Elle n'a pas réussi à tirer parti et est susceptible d'être supprimée.

Réf: https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries

1
basarat