Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: support directories on drag and drop #60

Closed
michaelmdr opened this issue Oct 26, 2023 · 10 comments · Fixed by #67
Closed

Suggestion: support directories on drag and drop #60

michaelmdr opened this issue Oct 26, 2023 · 10 comments · Fixed by #67

Comments

@michaelmdr
Copy link

Hello Harold, I really appreciate your great work!

I would like to implement the ability to extract files from folders and subfolders using drag and drop.

If you don't mind, I'll start a pull request in the near future.

Greetings from Bavaria!

hackingharold added a commit that referenced this issue Nov 22, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
@hackingharold
Copy link
Owner

Hi there,

I just released the latest version for Angular v17 which includes support for dropping folders by default.
Please let me know if this helps you. I used your implementation for inspiration.

Releases

@michaelmdr
Copy link
Author

michaelmdr commented Nov 24, 2023

Hello there,

I just tested it, it works great! Thank you very much.

One detail i noticed is that in Chrome if you drop a folder containing more than 100 files, it just gets 100 files and not the whole content. For that issue i wrote the _breakDirectoryReaderFilesLimit* method in my Pull Request to solve this problem. It would be nice to also solve this within your solution.

It is arguably not the prettiest solution, but it does its job (maybe you are able to find a better one):

private async _breakDirectoryReaderFilesLimit(directoryEntry: FileSystemDirectoryEntry) {
  const reader = directoryEntry.createReader();
  let resultEntries: FileSystemEntry[] = [];

  const recurse = async () => {
    const entries = await this._readDirectoryEntries(reader);
    if ((entries).length > 0) {
      resultEntries = resultEntries.concat(entries);
      await recurse();
    }
  };

  await recurse();
  return resultEntries;
}

Thank you in advance,
I'm looking forward to your solution!

@hackingharold
Copy link
Owner

Hi @michaelmdr,

thanks for your feedback. As stated in MDN, the readEntries() method will only return the first 100 entries.
I am just curious, what's your use case for uploading more than a hundred files using a single file input?

In my opinion, we should use an optional limit property to keep this default behaviour for users which are less involved in this topic.

@hackingharold hackingharold reopened this Nov 27, 2023
@michaelmdr
Copy link
Author

Hey @hackingharold,

for your interest, we develop a data asset managament (DAM) software. Some of our customers need to upload large amounts of files at once. Hence the ability to drag and drop files from folders breaking the 100 files limit makes things easy.

The optional limit property is a good idea!

@hackingharold
Copy link
Owner

Hi @michaelmdr,

I just released v17.2.0 which skips the limit by default.

After thinking about it, I decided not to create a property for this. We're basically creating a consistent cross-browser experience by ignoring Chrome's behavior here.

@michaelmdr
Copy link
Author

Hello @hackingharold,

Thank you for your efforts to ensure our requirements!

@alexandis
Copy link

alexandis commented Jan 24, 2024

Hi guys.

Would be great to include file relative path information (now it is always ''). The reason this might be needed - if we are going to recreate a directory structure on server.

So I had to use this (probably not the best) method for that:

onDropCases(event: DragEvent) {
    var items = event.dataTransfer.items;
    this.files = [];
    let rejectionOccurred = false;
    const traverseDirectory = (entry: FileSystemEntry, path: string): Promise<void> => {
      return new Promise<void>((resolve, reject) => {
        if (entry.isFile) {
          const fileEntry = entry as FileSystemFileEntry;
          fileEntry.file((file: File) => {
            if (rejectionOccurred) {
              //Skip further processing
            }
            else if (file.size > AbxMeasureProtocols.maxUploadFileSizeMb * 1024 * 1024) {
              this.files = [];
              this.toaster.error('::MeasureProtocols:MaxUploadFileSizeError', 'Core:CommonUI::Error', { ...this.toasterOptions && { life: 2000 }, messageLocalizationParams: [ AbxMeasureProtocols.maxUploadFileSizeMb.toString() ] });
              rejectionOccurred = true;
              reject(new Error('File size exceeds the allowed limit.'));
            }
            else {
              this.files.push(this.copyFile(file, fileEntry.fullPath));
              resolve();
            }
          });
        }
        else if (entry.isDirectory) {
          const directoryEntry = entry as FileSystemDirectoryEntry;
          const dirReader = directoryEntry.createReader();
          dirReader.readEntries(entries => {  
            const promises = entries.map(childEntry => traverseDirectory(childEntry, path + entry.name + '/'));
            Promise.all(promises).then(() => resolve());
          });
        }
      });
    };
    const promises = [];
    for (let i = 0; i < items.length; i++) {
      let item = items[i];
      if (item.kind === 'file') {
        let webkitGetAsEntry = item.webkitGetAsEntry();
        promises.push(traverseDirectory(webkitGetAsEntry, ''));
      }
    }
    Promise.allSettled(promises).then(() => {});
  }

@hackingharold
Copy link
Owner

Hi @alexandis,

the missing webkitRelativePath is a common issue when dropping folders which is also discussed in other dropzone libraries. Bottom line is, if the native system dialog is used the path is included, on drop it is lost when transforming the FileSystemFileEntry to the well-known File blob. Since it's a readonly property, it cannot be reassigned on the File object.

In my opinion as a library maintainer, I want to stay as close to the HTML standard as possible. Other libraries solve this by returning custom FileEntry elements with custom properties attached, but this is not an option for me atm.
I suggest you disable the drop behavior and only allow users to use the native OS dialog to work around this.

@alexandis
Copy link

alexandis commented Jan 27, 2024

Hi @hackingharold.

Drag-n-drop behavior was a client requirement (as usual it happens :-D).
Besides, I have noticed, that Native OS dialog does not allow selecting multiples folders (which is strange, since multiple attribute is present).

All in all, I am stick to my own custom handler for drop event (for the sake this relative file path). But I've noticed, that I bumped onto the same limitation which was mentioned before, 100 files per readEntries. Does it mean I need to copy-paste the code already implemented in your library? Saying the truth, I don't know how what is a correct way to do that without pulling too much copy-paste. Could you please help me with this my part?

        else if (entry.isDirectory) {
          const directoryEntry = entry as FileSystemDirectoryEntry;
          const dirReader = directoryEntry.createReader();
          dirReader.readEntries(entries => {  
            const promises = entries.map(childEntry => traverseDirectory(childEntry, path + entry.name + '/'));
            Promise.all(promises).then(() => resolve());
          });
        }

@hackingharold
Copy link
Owner

Yes, you cannot select multiple directories from the file picker. The multiple attribute only works for files, which is a native behavior.

To work around the file limit, I've written the readDirectoryWithoutLimit method, maybe this helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants