如何使用更改和删除事件在 firefox 和 chrome/chromium 上传和列出目录

How to upload and list directories at firefox and chrome/chromium using change and drop events

mozilla 和 webkit 浏览器现在都允许目录上传。当在 <input type="file"> 元素处选择一个或多个目录或在一个元素处放置目录时,如何按照它们在 firefox 和 chrome/chromium 的实际目录中出现的顺序列出所有目录和文件,并对文件执行任务何时迭代所有上传的目录?

您可以在 <input type="file"> 元素上设置 webkitdirectoryallowdirs 属性;将 changedrop 事件附加到 <input type="file"> 元素;在 mozilla 使用 .getFilesAndDirectories(),在 webkitArray.prototype.reduce()Promise,递归 .createReader().readEntries()

Firefox 的注意事项 drop 事件没有将选择列为 Directory,而是 File 具有 size 0 的对象,因此将目录放在firefox 不提供已删除文件夹的表示,即使使用 event.dataTransfer.getFilesAndDirectories() 也是如此。当设置 allowdirs 属性时,firefox 还提供了两个输入元素;第一个元素允许单个文件上传,第二个元素允许目录上传。 chrome/chromium 提供单个 <input type="file"> 元素,其中只能选择单个或多个目录,而不是单个文件。

在包含文件和目录的目录中,首先读取目录。

<!DOCTYPE html>
<html>

<head>
  <style type="text/css">
    input[type="file"] {
      width: 98%;
      height: 180px;
    }

    label[for="file"] {
      width: 98%;
      height: 180px;
    }

    .area {
      display: block;
      border: 5px dotted #ccc;
      text-align: center;
    }

    .area:after {
      display: block;
      border: none;
      white-space: pre;
      content: "Drop your files or folders here!\aOr click to select files folders";
      position: relative;
      left: 0%;
      top: -75px;
      text-align: center;
    }

    .drag {
      border: 5px dotted green;
      background-color: yellow;
    }

    #result ul {
      list-style: none;
      margin-top: 20px;
    }

    #result ul li {
      border-bottom: 1px solid #ccc;
      margin-bottom: 10px;
    }

    #result li span {
      font-weight: bold;
      color: navy;
    }
  </style>
</head>


<body>
  <label id="dropArea" class="area">
    <input id="file" type="file" directory allowdirs webkitdirectory/>
  </label>
  <output id="result">
    <ul></ul>
  </output>
  <script>
    var dropArea = document.getElementById("dropArea");
    var output = document.getElementById("result");
    var ul = output.querySelector("ul");

    function dragHandler(event) {
      event.stopPropagation();
      event.preventDefault();
      dropArea.className = "area drag";
    }

    function filesDroped(event) {
      var webkitResult = [];
      var mozResult = [];
      var files;
      console.log(event);
      event.stopPropagation();
      event.preventDefault();
      dropArea.className = "area";

      // do mozilla stuff
      // TODO adjust, call `listDirectory()`, `listFile()`
      function mozReadDirectories(entries, path) {
        console.log("dir", entries, path);
        return [].reduce.call(entries, function(promise, entry) {
            return promise.then(function() {
              return Promise.resolve(entry.getFilesAndDirectories() || entry)
                .then(function(dir) {
                  return dir
                })
            })
          }, Promise.resolve())
          .then(function(items) {
            var dir = items.filter(function(folder) {
              return folder instanceof Directory
            });
            var files = items.filter(function(file) {
              return file instanceof File
            });
            if (files.length) {
              // console.log("files:", files, path);
              files.forEach(function(file) {
                console.log(file)
              });
              mozResult = mozResult.concat.apply(mozResult, files);
            }
            if (dir.length) {
              // console.log(dir, dir[0] instanceof Directory);
              return mozReadDirectories(dir, dir[0].path || path);

            } else {
              if (!dir.length) {
                return Promise.resolve(mozResult).then(function(complete) {
                  return complete
                })
              }
            }

          })

      };

      function handleEntries(entry) {
        let file = "webkitGetAsEntry" in entry ? entry.webkitGetAsEntry() : entry
        return Promise.resolve(file);
      }

      function handleFile(entry) {
        return new Promise(function(resolve) {
          if (entry.isFile) {
            entry.file(function(file) {
              listFile(file, entry.fullPath).then(resolve)
            })
          } else if (entry.isDirectory) {
            var reader = entry.createReader();
            reader.readEntries(webkitReadDirectories.bind(null, entry, handleFile, resolve))
          } else {
            var entries = [entry];
            return entries.reduce(function(promise, file) {
                return promise.then(function() {
                  return listDirectory(file)
                })
              }, Promise.resolve())
              .then(function() {
                return Promise.all(entries.map(function(file) {
                  return listFile(file)
                })).then(resolve)
              })
          }
        })

        function webkitReadDirectories(entry, callback, resolve, entries) {
          console.log(entries);
          return listDirectory(entry).then(function(currentDirectory) {
            console.log(`iterating ${currentDirectory.name} directory`, entry);
            return entries.reduce(function(promise, directory) {
              return promise.then(function() {
                return callback(directory)
              });
            }, Promise.resolve())
          }).then(resolve);
        }

      }
      // TODO: handle mozilla directories, additional directories being selected dropped and listed without
      // creating nested list at `html` where different directory selected or dropped
      function listDirectory(entry) {
        console.log(entry);
        var path = (entry.fullPath || entry.webkitRelativePath.slice(0, entry.webkitRelativePath.lastIndexOf("/")));
        var cname = path.split("/").filter(Boolean).join("-");
        console.log("cname", cname)
        if (!document.getElementsByClassName(cname).length) {
          var directoryInfo = `<li><ul class=${cname}>
                      <li>
                      <span>
                        Directory Name: ${entry.name}<br>
                        Path: ${path}
                        <hr>
                      </span>
                      </li></ul></li>`;
          var curr = document.getElementsByTagName("ul");
          var _ul = curr[curr.length - 1];
          var _li = _ul.querySelectorAll("li");
          if (!document.querySelector("[class*=" + cname + "]")) {
            if (_li.length) {
              _li[_li.length - 1].innerHTML += `${directoryInfo}`;
            } else {
              _ul.innerHTML += `${directoryInfo}`
            }
          } else {
            ul.innerHTML += `${directoryInfo}`
          }
        }
        return Promise.resolve(entry);
      }
       // TODO: handle mozilla files
      function listFile(file, path) {
        path = path || file.webkitRelativePath || "/" + file.name;
        var filesInfo = `<li>
                        Name: ${file.name}</br> 
                        Size: ${file.size} bytes</br> 
                        Type: ${file.type}</br> 
                        Modified Date: ${file.lastModifiedDate}<br>
                        Full Path: ${path}
                      </li>`;

        var currentPath = path.split("/").filter(Boolean);
        currentPath.pop();
        var appended = false;
        var curr = document.getElementsByClassName(`${currentPath.join("-")}`);
        if (curr.length) {
          for (li of curr[curr.length - 1].querySelectorAll("li")) {
            if (li.innerHTML.indexOf(path.slice(0, path.lastIndexOf("/"))) > -1) {
              li.querySelector("span").insertAdjacentHTML("afterend", `${filesInfo}`);
              appended = true;
              break;
            }

          }
          if (!appended) {
            curr[curr.length - 1].innerHTML += `${filesInfo}`;
          }
        }
        console.log(`reading ${file.name}, size: ${file.size}, path:${path}`);
        webkitResult.push(file);
        return Promise.resolve(webkitResult)
      };

      function processFiles(files) {
        Promise.all([].map.call(files, function(file, index) {
            return handleEntries(file, index).then(handleFile)
          }))
          .then(function() {
            console.log("complete", webkitResult)
          })
          .catch(function(err) {
            alert(err.message);
          })
      }

      if ("getFilesAndDirectories" in event.target) {
        return (event.type === "drop" ? event.dataTransfer : event.target).getFilesAndDirectories()
          .then(function(dir) {
            if (dir[0] instanceof Directory) {
              console.log(dir)
              return mozReadDirectories(dir, dir[0].path || path)
                .then(function(complete) {
                  console.log("complete:", complete);
                  event.target.value = null;
                });
            } else {
              if (dir[0] instanceof File && dir[0].size > 0) {
              return Promise.resolve(dir)
                    .then(function(complete) {
                      console.log("complete:", complete);
                    })
              } else {
                if (dir[0].size == 0) {
                  throw new Error("could not process '" + dir[0].name + "' directory"
                                  + " at drop event at firefox, upload folders at 'Choose folder...' input");
                }
              }
            }
          }).catch(function(err) {
            alert(err)
          })
      }

      // do webkit stuff
      if (event.type === "drop" && event.target.webkitdirectory) {
        files = event.dataTransfer.items || event.dataTransfer.files;
      } else if (event.type === "change") {
        files = event.target.files;
      }

      if (files) {
        processFiles(files)
      }

    }
    dropArea.addEventListener("dragover", dragHandler);
    dropArea.addEventListener("change", filesDroped);
    dropArea.addEventListener("drop", filesDroped);
  </script>
</body>

</html>

plnkrhttp://plnkr.co/edit/ff5vmuuIMzSapu6MiUJ3?p=preview