-
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
High-level React Components #170
High-level React Components #170
Conversation
Thank you! I will review soon. |
I added a bit of an experiment for mounting/unmounting the |
@goto-bus-stop Wondering if these components are ready to merge? I am especially curious about playing with |
55b3f0a
to
dfb9af7
Compare
Rebased. I think we'll try to get these in during September(?) @oyeanuj, |
* will. | ||
*/ | ||
class ReactDashboardPlugin extends DashboardPlugin { | ||
constructor (core, opts) { |
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.
I’m a little worried about doing this. Could we maybe add onRequestClose/hideModal callback into the Dashboard plugin itself? Otherwise its third-level of class nesting, which is not that bad, but starting to feel like a dangerous territory :)
whoops, pushed an |
Sure! Thanks. |
db76265
to
cdcb3f9
Compare
Trying to address the first TODO by making Dashboard auto-add provider plugins to itself if they did not do so explicitly, i.e.: uppy.use(GoogleDrive, { target: Dashboard }) // does not add target because no Dashboard plugin is known
uppy.use(Dashboard) // finds GoogleDrive plugin with `.opts.target===Dashboard` and adds it
uppy.use(Dropbox, { target: Dashboard }) // adds target in `mount()` like it always has This works with React components like: const DashboardPlugin = require('uppy/lib/plugins/Dashboard')
const Dashboard = require('uppy/lib/uppy-react/Dashboard')
const uppy = Uppy()
.use(GoogleDrive, { target: DashboardPlugin })
.run()
const Demo = () => (
<Dashboard uppy={uppy} />
) It's confusing unfortunately, because you have to I was also thinking of making Dashboard auto-add all Maybe to address the last point, we can add a |
src/plugins/Plugin.js
Outdated
@@ -95,6 +103,10 @@ module.exports = class Plugin { | |||
} | |||
} | |||
|
|||
focus () { | |||
return | |||
} |
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.
Do we need focus
for something after all?
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.
no, it's not being used anywhere. will remove, thanks!
1efcfb3
to
637b66f
Compare
src/uppy-react/DashboardModal.js
Outdated
DashboardModal.propTypes = { | ||
uppy: PropTypes.instanceOf(UppyCore).isRequired, | ||
open: PropTypes.bool, | ||
onRequestClose: PropTypes.func |
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.
onRequestClose
doesn't work yet
src/uppy-react/Wrapper.js
Outdated
|
||
UppyWrapper.propTypes = { | ||
uppy: PropTypes.instanceOf(UppyCore).isRequired, | ||
plugin: PropTypes.string.isRequired |
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.
Looking into making a custom proptype validator here to assert that the plugin exists
Trying a different approach to React components suggested by @arturi. The core of this approach is the The one change required in Uppy itself for this to work is to the (Still have to work out how to make the See the react-example for how this looks when used |
@goto-bus-stop Seeing this example made me think if you folks considered something (and rejected?) like below as a maybe more React way of using Uppy: <Uppy
files = {this.state.files}
onUploadSuccess = {this.handleUploadSuccess}
onUploadComplete = {this.handleUploadComplete}
>
<Tus10 endpoint = 'https://master.tus.io/files' />
<DashboardPlugin inline />
<DragDropPlugin locale = {localeStringObject} />
<ProgressBarPlugin />
<GoogleDrive target = {DashboardPlugin} host = 'https://server.uppy.io' />
</Uppy> And I'm curious if you tried it, what did you like/dislike about the approach? |
Briefly considered it, but there are some issues with that approach IMO: React is primarily a view framework so putting all of the upload logic in that place feels wrong to me. I don't like having non-UI things in a React render method. It's possible to do it like that, and probably fine in many cases, but I think in a redux-like architecture the uploading should be done in a middleware or an action somewhere. Or even just explicitly in a component lifecycle method, if not using Redux. Also, different UI plugins can mount to different places with Uppy, and it's not clear how that would work in React. In React, usually when you write We discussed the new approach in slack/hangouts on Monday and are now thinking of going back to the previous approach, where we have components for UI plugins. But instead of setting const DashboardPlugin = require('uppy/lib/plugins/Dashboard')
const Dashboard = require('uppy/lib/uppy-react/Dashboard')
const uppy = Uppy()
.use(GoogleDrive) // no target:
.run()
const Demo = () => (
<Dashboard
note="Select some stuff from Google Drive"
uppy={uppy}
plugins={['GoogleDrive']} />
) The downside is that you have to duplicate the 'GoogleDrive' name, but it's also nicely explicit, I think. |
@goto-bus-stop appreciate the detailed response 😄! I think your points are fair. I have to say though that I consider React to be more than a view framework with the idea of self-encapsulated components, so in that vein, the above idea that I proposed could make sense. Increasingly, a lot more components are being published that don't render anything by default - either act like an HoC or provide some functionality, like At the end of the day, for a more seamless and natural integration into a React-Redux environment, my wishlist would be that it either acts like a self-encapsulated black-box with hooks like in the idea above, or provides individual functions that a developer can use within their lifecycle functions, and/or action-creators, but not necessarily have any hidden black-boxiness to it (not to beat a dead horse, since I know we discussed that before with @hedgerh ). Thanks for being open to the idea! |
I think we should at least finish and publish the current proposal ☝️, and then maybe experiment with more-react-like individual components later. We do have |
@arturi 👍 As far as If you add And then this setup also allows you to have In the case of |
TODO:
|
If the dashboard is used in inline mode, the z-indexes are not necessary, and may even interfere. This is visible in the React demo, where the inline dashboard would appear on top of the overlay of the modal dashboard, instead of behind it.
This implements unmounting for the inline Dashboard component. To do that, the Dashboard plugin now has a method `uninstall`, which removes all listeners and removes the Dashboard element from the DOM. The `componentWillUnmount` handler removes the plugin from Uppy's internal plugin list. If this approach is the way to go, we'd add a method to core to remove plugins… I think in that case, it'd be good to add some concept of View Plugins, so that Acquirer plugins function like they do now but View plugins can be mounted and unmounted.
This is a different approach to React components suggested by @arturi. Instead of the components adding and removing plugins when mounting and unmounting, plugins are configured at the start, and the components act as slots for the plugins to actually mount in. Because all plugins are still being configured up front here, we don't have to change how the provider `target:` options work, they can install themselves into eg. the Dashboard plugin immediately. The core of this approach is the `UppyWrapper` component, which takes an Uppy instance and a plugin ID, and mounts the plugin inside itself. The other components only provide a default plugin ID. The one change required in Uppy itself for this to work is to the `mount()` functions: now, `mount()` configures `this.target` on the plugins. If plugins do not have a `target:` option, they do not mount on install. Some DOM code also had to be moved into the `mount()` function for the DragDrop component instead of staying in `install()`, but I think it makes sense. (Still have to work out how to make the `DashboardModal` component's `onRequestClose` option work.)
37b2242
to
c124f82
Compare
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.
Hot 🔥
This PR introduces three high-level React components that render a Dashboard or a DragDrop area for a given Uppy instance.
There's a plain
<Dashboard />
component, that renders the Dashboard using theinline
option. This is nice & flexible and works well to actually render the dashboard inline, but also to render it in a modal from some other library if one is in use in someone's project already.There's a
<DashboardModal />
component, that renders a Dashboard modal like react-less Uppy does. The difference is that it doesn't have atrigger
option, and instead has anopen
prop. This way it can be opened or closed depending on state held in React or Redux and controlled manually by eg. a button andsetState
. When the user clicks outside the modal, theonRequestClose
event is fired. The developer can then decide if and how the modal should be closed.There's a
<DrapDrop />
component, that renders theDrapDrop
plugin. There's not much more to this ATM. A bunch of event callbacks for hover and drop may be useful here. These events would be actually related to the DOM that's rendered, and not as much to the Uppy Core instance, like with the onProgress/onComplete events (see below).Todos:
target:
option which seems to have to be(?) a Dashboard plugin instance. This is difficult with how these React components work, since these add the Dashboard plugin inside the component. One way to work around this would be to have the Dashboard find the relevant acquirer plugins by itself (probably by iterating overUppy
s internal plugin list).Figure out the event callbacks game. Having onProgress/onComplete callbacks on the components might be useful, but these are also available as events on the Uppy instance, and having multiple ways to add them might be confusing. I'll play around with this a bit more in the Example to see how it feels to use the event emitter vs handlers on the React components.Just useuppy.on()
for eventsuninstall
method.defaultTabIcon/showProgressDetails options as props.locale
props to all components.<ProgressBar />
And a separate thought re: the DragDrop component: A good story with eg. React-DnD seems useful. If there's an Uppy method to add a browser File object (like you get from
input.files[0]
) that is likely enough, though, and wouldn't need anything React-specific.