javascript - HTML5 ondrop event returns before zip.js can finish operations -
the crux of issue need use datatransferitemlist asynchronously @ odds functionality described in specs, locked out of datatransfer.items collection once event ends.
https://bugs.chromium.org/p/chromium/issues/detail?id=137231 http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-data-store
the case offender following. more detailed description of problem , thoughts below it.
drophandler: function(event) { event.stoppropagation(); event.preventdefault(); event.datatransfer.dropeffect = 'copy'; zip.workerscriptspath = "../bower_components/zip.js/webcontent/"; zip.usewebworkers = false; // disabled because makes life more complicated // check if files contains zip if (event.datatransfer.files[0].name.match(/(?:\.([^.]+))?$/) == 'zip') { var reader = new filereader(); = this; reader.onload = function(e) { that.fire('zipuploaded', e.target.result.split(',')[1]); } reader.readasdataurl(event.datatransfer.files[0]); // rev in browser zipping } else { var = this; var items = event.datatransfer.items; // async operation, execution falls through here zip.createwriter(new zip.data64uriwriter(), function(writer) { (function traverse(list, path, i, depth) { return new promise(function(resolve, reject) { var item; if (depth == 0) { if (i == list.length) { writer.close(function(uri) { that.fire('zipuploaded', uri.split(',')[1]); // base64, please fulfill(1); return; }); } else { console.log(i); console.log(list); var item = list[i].webkitgetasentry(); } } else { if (i == list.length) { resolve(0); return; } else { item = list[i]; } } if (item.isfile) { item.file(function(file) { // zipping operations done asynchronously, it'll fail second operation writer.add(path + file.name, zip.blobreader(file), function() { traverse(list, path, + 1, depth).then(resolve(0)); // next item }); }); } else if (item.isdirectory) { var dirreader = item.createdirreader(); dirreader.readentries(function(entries) { // operate on child folder next item @ level traverse(entries, path + item.name + "/", 0, depth + 1).then(function() { traverse(list, path, + 1, depth).then(resolve(0)); }); }); } }); })(items, "", 0, 0); // begin datatransferitemlist, 0th element, depth 0 }); this.$.uploadarea.classlist.remove('highlightdrag'); } // when exit kills event.datatransfer.items },
i using zip.js asynchronous html5 dnd api. ondrop event ends before asynchronous zip.createwriter/writer.add operations finish. can think of 4 ways solve although don't know how implement of them , advice.
- block until createwriter done. (blocking javascript? uhoh)
- prevent ondrop locking me out of datatransfer.items (it seems security unlikely)
- synchronously copy out contents of datatransfer.items first (probably slow)
- do synchronously (don't think zip.js allows this, jszip does, moved away due having own limitations large file sets)
html5 dnd works expected. problem is, when adding multiple files, if add file before previous finish, zip.js
breaks silently. can fixed calling writer.add
in series.
the snippet might not work, see pen instead.
this example flats structure of dropped files, add zip in series.
function mes(it) { const m = document.queryselector('#mes') return m.textcontent = + '\n' + m.textcontent } function read(items) { return promise.all(items.map(item => { if (item.isfile) return [item] return new promise(resolve => item.createreader().readentries(resolve)) .then(entries => { entries.foreach(it => it.path = item.path + '/' + it.name) return read(entries) }) })).then(entries => entries.reduce((a, b) => a.concat(b))) } function handleresult(blob){ const res = document.queryselector('#result') res.download = 'files.zip' res.href = window.url.createobjecturl(blob) res.textcontent = 'download zipped file' } function handleitems(items){ mes(items.length) items.foreach(item => item.path = item.name) const initzip = new promise(resolve => zip.createwriter(new zip.blobwriter, resolve) ) const getfiles = read(items).then(entries => { return promise.all(entries.map(entry => new promise(resolve => entry.file(file => { file.path = entry.path resolve(file) }) ) )) }) return promise.all([getfiles, initzip]).then(([files, writer]) => files.reduce((current, next) => current.then(() => new promise(resolve => { mes(next.path) writer.add(next.path, new zip.blobreader(next), resolve) }) ) , promise.resolve()) .then(() => writer.close(handleresult)) ) } zip.usewebworkers = false const drop = document.queryselector('#drop'); ['dragover', 'drop'].foreach(name => drop.addeventlistener(name, ev => ev.preventdefault()) ) drop.addeventlistener('drop', ev => { const items = [].slice.call(ev.datatransfer.items) .map(item => item.webkitgetasentry()) return handleitems(items) })
html, body, #drop { height: 100%; width: 100%; }
<script src="http://gildas-lormeau.github.io/zip.js/demos/zip.js"></script> <script src="http://gildas-lormeau.github.io/zip.js/demos/deflate.js"></script> <div id="drop"> drop here! <br> <a id="result"></a> </div> <pre id="mes"></pre>
jszip easier this, might want give try.
Comments
Post a Comment