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

Use vertical line as drop target indicator for plots #1935

Closed

Conversation

SkyLeite
Copy link

@SkyLeite SkyLeite commented Jun 21, 2022

Fixes #1604

This was a tricky one to do initially. My first idea was to simply change the DropTarget to a vertical line and change the dragged column to not be hidden, but that didn't work because the vertical line was still part of the table flow, meaning it would consume the entire width of a header.

My current approach passes the header element itself to DropTarget so it can render the header as well as the drop indicators.

Here's a video of how it looks: https://streamable.com/tgy6ew

TODO:

  • Fix not being able to drag a column to the first or last spot
  • Fix tests
  • Manually test in VSCode (I've been using Storybook for development)

Known issues:

  • Indicator looks thicker when the target is the first or last column
    This wasn't intended during development, and it happens as a quirk of the indicator appearing under the headers when it's sandwiched between two of them, making it look thinner. Personally I think this can be repurposed into a good thing, as the line being thicker serves as a more clear indicator in this case where the user is less likely to notice the indicator at all.

  • Invalid DOM tree
    Due to the way the indicator is placed in the DOM, it generates a DOM tree that fails react-dom's check as a div is not supposed to be a child of tr. I can't think of a way to get around this, as the indicator needs to be a child of the row to be appropriately placed.

  • Test should move a template plot from one type in another section of the same type and show two drop targets fails
    I need some guidance for this one. I couldn't find the scenario it describes in storybook, and it also seems like it would no longer be relevant with the new Drop Target mechanism for plots, but I'm not entirely sure.

@sroy3
Copy link
Contributor

sroy3 commented Jun 21, 2022

  • Test should move a template plot from one type in another section of the same type and show two drop targets fails
    I need some guidance for this one. I couldn't find the scenario it describes in storybook, and it also seems like it would no longer be relevant with the new Drop Target mechanism for plots, but I'm not entirely sure.

You modified the behaviour of drag and drop for other plots (Trends and Data Series), it should stay the same. If the test still fails after, this is shown here by the last drag in the video:

Screen.Recording.2022-06-21.at.7.10.42.PM.mov

@sroy3
Copy link
Contributor

sroy3 commented Jun 21, 2022

There is a little bug when dropping on an invalid target. The column will stay dark:

Screen.Recording.2022-06-21.at.7.15.48.PM.mov

Other than that, your implementation looks great. Great job! Getting around table behaviour is not an easy task.

Like I said previously, be careful not to change the behaviour of drag and drop elsewhere (checkpoints and template plots and column headers in the table) and the PR will be pretty solid after that.

@SkyLeite
Copy link
Author

@sroy3 Thank you for the feedback and help! I managed to fix both issues, and even added back the border that was missing for the drop target of Trends and Data Series. As such I'm marking this as ready for review :)

@SkyLeite SkyLeite marked this pull request as ready for review June 22, 2022 02:49
@rogermparent
Copy link
Contributor

  • nvalid DOM tree
    Due to the way the indicator is placed in the DOM, it generates a DOM tree that fails react-dom's check as a div is not supposed to be a child of tr. I can't think of a way to get around this, as the indicator needs to be a child of the row to be appropriately placed.

Does using divs with display: table instead of trs get around this? If not, maybe we can re-create the styles with flexbox much like how react-table does so with the experiments table.

Copy link
Contributor

@rogermparent rogermparent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work so far! I've found a couple tiny nitpicks from a first read-over, but it seems like all the big issues were caught by Stephanie and you've already handled them! I'll be taking another look early tomorrow.

Comment on lines 207 to 221
style={(id === draggedId && { display: 'none' }) || draggable.props.style}
style={
(id === draggedId && hideDragged && { display: 'none' }) ||
draggable.props.style
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it was like this before this PR, but is the value of this style prop not just a weirdly written ternary? i.e. id === draggedId && hideDragged ? {display: 'none'} : draggable.props.style

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure in this particular case, but I think our linter favours this syntax over ternaries using ? and : sometimes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I changed it to a ternary and ESLint seemed to be fine with it in this case.

const target = screen.getByTestId('drop-target')

// eslint-disable-next-line testing-library/no-node-access
expect(target.nextElementSibling?.id).toStrictEqual(plots[0].id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could these just compare the elements' references? i.e. expect(target.nextElementSibling).toBe(plots[0])

Not super important, just makes things a tiny bit simpler. This understandably apes the existing implementation, so it's not like it comes from nowhere.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I agree with comparing by reference instead of id 👍

className={styles.dropTargetIndicator}
data-testid="comparison-drop-target-indicator"
style={{
[direction.toLowerCase()]: -4
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the transformed value of an enum directly as a string makes me a little uneasy, though it's not like avoiding doing so makes the code that much more elegant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something I've seen done in Elm on cases like this, since in Elm you can't derive a string from an enum, is a function that explicitly returns a string from an enum value. I thought about doing the same thing here, but it gave me "early optimization" vibes. Does team have any ideas or precedents in the code base for tackling this?

Comment on lines 278 to 281
& > th {
display: block;
width: 100%;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still apply to anything? It seems like the drop target was turned into a div in another part of the code, and changing a th's display seems like it would cause a lot more chaos if it were applying.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. It is applying because the current implementation re-renders the dragged cell as the drop target, which in this case is a th. The display: block is indeed unnecessary, but the width: 100% is there so the cell expands to fill the drop target.

Copy link
Contributor

@sroy3 sroy3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drag and drop elsewhere is still not quite how it was before:

Screen.Recording.2022-06-22.at.9.06.41.AM.mov

There is a double border when the drop target is at the start:

Screen Shot 2022-06-22 at 9 07 51 AM

Did you try changing the style of the dragged element like the mock? Was it too complicated?

plots[1].id,
plots[2].id
])
const target = screen.getByTestId('drop-target')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little uneasy about changing a test for template plots drag and drop while the feature is meant to change only the comparison table.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. This should've been reverted in ba9c284 along with the other changes. It's done now.

@SkyLeite
Copy link
Author

@sroy3

The drag and drop elsewhere is still not quite how it was before:

It should be fine as of e2e8caf. Do you think the approach of special casing based on the hideDragged prop makes sense? I feel like it works fine for now, but I'm concerned about introducing technical debt since it's not a very flexible approach. How does the team usually handle these scenarios?

There is a double border when the drop target is at the start:

Fixed in 31aebcf. Initially I thought that was a nice touch, as I find the indicator a little hard to see when it's not between two elements, but after seeing what it looks like in your clip (Safari, I presume) I agree that it's not the best way to solve this.

Did you try changing the style of the dragged element like the mock? Was it too complicated?

I did initially, but only came across solutions like setting and re-setting the element's style on dragStart, which doesn't even seem to work on modern browsers anymore. However, after reading your comment I decided to take another look and found documentation for setDragImage on MDN and that seems to work great! It's not very elegant because we need to append an element off-screen to be used as the ghost image, but it's what MDN recommends so I'd say it's good enough. I've implemented it in 0887cc0

@SkyLeite
Copy link
Author

  • nvalid DOM tree
    Due to the way the indicator is placed in the DOM, it generates a DOM tree that fails react-dom's check as a div is not supposed to be a child of tr. I can't think of a way to get around this, as the indicator needs to be a child of the row to be appropriately placed.

Does using divs with display: table instead of trs get around this? If not, maybe we can re-create the styles with flexbox much like how react-table does so with the experiments table.

I managed to fix this in 42f369d. The solution boils down to changing the wrapper element back to a th, and re-creating the element that's rendered by DropTarget as a div. It solves both issues (div cannot be a child of tr and th cannot be a child of div) without having to recreate the table layout.

I also tried using divs with display: table instead of trs, but that triggered the same issue in that divs cannot be children of thead. Following this rabbit hole would mean getting rid of the table altogether and creating a div soup all the way down, so I figured it was best to drop it in favor of the current solution.

@SkyLeite
Copy link
Author

One more thing: I see a codeclimate issue from GitHub, but it seems I don't have access to the Details page to see what the issue is. Do we have a way of running these checks locally?

@SkyLeite SkyLeite requested review from sroy3 and rogermparent June 23, 2022 16:40
@mattseddon mattseddon closed this Jun 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve drag and drop feedback in plots comparison table
4 participants