-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Implemented vertical splitting into SplitPane #541
Conversation
|
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at [email protected]. Thanks! If you are contributing on behalf of someone else (eg your employer): the individual CLA is not sufficient - use https://developers.facebook.com/opensource/cla?type=company instead. Contact [email protected] if you have any questions. |
|
Oh, didn't realise there was a CLA, have now signed 👍 |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
frontend/Container.js
Outdated
| } | ||
|
|
||
| componentDidMount() { | ||
| window.addEventListener('resize', (e) => this.handleResize(e), false); |
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.
Please throttle this event listener. Something like once every 100ms should be fine.
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.
Yeah, that was why I asked about adding a resize :) wasn't sure if you'd want to throttle or not. 100 feels a little slow, 50 seems comparable to the Devtool's element tab.
gaearon
left a comment
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.
Looks great! Left a few comments.
frontend/Container.js
Outdated
| } | ||
|
|
||
| componentWillUnmount() { | ||
| window.removeEventListener('resize'); |
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 don't think you're unsubscribing since you're not passing the listener.
Instead, I suggest declaring the listener as a class property:
handleResize = (e) => {
// ...
}Then you can window.addEventListener('resize', this.handleResize) and window.removeEventListener('resize', this.handleResize).
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 actually prefer this syntax, but have had trouble getting it to work in other people's projects before, a simple fix.
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.
It requires class-properties plugin which we use.
frontend/Container.js
Outdated
| super(props); | ||
|
|
||
| this.state = { | ||
| isVertical: (window.innerWidth < 500), |
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.
Let's extract a constant since it's used in two places and we might forget one without the other by mistake.
Alternatively you can make a computeState(window) function and use it in both places.
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.
D'you mean in /agent/consts.js? Or as a local constant? Or somewhere completely different?
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.
Here in the file. Just so two 500s are one.
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.
And just to prove the point it's actually three 500s :)
Have named it IS_VERTICAL_BREAKPOINT
|
|
||
| onMove(x: number) { | ||
| componentWillReceiveProps(nextProps: Props) { | ||
| this.setState({ |
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.
Looks like there's no need to run this code if nextProps.isVertical hasn't changed.
if (nextProps.isVertical !== this.props.isVertical) {
// put your code here
}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.
It is dangerous to depend on this.state when calling setState.
And I think you don't need to. Maybe you can do something like this?
if (nextProps.isVertical && !this.props.isVertical) {
this.setState({width: '100%'});
} else if (!nextProps.isVertical && this.props.isVertical) {
this.setState({height: '100%'});
}|
I had a look at the failing tests, there are 9 errors, most are fixed by changing the width/height props to a String, since I'm using 100% now which of course isn't a Number. It's also saying I need to add an annotation to |
|
Maybe |
frontend/Container.js
Outdated
| handleResizeTimeout(scope: Container, e: Event) { | ||
| resizeTimeout = null; | ||
|
|
||
| scope.setState({ |
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.
Let's just use the property syntax so you can safely use this here.
| } | ||
|
|
||
| componentWillUnmount() { | ||
| window.removeEventListener('resize', this.handleResize); |
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.
Please also cancel the interval here.
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've added the clearTimeout in here
frontend/Container.js
Outdated
| } | ||
|
|
||
| handleResize = (e: Event) => { | ||
| if (!resizeTimeout) { |
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.
React synthetic events get reused. I don't think it's safe to pass them around. Could you read target here and then pass just the target?
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.
Actually isn't target always going to be the window?
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, it's always going to be window
frontend/SplitPane.js
Outdated
|
|
||
| onMove(x: number) { | ||
| componentWillReceiveProps(nextProps: Props) { | ||
| if (nextProps.isVertical !== this.props.isVertical) { |
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.
The outer condition is now unnecessary because inner conditions always make it true.
|
Sorry about all the little fixes |
|
@gaearon d'you have any idea why this check is failing? It's my first time using Flow, so not sure if I've misses something obvious. Link to Travis - https://travis-ci.org/facebook/react-devtools/builds/203209111#L568 |
|
Actually could we circumvent all of this by just using a |
|
@jaredly cause we're using an onMove to handle the dragging and need to track different directions based on the the layout horizontal or vertical, I can't quite see how this could work with with just a Happy to hear suggestions on how this could work and refactor if there's a better way to solve this. |
frontend/Container.js
Outdated
| if (!resizeTimeout) { | ||
| resizeTimeout = setTimeout(this.handleResizeTimeout, 50); | ||
| } | ||
| } |
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 think you might need to add semicolon at the end of property initializers. This probably breaks some the (potentially old) Flow version we're using.
handleStuff = () => {
// ...
};
handleOtherStuff = () => {
// ...
};You don't need to do it for regular methods though.
frontend/Container.js
Outdated
| }; | ||
|
|
||
| var IS_VERTICAL_BREAKPOINT = 500; | ||
| var resizeTimeout = null; |
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 should still store it on the instance.
If Flow complains, you need to explcitly declare it:
resizeTimeout?: number;
Just like we declare props and state types inside the class.
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.
The linter didn't like the ?: syntax, so I just set it's initial value to null
frontend/Container.js
Outdated
| extraTabs: {[key: string]: () => React$Element}, | ||
| }; | ||
| state: State; | ||
| resizeTimeout: number = null; |
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.
If it's a number it can't be null. number | null or ?number would work though.
|
I recommend to run Flow locally instead of waiting for CI. |
|
I played with it, and I think there a few UI issues I'd like to see addressed. By default, as soon as I make the window narrow enough, it collapses like this: I would expect the panels to start 50/50 or with some other reasonable default split size. Next, if I resize the split point the way I like it, but later resize the window to be wide and back, it doesn't remember my previous vertical split point. If you do the same in Chrome, you'll notice it remembers the previous setting in the vertical mode. We don't have to persist it, but we should have the same behavior at least within a single session. Finally, while this may not be a problem of this PR in particular, I noticed that when I resize it to be wider (or taller), the tree view stays the same while the secondary panel grows. I expect the opposite to happen. It's the tree panel that should grow or shrink by default. This is also how Chrome panel works. I think these are three important improvements, and I'd be excited to merge a PR fixing them. |
gaearon
left a comment
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.
Please see feedback above.
|
Thanks for the feedback, sorry it's taken a while to reply, I was on holiday last week. Firstly, I really appreciate the help with fixing the Flow issues I was seeing. I'd like to continue working on this and fixing the UI issues you listed above, hopefully it shouldn't take too long and it'll be merge-able by the end of the week. |
frontend/SplitPane.js
Outdated
| moving: boolean, | ||
| width: number, | ||
| width: string, | ||
| height: string, |
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.
using toString and then parseInt is definitely a smell here.
You can just have width & height be numbers, and forget about setting them to 100%. Then down in render, you just do e.g. props.isVertical ? '100%' : this.state.width
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.
Thanks for pushing for this, I knew it was a bit of a smell when I was writing it, but my focus was on getting the flow check to pass.
Swapping the type back to a number has simplified the code and made it a lot less smelly
|
This looks great. There’s just one final change I’d like to make. When I wrote last feedback I haven’t fully thought it through. It was slightly contradictory:
cannot also work well with
Instead of 50/50, it should start with the detail pane being of reasonable fixed default width (or height). Right now, if you don’t drag it but resize the window, it keeps being 50/50. But if you touch and slightly move it first, then it stays fixed. I’d like it to stay fixed from the very beginning. Could you do this? |
|
I haven't forgotten about this, just got busy - once I get some time it shouldn't take too long to sort. |
|
Just checking in—do you think you’ll have some time to finish it off? |
|
Hi Dan, thanks for the poke, just pushed another commit, Let me know if that resizing was what you're after. |
|
This works great. 👍 |
|
😃 |
|
Noticed a small issue: #611. |
|
Oh, yeah - that shouldn't be doing that!
Will take a look as soon as I can, unless someone else wants to pick it up
as a good first bug.
|

I uploaded a video to Youtube to show it in action
In Container.js I did the following:
In SplitPane.js I did the following:
In DetailPane.js I did 1 thing: