vaadin / web-components

A set of high-quality standards based web components for enterprise web applications. Part of Vaadin 20+
https://vaadin.com/docs/latest/components
443 stars 83 forks source link

[upload] Add support for uploading folders #857

Open onuridrisoglu opened 4 years ago

onuridrisoglu commented 4 years ago

It should be possible to select or drop a folder and upload the contents of the folder

Haprog commented 4 years ago

This is a good idea to offer a slightly better UX for uploading all files in a directory. Can be considered as progressive enhancement imo and ok to implement even if it won't work on all browsers (looks like it's well supported on desktop browsers except not on IE11).

Related resources:

Haprog commented 4 years ago

One thing to consider is what should happen if the selected directory contains nested directories with more files (and possibly files of same name in sub directories)? I didn't check if the native APIs allow for getting the whole directory hierarchy or only some flattened list of files from the directory (or does it even get files from sub directories of selected directory?).

There could be an additional (optional) related feature that would automatically zip the directory on client side before uploading it. Or other special handling (or customizable handler method) might be needed.

We should probably investigate how it behaves in these cases with the native:

<input type="file" directory>

and try to mimic that behaviour. Possibly providing additional value/features.

web-padawan commented 1 year ago

Note, we can use webkitdirectory attribute for uploading folders - see the Codepen illustrating how it works. While it allows to select folders, it disallows selecting individual files, so you can't have both at the same time.

web-padawan commented 1 year ago

Here's the current version of the File and Directory Entries API: https://wicg.github.io/entries-api/

It contains some examples of how to get the directories from the drag and drop (which will require us to change how we handle event.dataTransfer object) and helper functions e.g. getEntriesAsPromise() and readFileEntry().

These APIs could be used to get the contents (individual files) from the folder uploaded using drag & drop.

tommilukkarinen commented 11 months ago

Had some fun learning JS and injecting code into Vaadin client:

Problems: Does not pass folder structure to server, might be easiest to make with standard element.$server invocation

Test with text files, these will print to the screen

Tried only in Jetty, might be some problems in production release?

Use:

  1. press inject button
  2. drop folders to upload area
package fi.protieto.juuri.front.pages.map4;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.upload.Upload;
import com.vaadin.flow.component.upload.receivers.MultiFileMemoryBuffer;
import com.vaadin.flow.router.Route;
import org.apache.commons.io.IOUtils;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;

// written on top of Vaadin autoupload example

@Route("upload-auto-upload-disabled")
public class AutoUpload extends Div {
    // event listener explained
    // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
    // handling folders explained
    // https://stackoverflow.com/questions/3590058/does-html5-allow-drag-drop-upload-of-folders-or-a-folder-tree
    // replacing functions explained:
    // https://stackoverflow.com/questions/2136522/can-you-alter-a-javascript-function-after-declaring-it
    // web component:
    // https://github.com/vaadin/web-components/blob/main/packages/upload/src/vaadin-upload.js
    // source in chrome:
    // vaadin -> bundles -> node-modules -> @vaadin -> upload -> src -> vaadin-upload.js

    public AutoUpload() {
        MultiFileMemoryBuffer buffer = new MultiFileMemoryBuffer();
        Upload upload = new Upload(buffer);
        add(upload);

        Button injectJS = new Button("inject");
        injectJS.addClickListener(e -> {
            upload.getElement().executeJs(
                            "" +

                            "function traverseFileTree(item, path, uploadThing) {" +
                                "if (item.isFile) {" +
                                    "item.file(function(file) {" +
                                        "uploadThing._addFile(file);" +
                                    "});" +
                                "}" +
                                "if (item.isDirectory) {" +
                                    "var dirReader = item.createReader();" +
                                    "dirReader.readEntries(function(entries) {" +
                                        "for (var i=0; i<entries.length; i++) {" +
                                            "traverseFileTree(entries[i], path + item.name + '/', uploadThing);" +
                                        "}" +
                                    "});" +
                                "}" +
                            "}" +

                            "this.addEventListener('drop', function(event) {" +
                                "var items = event.dataTransfer.items;" +
                                "console.log('_onDrop top level item size: ' + items.length);" +
                                "for (var i=0; i<items.length; i++) {" +
                                    "var item = items[i].webkitGetAsEntry();" +
                                    "var length = traverseFileTree(item, '', this);" +
                                "}" +
                            "});" +

                            "this._addFiles = function(files) {" +
                                "if(!files.isArray) {" +
                                    "console.log('_addFiles files as string (not array):' + JSON.stringify(files));" +
                                    "return;" +
                                "}" +
                            "};"
                    );
        });

        add(injectJS);

        upload.addSucceededListener(e -> {
            try {
                InputStream is = buffer.getInputStream(e.getFileName());

                String result = IOUtils.toString(is, StandardCharsets.UTF_8);

                System.out.println(e.getFileName() + ": " + result);

                is.close();
            }catch (Exception ex){
                ex.printStackTrace();
            }
        });

    }
}