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

Drop zone: rewrite with hooks (useDropZone) #19514

Merged
merged 14 commits into from
Jan 10, 2020
Merged

Conversation

ellatrix
Copy link
Member

@ellatrix ellatrix commented Jan 8, 2020

Description

This PR removes the last remaining control element from the block DOM. It looks like it offers a further 10% performance improvement on typing.

I created a useDropZone hook alternative that allows you to create a drop zone without needing to create an additional element. It works by passing the reference to the target element to the hook.

How has this been tested?

  • Move blocks by drag and drop.
  • Drop a block on an appender.
  • Drop files between blocks.
  • Drop files on the media placeholder.

Screenshots

Screenshot 2020-01-09 at 11 57 20

Types of changes

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR. .

@ellatrix ellatrix added [Type] Performance Related to performance efforts [Type] Code Quality Issues or PRs that relate to code quality labels Jan 8, 2020
@ellatrix ellatrix marked this pull request as ready for review January 8, 2020 22:33
@ellatrix
Copy link
Member Author

ellatrix commented Jan 9, 2020

Performance test:


 + 69f98a0db...e8bf37d4e try/drop-zone-hooks -> try/drop-zone-hooks (forced update)
Ellas-MacBook-Pro:gutenberg ella$ npm run test-performance

> [email protected] test-performance /Users/ella/gutenberg
> wp-scripts test-e2e --config packages/e2e-tests/jest.performance.config.js

 PASS  packages/e2e-tests/specs/performance/performance.test.js (23.948s)
  Performance
    ✓ 1000 paragraphs (20627ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        24.067s, estimated 26s
Ran all test suites.

Average time to load: 5149ms
Average time to DOM content load: 4832ms
Average time to type character: 40.655ms
Slowest time to type character: 127ms
Fastest time to type character: 29ms
		
Ellas-MacBook-Pro:gutenberg ella$ git checkout master
M	packages/e2e-tests/config/setup-test-framework.js
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
Ellas-MacBook-Pro:gutenberg ella$ npm run test-performance

> [email protected] test-performance /Users/ella/gutenberg
> wp-scripts test-e2e --config packages/e2e-tests/jest.performance.config.js

 PASS  packages/e2e-tests/specs/performance/performance.test.js (25.898s)
  Performance
    ✓ 1000 paragraphs (23110ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        26.01s
Ran all test suites.

Average time to load: 5531ms
Average time to DOM content load: 5143ms
Average time to type character: 45.255ms
Slowest time to type character: 125ms
Fastest time to type character: 33ms
		
Ellas-MacBook-Pro:gutenberg ella$ 

@ellatrix ellatrix changed the title Drop zone: rewrite with hooks Drop zone: rewrite with hooks (useDropZone) Jan 9, 2020
Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

  • When you start a new post, dropping a file on the post placeholder doesn't work.

getClientIdsOfDescendants,
hasUploadPermissions,
isLockedAll,
} = useSelect( selector, [ rootClientId ] );
Copy link
Contributor

Choose a reason for hiding this comment

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

What pattern did we settle on? I thought we decided to use inline functions?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought we settled on allowing it. It's especially handy if variable names are clashing. It's also an optimisation if useSelect has no dependencies, you can move it out of the component. Here we depend on rootClientId though.

packages/block-editor/src/components/block-list/block.js Outdated Show resolved Hide resolved
import { DropZoneConsumer } from './provider';
import { DropZoneConsumer, Context } from './provider';

export function useDropZone( { element, onFilesDrop, onHTMLDrop, onDrop, isDisabled } ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add some JSDocs and types :p cc @aduth

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we move this to @wordpress/compose like all other hooks? I guess the problem is that it's dependent on the DropzoneProvider so I'm on the fence. I know the provider is necessary for performance reasons, other than that it would be good to kill.

Copy link
Member Author

Choose a reason for hiding this comment

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

I haven't looked into the DropzoneProvider, but as long as we need that, this seems like the right place. We can prefix it with __unstable if we want to keep our options open.

Copy link
Member Author

Choose a reason for hiding this comment

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

I prefixed it. I will have another look separately to move it and look into the provider.

{ shouldRenderDropzone && <BlockDropZone
clientId={ clientId }
rootClientId={ rootClientId }
/> }
Copy link
Contributor

Choose a reason for hiding this comment

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

The main problem with this PR I think is that the dropzone is smaller than before. In master for paragraphs, its size is "37px" with this pr it is "30px". They both suffer from the same issue, there's a space (margin) between blocks where you can't drop blocks. It's is more visible in this PR because of the smaller size though.

Copy link
Member Author

Choose a reason for hiding this comment

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

@youknowriad I fixed the issue entirely by rewriting useBlockDropZone to work on BlockList instead of BlockListBlock. Now there are no longer "dead zones" between the blocks where you can't drop.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is cool, I'll test a bit later.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! I think the error might be gone too, but let me know if it isn't. :)

@youknowriad
Copy link
Contributor

youknowriad commented Jan 10, 2020

After a few drag and drops, I started having this JS error while dragging blocks.

Capture d’écran 2020-01-10 à 9 57 04 AM

@ellatrix
Copy link
Member Author

@youknowriad Thanks, I'll fix the issues!

const offset = y - rect.top;
const target = Array.from( element.current.children ).find( ( blockEl ) => {
return blockEl.offsetTop + ( blockEl.offsetHeight / 2 ) > offset;
} );
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to take into consideration the blocks that are laid out horizontally (I'm not sure it was the case before)

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not possible in master right now, so I'd prefer to tackle this in a separate PR to keep this small and contained.

let x = detail.clientX;
let y = detail.clientY;

if ( ! hoveredDropZone.withExactPosition ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we just use another property like "coordinates" instead of hacking two different types in the same property?

Copy link
Member Author

Choose a reason for hiding this comment

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

I personally wouldn't, because it will make the component rerender much more, but I'm fine with it either way. We could remove the top/bottom calculation since they were specific for blocks.

Copy link
Member Author

Choose a reason for hiding this comment

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

Although is-close-to-top is part of DropZone's API. Wouldn't mind removing it if it's ok, nothing in Gutenberg is now using these classes.

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 mind dropping it too if not used. (dev note)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I removed them. :)

@youknowriad
Copy link
Contributor

Usability wise, this feels way better than master. I'd appreciate some testing from @jasmussen

@ellatrix ellatrix added the Needs Dev Note Requires a developer note for a major WordPress release cycle label Jan 10, 2020
@jasmussen jasmussen self-requested a review January 10, 2020 14:44
Copy link
Contributor

@jasmussen jasmussen left a comment

Choose a reason for hiding this comment

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

Trusting the code review to Riad and others, from an experience point of view this is substantially better. Before:

between

appender

placeholder

After:

between new

appender new

placeholder new

@ellatrix
Copy link
Member Author

Thanks a lot for the reviews! Let's merge and iterate. I think this improves things in many ways.

@ellatrix ellatrix merged commit 1e0a6ce into master Jan 10, 2020
@ellatrix ellatrix deleted the try/drop-zone-hooks branch January 10, 2020 15:39
@chrisvanpatten
Copy link
Member

I am so excited for this PR — the old drag-and-drop behavior was quite finicky but this looks like a tremendous improvement!

@aduth
Copy link
Member

aduth commented Jan 10, 2020

  • When you start a new post, dropping a file on the post placeholder doesn't work.

I don't know if this was addressed, but I'm pretty sure it behaved this way (i.e. didn't allow drop on a new post) prior to the changes here.

@ellatrix
Copy link
Member Author

I double checked that you can drop on the appender, but right, you can't drop on the rest of the document.

@ellatrix ellatrix added this to the Gutenberg 7.3 milestone Jan 20, 2020
@jorgefilipecosta jorgefilipecosta mentioned this pull request Feb 12, 2020
23 tasks
@plaidpowered
Copy link

The dropzone behavior is so much more stable with this change, but I have to ask, is the editor.BlockDropZone coming back? Or is there some way to attach an equivalent behavior hook?

@ellatrix
Copy link
Member Author

I think we need to see how media upload can best be filtered. I think the drop zone is the wrong place for a hook because (1) there’s more places where media can be uploaded/inserted (paste, placeholder…) and (2) the drop zone handles more than just media, it also handles internal block dropping. @plaidpowered What were you using the hook for?

@plaidpowered
Copy link

plaidpowered commented Feb 28, 2020

Thanks for getting back to me!
I was essentially using the filter to take over the dropzone behavior and add more functionality - for instance, give the user the option to swap two blocks, rather than move/insert .

Thankfully no production code has been broken, but as the project was in progress, I wanted to ensure my work is future-proof. I may have to go at it from a different direction.

@ellatrix
Copy link
Member Author

@plaidpowered Would be good to see it in action with a GIF for example, to better understand what you mean. Let me know if there's anything in particular you'd like to filter after this change. Any props in useBlockDropZone?

@plaidpowered
Copy link

Actually, perhaps even better than a filter would be to add an action at the end of onDrop, something like

wp.hooks.doAction( 'editor.BlockDropZone.afterOnDrop', src, dst, event );

For my specific use case, that's literally all I'd need. Of course, this wouldn't filter onDrop behavior, simply allow actions to be performed after blocks are rearranged, and I imagine there would be multiple use cases beyond my own. For instance, if a developer wants to validate whether a block is allowed to be placed in a certain location.

I really appreciate you discussing this with me, thanks!

@ellatrix
Copy link
Member Author

What are you doing after the blocks have rearranged? I don't get what this "option to swap two blocks" is. :)

@plaidpowered
Copy link

plaidpowered commented Feb 28, 2020

It depends on a number of factors, some of which I haven't figured out yet - the idea is that if you have block a and block b, and drag block a onto block b, block a will take the location of block b and block b will be moved to the former location of block a.

I'm still working out the UI for this. Ideally I want a dedicated area in block b's dropzone, and if the block a is dropped there, the replacement/swap occurs instead of the standard insert behavior. But if I can't make that work with the present codebase, I'll present some kind of dialog to the user asking if they want to Insert (before the dst block), Swap (dst with src), or Replace (dst is deleted, src takes its place).

Admittedly, the old editor.BlockDropZone filter wasn't very helpful in reaching this goal, essentially I had to completely replace dropZone.prototype.onDrop from within the addFilter callback. That never felt like an effective long term solution.

@ellatrix
Copy link
Member Author

The dragging and dropping of blocks already swaps the blocks, no?

@plaidpowered
Copy link

I suppose it does if you are dragging and dropping blocks within the same parent container, and the two blocks are adjacent to each other, but if you are moving blocks between two different containers, the present behavior simply inserts the dragged block to before the destination dropzone. My intention is to have the dragged block and destination block trade places, even if they are in different containers.

@plaidpowered
Copy link

plaidpowered commented Feb 28, 2020

Hi again @ellatrix
I was able to accomplish my goal through a different method, that avoids using actions/filters - using the dragover/dragend window events, and the wp.data store. Here's a GIF of what I was talking about.

3qtfh9

Anyway, thanks for your help! It may still be valuable to include some filters in this functionality. Filters are the best. :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Code Quality Issues or PRs that relate to code quality [Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants