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

How can I get info about dragging files in canDrop method? #584

Closed
andrewQwer opened this issue Nov 22, 2016 · 36 comments · Fixed by #3262
Closed

How can I get info about dragging files in canDrop method? #584

andrewQwer opened this issue Nov 22, 2016 · 36 comments · Fixed by #3262
Labels
pinned won't become stale from stalebot

Comments

@andrewQwer
Copy link

I want to restrict allowed files extension and need to know what files user is dragging, but console throws warning: NativeDragSources.js:61 Browser doesn't allow reading "files" until the drop event. So what is the best way of checking file extension or dragging files count before dropping it on drop target?

@theTechie
Copy link
Member

theTechie commented Feb 14, 2017

Are you looking for not allowing drag on the Source OR to show not allowed on the Target based on a type ?

  1. to not allow drag, you should return false from canDrag in Source.
  2. to not allow drop, you should control using types on Target

@andrewQwer
Copy link
Author

@theTechie No, I'm loking for a way of reading file extension that I'm currently dragging.

@theTechie
Copy link
Member

All of the source and target methods get monitor as the second argument. (including canDrop)

You can use monitor.getItem() to get the currently dragged items.

Reference:

canDrop(props, monitor): Optional. Use it to specify whether the drop target is able to accept the item. If you want to always allow it, just omit this method. Specifying it is handy if you'd like to disable dropping based on some predicate over props or monitor.getItem(). Note: You may not call monitor.canDrop() inside this method.
you can use the monitor

@andrewQwer
Copy link
Author

It doesn't work for files

@theTechie
Copy link
Member

That's strange.
I believe you are dragging and dropping files from filesystem onto a Target ?

I have done a similar thing and it seems to be working for me. If you can share some code, we can probably see what's wrong.

@andrewQwer
Copy link
Author

Could you please show how you read file extension in canDrop method?

@theTechie
Copy link
Member

as i already mentioned above, monitor.getItem().files inside canDrop should give you an array of native File objects

@andrewQwer
Copy link
Author

Read my fist comment, when you read files in canDrop it throws error like I mentioned. Do you have working example of reading files in canDrop or you just refer to the documentation? Error says that i can't read files until drop event NativeDragSources.js:61 Browser doesn't allow reading "files" until the drop event

@theTechie
Copy link
Member

theTechie commented Feb 14, 2017

It's possible we are using two different versions and this restriction has been added later. I am using 2.1.4.

@andrewQwer
Copy link
Author

Ok, I will test the latest version and let you know if it works

@turnerhayes
Copy link

I'm getting this, too, when trying to filter files in canDrop by MIME type. As soon as I call monitor.getItem().files it throws the above warning (Chrome 56.0.2924.87, react-dnd 2.2.4, react-dnd-html5-backend 2.2.4).

@turnerhayes
Copy link

According to MDN my case, at least, could be accomplished using a property on event.dataTransfer, but as far as I can see, react-dnd doesn't expost that object or its information anywhere.

@andrewQwer
Copy link
Author

@theTechie

It's possible we are using two different versions and this restriction has been added later. I am using 2.1.4.

No, I use the same version. Also updated to the latest possible version (2.2.4) and getting the same error:

image

canDrop method looks like this:

canDrop: function (props, monitor) {      
        let files = monitor.getItem().files;
        console.log('Files: ', files);
        return true;
    }

How can it work on your side? Do you have working example?

@theTechie
Copy link
Member

theTechie commented Mar 8, 2017 via email

@theTechie
Copy link
Member

theTechie commented Mar 8, 2017

@andrewQwer - really sorry!
i had a wrong memory of what i had implemented and it doesn't use monitor.getItem().files in canDrop, all i am doing is restricting the number of files which can be dropped by maintaining the file count once it's dropped.

sorry for misleading!

PS: i am looking into ways we can achieve this, will let you know if i am able to.

@theTechie
Copy link
Member

I don't think this will ever be possible. Looks like reading info on dragged items is restricted intentionally for security purpose.

http://stackoverflow.com/questions/25016442/how-to-distinguish-if-a-file-or-folder-is-being-dragged-prior-to-it-being-droppe

@turnerhayes
Copy link

Would it be possible to expose some information about the files via the event.dataTransfer object (e.g. types)? I realize that may not satisfy all use cases, but it would help at least some.

@samsch
Copy link

samsch commented Aug 24, 2017

This might be something to be added to the HTML5 backend, but it does seem possible to determine if a file fits an accepted set of mime types before the file/folder is dropped. Specifcally, react-dropzone does this when you hover the item over the dropzone, as can be seen in this example.

@janpe
Copy link

janpe commented Nov 14, 2018

Any solution for this issue yet?

@stale
Copy link

stale bot commented Jul 6, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Jul 6, 2019
@dozoisch
Copy link

dozoisch commented Jul 6, 2019

Not stale

@stale stale bot removed the wontfix label Jul 6, 2019
@danisal
Copy link

danisal commented Sep 4, 2019

I'm also looking for a way to allow dropping specific files, e.g.: images, video, like react-dropzone. Does anyone have a solution?

@stale
Copy link

stale bot commented Nov 3, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Nov 3, 2019
@stale stale bot closed this as completed Nov 10, 2019
@LeopoldLerch
Copy link
Contributor

I have the issue too. It seems to be quite hardcoded in the "NativeDragSource.ts".

It creates a DragSource, however, for accessing the values it just adds a console-warning mentioning that the browser wouldn´t allow it.

However, the browser DOES allow it, it is just in protected mode and does not allow changing it or accessing the DATA ( of the files, but the items can with the files´ properties should be able to be enumerated. (like DropZone does).

Will try something and come back to you

@stale stale bot removed the wontfix label Nov 22, 2019
@darthtrevino darthtrevino added the pinned won't become stale from stalebot label Nov 22, 2019
@serhiiminin
Copy link

@LeopoldLerch @darthtrevino I still cannot access to files list before I drop. I want to check the mime type of the file on hover and change the color of the border depending on if it's a valid format of the file. I am using canDrop method for useDrop hook. And it returns an array of files just when I drop. When I hover over drop zone I have an empty array

@LeopoldLerch
Copy link
Contributor

LeopoldLerch commented Apr 2, 2020

Thing is, the events, that the browser delivers, are as they are, we can´t change them. The browser gives access to the data only on dragstart. After that, all subsequent events are in protected mode, which means, you can´t access the data.

if you use the onHover method of useDrop, you will get empty metadata, as the browser does not allow you to access it on "dragenter". Even if you store the object in dragstart (or the collect-function), the browser will in the background clear the properties, as you do have a reference to the object, not a copy of it.

The only way to work around that, is to use the "collect" function. Here you can access the "items" or "files" of the item (depending on the browser you use, IE11 will never allow you to access them before ondrop).

however, you have to do all by yourself: validating and remembering it in eg. a useRef. For example

collect: monitor => {
            const item: DataTransfer = monitor.getItem();
            const isOver = monitor.isOver();
            let _canDrop: VerificationResult = null;
            if (item) {
                if (item.items) {
                    if (item.items.length > 0) {
                        draggedFilesValid.current = allFilesAccepted(
                            fromList<DataTransferItem>(item.items),
                            accept,
                            multiple,
                            maxSize,
                            minSize
                        )
                            ? VerificationResult.Valid
                            : VerificationResult.Invalid;
                    }
                }
            } else {
                draggedFilesValid.current = null;
            }

            if (isOver && item && item.files) {
                _canDrop = VerificationResult.Unknown;
                if (draggedFilesValid.current != null) {
                    _canDrop = disabled
                        ? VerificationResult.Invalid
                        : draggedFilesValid.current;
                }
            }

            return {
                isDragActive: item != null,
                canDrop: _canDrop
            };
        }

@serhiiminin
Copy link

@LeopoldLerch
Thank you for the explanation! I will try this kind of workaround

@jgonera
Copy link
Contributor

jgonera commented Jul 16, 2021

@LeopoldLerch This does not work for me on Chrome 91 or Safari 14.1.1 on macOS. Surprisingly it does work on Firefox 90. In Chrome and Safari item.items.length is always 0.

I also second that getting this information is certainly possible in all the browsers as pointed out in #584 (comment). Go to https://react-dropzone.js.org/#section-styling-dropzone and try to drop an image and the border will turn green. Try to drop a different file and it will turn red. This is consistent across Chrome, Safari, and Firefox so it seems that react-dnd could get that data as well.

Test case of react-dnd's current behavior: https://codesandbox.io/s/solitary-architecture-gbw0e?file=/src/TargetBox.tsx
Open the console and try dropping a file in different browsers. Only Firefox will have item.items populated.

jgonera added a commit to jgonera/react-dnd that referenced this issue Jul 18, 2021
This is needed to load DataTransferItems from subsequent events in
Chrome and Safari.

Fixes react-dnd#584
jgonera added a commit to jgonera/react-dnd that referenced this issue Jul 18, 2021
Chrome (and apparently Safari too) will empty the `DataTransferItemList`
after a given drag event is done so we need to point `NativeDragSource`
to a new list on each dragenter/dragover event.

See https://bugs.chromium.org/p/chromium/issues/detail?id=137231

Fixes react-dnd#584
@jgonera
Copy link
Contributor

jgonera commented Jul 19, 2021

Proposed fix and explanation of why it's not fully working right now: #3262

@jgonera
Copy link
Contributor

jgonera commented Jul 19, 2021

@LeopoldLerch Thanks for the first fix you submitted! I can see now what you meant too. I guess your solution is to only use the data when the items list is non-empty and ignore it later when it's empty. With my fix above the list should never be empty as long as files are hovered over the drop target.

@thediamondor
Copy link

@LeopoldLerch
Your solution doesn't work for me on chrome, for some reason the DataTranferItems aren't populated at any time in the collect function for me

@jgonera
Copy link
Contributor

jgonera commented Jul 20, 2021

@thediamondor Make sure you're actually making a copy of the data, or storing only primitive values, e.g. store the type property instead of the entire item. My current code:

interface FilesItem {
  files: File[];
  items: DataTransferItemList;
}

function getFileTypes(item: FilesItem | null): string[] {
  if (!item) {
    return [];
  }

  const { files, items } = item;

  if (files && files.length > 0) {
    return files.map((f) => f.type);
  }

  if (items && items.length > 0) {
    return Array.from(items).map((i) => i.type);
  }

  return [];
}

// ...

collect: (monitor) => {
  const item = monitor.getItem() as FilesItem | null;
  const types = getFileTypes(item);

  // Only some events will propagate correct file info due to a bug in
  // react-dnd so don't change state if there's no data.
  // See https://github.com/react-dnd/react-dnd/pull/3262
  if (types.length !== 0) {
    if (types.length > 1) {
      setState('wrongCount');
    } else if (types[0] !== accept) {
      setState('wrongType');
    } else {
      setState('correct');
    }
  }

  return {
    isOver: monitor.isOver(),
  };
},

@thediamondor
Copy link

My problem is that I'm trying to get the names of the files, and not the type. But unfortunately if I call getAsFile on the items the result is null

@jgonera
Copy link
Contributor

jgonera commented Jul 20, 2021

@thediamondor That's not possible until the drop happens. I believe drop is the only event fired on the drop target that is not in the protected mode. That makes sense because a user might change their mind about dropping something while dragging it and file names can be considered sensitive information.

See https://html.spec.whatwg.org/multipage/dnd.html#dndevents and https://stackoverflow.com/a/41451395/365238

@thediamondor
Copy link

thediamondor commented Jul 20, 2021

Is there any way around this protection?
And will your fix enable this usecase

@LeopoldLerch
Copy link
Contributor

You have to read out the relevant information when the objects are not in protected mode, that is eg when Drag starts. Thats why my Code checks if it can access the items (this is only the case, when the drag actually started), and when, it reads out relevant informations or in my case, checks if the file is valid. The result is then saved in an external variable (in my case a ref) so it is stored through the whole dragging process. When the drag ends ("item" is null), the variable is cleared.

darthtrevino added a commit that referenced this issue Feb 3, 2022
* WIP Add a debugging case for hovering files

Temporarily, remove before merging.

* Load dataTransfer in every dragenter and dragover

Chrome (and apparently Safari too) will empty the `DataTransferItemList`
after a given drag event is done so we need to point `NativeDragSource`
to a new list on each dragenter/dragover event.

See https://bugs.chromium.org/p/chromium/issues/detail?id=137231

Fixes #584

* chore: cut semver

* fix: run prettier

Co-authored-by: Chris Trevino <chtrevin@microsoft.com>
darthtrevino added a commit that referenced this issue Feb 3, 2022
* WIP Add a debugging case for hovering files

Temporarily, remove before merging.

* Load dataTransfer in every dragenter and dragover

Chrome (and apparently Safari too) will empty the `DataTransferItemList`
after a given drag event is done so we need to point `NativeDragSource`
to a new list on each dragenter/dragover event.

See https://bugs.chromium.org/p/chromium/issues/detail?id=137231

Fixes #584

* chore: cut semver

* fix: run prettier

Co-authored-by: Chris Trevino <chtrevin@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pinned won't become stale from stalebot
Projects
None yet
Development

Successfully merging a pull request may close this issue.