Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Conversation

@jolyonruss
Copy link
Contributor

@jolyonruss jolyonruss commented Feb 17, 2017

I uploaded a video to Youtube to show it in action

Vertical splitting in action

In Container.js I did the following:

  • setup an state value isVertical
  • added an onResize event listener
  • update the isVertical when the window width is less than 500
  • pass an initialHeight value to SplitPane.js

In SplitPane.js I did the following:

  • setup the new props; isVertical & initialHeight
  • added height to the local state
  • update the state when isVertical changes in componentWillReceiveProps
  • updated the onMove method to handle x & y changes
  • in the render method switched a few values based on isVertical
  • added some new vertical specific styles, including a border bottom on LeftPane to make this clear where the split when the split is vertical

In DetailPane.js I did 1 thing:

  • added a light grey background to the header, so that when the split is vertical there's enough contrast to see where the handle/split is. Chrome Devtools' right/bottom pane has a grey tab at the top which helps with this. When the header was white it was very hard to see where the handle is.

@facebook-github-bot
Copy link

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.

@jolyonruss
Copy link
Contributor Author

Oh, didn't realise there was a CLA, have now signed 👍

@facebook-github-bot
Copy link

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

}

componentDidMount() {
window.addEventListener('resize', (e) => this.handleResize(e), false);
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@gaearon gaearon left a 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.

}

componentWillUnmount() {
window.removeEventListener('resize');
Copy link
Contributor

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).

Copy link
Contributor Author

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.

Copy link
Contributor

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.

super(props);

this.state = {
isVertical: (window.innerWidth < 500),
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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({
Copy link
Contributor

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
}

Copy link
Contributor

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%'});
}

@jolyonruss
Copy link
Contributor Author

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 handleResize(e) should this be Object, Event or SyntheticEvent? Here's a link to the specific line in Travis - https://travis-ci.org/facebook/react-devtools/builds/202606486#L565

@gaearon
Copy link
Contributor

gaearon commented Feb 17, 2017

Maybe SyntheticUIEvent will work.
http://www.saltycrane.com/blog/2016/06/flow-type-cheat-sheet/#lib/react.js

handleResizeTimeout(scope: Container, e: Event) {
resizeTimeout = null;

scope.setState({
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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

}

handleResize = (e: Event) => {
if (!resizeTimeout) {
Copy link
Contributor

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?

Copy link
Contributor

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?

Copy link
Contributor Author

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


onMove(x: number) {
componentWillReceiveProps(nextProps: Props) {
if (nextProps.isVertical !== this.props.isVertical) {
Copy link
Contributor

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.

@jolyonruss
Copy link
Contributor Author

Sorry about all the little fixes

@jolyonruss
Copy link
Contributor Author

jolyonruss commented Feb 19, 2017

@gaearon d'you have any idea why this check is failing?

 77:   handleResizeTimeout = () => {
       ^^^^^^^^^^^^^^^^^^^ Unexpected identifier

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

@jaredly
Copy link
Contributor

jaredly commented Feb 20, 2017

Actually could we circumvent all of this by just using a media query?

@jolyonruss
Copy link
Contributor Author

@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 media query.

Happy to hear suggestions on how this could work and refactor if there's a better way to solve this.

if (!resizeTimeout) {
resizeTimeout = setTimeout(this.handleResizeTimeout, 50);
}
}
Copy link
Contributor

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.

};

var IS_VERTICAL_BREAKPOINT = 500;
var resizeTimeout = null;
Copy link
Contributor

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.

Copy link
Contributor Author

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

extraTabs: {[key: string]: () => React$Element},
};
state: State;
resizeTimeout: number = null;
Copy link
Contributor

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.

@gaearon
Copy link
Contributor

gaearon commented Feb 21, 2017

I recommend to run Flow locally instead of waiting for CI.

@gaearon
Copy link
Contributor

gaearon commented Feb 24, 2017

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:

screen shot 2017-02-24 at 9 38 07 pm

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.
Let me know if you'd like to keep working on this!

Copy link
Contributor

@gaearon gaearon left a 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.

@jolyonruss
Copy link
Contributor Author

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.

moving: boolean,
width: number,
width: string,
height: string,
Copy link
Contributor

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

Copy link
Contributor Author

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

@jolyonruss
Copy link
Contributor Author

@gaearon flow check passes locally and I've fixed the three UI issues you listed above.

I swapped the type of width/height back to a number as per @jaredly's suggestion, which cleaned up the code and means I no long need to check when SplitPane receives new props in componentWillReceiveProps.

@gaearon
Copy link
Contributor

gaearon commented Mar 9, 2017

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:

I would expect the panels to start 50/50

cannot also work well with

I expect the opposite to happen. It's the tree panel that should grow or shrink by default.

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?

@jolyonruss
Copy link
Contributor Author

I haven't forgotten about this, just got busy - once I get some time it shouldn't take too long to sort.

@gaearon
Copy link
Contributor

gaearon commented Mar 27, 2017

Just checking in—do you think you’ll have some time to finish it off?

@jolyonruss
Copy link
Contributor Author

Hi Dan, thanks for the poke, just pushed another commit, flow check still passes.

Let me know if that resizing was what you're after.

@gaearon
Copy link
Contributor

gaearon commented Apr 19, 2017

This works great. 👍

@gaearon gaearon merged commit 4ebadbf into facebook:master Apr 19, 2017
@jolyonruss
Copy link
Contributor Author

😃

@gaearon
Copy link
Contributor

gaearon commented Apr 20, 2017

Noticed a small issue: #611.

@jolyonruss
Copy link
Contributor Author

jolyonruss commented Apr 20, 2017 via email

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants