-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Allow to select multiple files at once from remote providers #419
Conversation
Added isFolder property for each file, so it's now possible to discard them in custom file checkers, but I'm still not sure if we should just set a mimetype for them, or keep it as another property. Google Drive has |
Hi! Thanks a lot for your work, it’s a useful feature indeed. Will test it out and review soon. @ifedapoolarewaju might join in for uppy-server conversation :) |
src/core/Core.js
Outdated
@@ -338,6 +338,8 @@ class Uppy { | |||
}, | |||
size: file.data.size || 'N/A', | |||
isRemote: isRemote, | |||
isCheckbox: file.isCheckbox || false, | |||
isFolder: file.isFolder || false, | |||
remote: file.remote || '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how I feel about adding isCheckbox
to a file object. It seems it’s only used to not close the provider panel when a checkbox is selected. I think we could separate the file name and checkbox is the markup and have two different click handlers for those cases.
Or maybe it’s better to not show checkboxes at all do multiselect on click, and then submit only when “done” is pressed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isFolder
is not used at the moment, because of uppy-server support, as you mentioned? Would it make sense to do this client-side, meaning that if a folder is selected, we requests it’s content and then add all files in it? Having a file that has isFolder: true
feels a little weird, I see that it’s supported in Google Drive, but we have to check OneDrive, Dropbox and others as well before we go this route.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, isCheckbox
is only used to suppress hideAllPanels
function, which is always called on core:file-added
event. We cannot avoid calling that event, so I'm not sure about the best solution here. Maybe we shouldn't use hideAllPanels
at all?
Or maybe it’s better to not show checkboxes at all do multiselect on click, and then submit only when “done” is pressed?
This would probably solve the previous problem since we won't hide the panel automatically anymore. Hopefully you could decide on this.
I use isFolder
in my custom checkRestrictions
function, which isn't a perfect use case to include it in this PR, but it feels odd to exclude it.
Dropbox supports isFolder as well. The idea is that server could easily process and automatically download thousands of files, while doing this on client side would take much more time. But it seems like the later option could work with existing uppy-server API. Maybe we could come up with a default behavior which does this on client side, but with ability to pass a custom function which will process the folder on server side instead? onBeforeFileAdded
could possibly be used for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to do this client-side, meaning that if a folder is selected, we requests it’s content and then add all files in it
I am in favor of this because it requires less implementation and less added complexity. Currently when a file is sent to the server for upload, a socket channel is created and sent to the client to receive upload progress. Sending a folder for the server to download would mean various socket channels would need to be created for each file in the folder so that the client can get upload progress for each file as they happen in parallel. While this is doable, I'd rather we don't add the extra complexity if there's an alternative. 🙂
The idea is that server could easily process and automatically download thousands of files, while doing this on client side would take much more time.
the actual file downloads would happen on uppy-server, we would just be fetching the metadata of all the files in the selected folder on the client side. This is already what we do when you click a folder from the dropbox/google drive view on the dashboard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, I completely forgot about sockets since I had to remove them from my server implementation (google app engine restrictions). I'll work on this. I will skip recursive folder walk to be safe, and we will need additional 'loading' state for each checkbox where it's disabled while we fetch the data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe it’s better to not show checkboxes at all do multiselect on click, and then submit only when “done” is pressed?
BTW, I still like this idea but how do we differentiate between selection a folder and opening it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we could differentiate on double click, but that is tricky on mobile. Let’s go with checkboxes for now.
src/plugins/Dashboard/FileItem.js
Outdated
@@ -99,7 +99,7 @@ module.exports = function fileItem (props) { | |||
} | |||
</h4> | |||
<div class="UppyDashboardItem-status"> | |||
${file.data.size && html`<div class="UppyDashboardItem-statusSize">${prettyBytes(file.data.size)}</div>`} | |||
${isNaN(file.data.size) ? '' : html`<div class="UppyDashboardItem-statusSize">${prettyBytes(file.data.size)}</div>`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are trying to support IE 10-11, so we’ll need a polyfill for this one, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use lodash.isFinite for this, but again I'm not sure if they support IE10. I could just hardcore something like this but it looks messy.
src/generic-provider-views/index.js
Outdated
} | ||
} | ||
|
||
fileToId (file) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sadovnychyi what is the need for the fileToId
method? Why not just use this.plugin.getItemId(file)
?
Nice work btw 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.plugin.getItemId(file)
returns an ID from provider, while internally uppy uses Utils.generateFileID
to construct file IDs from multiple parameters. Internal uppy ID is required to remove existing file when you uncheck the checkbox (toggleCheckbox
) and to check if the current checkbox isChecked
, by looking on files in uppy by their ID.
this.plugin.core.removeFile(itemId)
– this one has to be internal uppy ID, not providers ID.
this.fileToId(file) in this.plugin.core.getState().files
we could avoid using internal uppy ID here by comparing provider's ID for each file, but since we already use it to remove file I left it as is.
The checkbox state isn't updated properly anymore because of (abfd1c7#diff-24345cdf8d762d6253b9e81d7f2e8238R66). @arturi could you suggest anything since it was your change? The HTML property checked="checked" is set correctly but the UI isn't updated in time. Can I revert it back to use |
Updated to fetch folder contents on checkbox change. Each checkbox is disabled while we fetch the data.
|
Yeah, tested it too, I’m able to reproduce. Haven’t got too many ideas myself, let’s keep Reasons for removing
|
Great work on folders and overall! 👌 Thank you.
Idea: would it make sense to keep a list of selected files in local state until “done” is pressed, and only then call
And this will also be solved if we don’t store selected files permanently, but instead reset that list once “done” is pressed? I might be overseeing something though.
I think an Informer message about this would be cool, yes. |
Well this would add some extra complexity since we would have to manage two separated states.
But it seems bad for UX to reset the state once done is pressed. I would rather have an option to come back to that screen to select additional files after overviewing them at main screen. I've removed the isCheckbox core property, and we now call hideAllPanels manually after a file been added instead of listening to an event. Fixed an error in dashboard's i18n and i18ned the "added X files from Y" message. Also, we now listen to file removal events so we will uncheck a folder if you remove all files from the main screen. Not sure though how do we stop listening on this event in case plugin gots destroyed. |
Well, depends on how you look at the UX. I do like the way it currently works, apart from the added mild code complexity. What I meant was that we could (not saying we should) treat file selection in provider as a temporary “selection mode” thing that you do to add files “to cart”. Like you are on an e-commerce site and you clicked “add to cart” on a t-shirt, it won’t necessarily add “already in cart” badge to the t-shirt :-) Same if you click “My Device” and files from local disk, they won’t show that you’ve added those files when you return to the system file selection dialog. Having said that, you actually went extra mile to implement this state-syncing, and I think it’s a good feature to have. |
I guess a proper way to do it is to move that event listener to each provider’s install method, and add
👍 |
We would have to add something like ProviderPlugin with those install/uninstall methods so others (drive, dropbox, etc.) would extend from it instead of a Provider. |
Well, we already have the |
yes but this logic is more concerned with the view and not the provider. I think we could have like a So in the view we can have like tearDown() {
// remove listener here
} Then in the provider plugin we have uninstall() {
this.view.tearDown()
this.unmount()
} |
I’m in favor of what @ifedapoolarewaju suggested:
Let’s do it like that, @sadovnychyi would you be able to add this to your PR? Or I can add it later after merge. |
Added tearDown method. |
Thank you! One last nitpick: would you be able to add a little comments in difficult places like here: https://github.com/sadovnychyi/uppy/blob/0e7505dcda86040a4663499532ecb6c0c41c8994/src/generic-provider-views/index.js#L413-L452 ? I worry that we might have hard times debugging this sometime later. |
Added some docstrings. I think understanding the basic idea of a method should be enough. |
Thank you! Merged. |
Right now it's pain to select multiple files from e.g. Google Drive, since you need to constantly open the window to select another file. This PR adds a checkbox next to each file and folder.
While current uppy-server implementation isn't able to accept "folders" I believe it could be useful as well, at least for my use case – I made my own implementation of the server side on python, and it seems should be easy to download everything from a dropbox/drive folder.
I have no idea how this PR affects other providers or plugins, so please advice on that. File ID checks are a bit ugly, not sure about the best way to do that. Is it safe to just compare the provider ID? Will they ever overlap?
Should we automatically disable checkboxes if maxNumberOfFiles is set to 1?
Maybe whole table row should act like a checkbox when you click with shift?