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

Reorder blocks via drag & drop (v2. using editor dropzones). #4115

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
b62160a
Drag & Drop: Added functionality for reindexing blocks.
chriskmnds Dec 20, 2017
7723d70
Drag & Drop for blocks: Added drag listeners and styling for dragging…
chriskmnds Dec 20, 2017
51cd0f5
Drag & Drop for blocks: Updated drop logic to improve precision of th…
chriskmnds Dec 21, 2017
82e9886
Drag & Drop for blocks: Resolved conflicts with latest master.
chriskmnds Dec 22, 2017
d6df73d
Drag & Drop for blocks: Added z-index to block underlay used in dragg…
chriskmnds Dec 27, 2017
f38db58
Drag & Drop for blocks: Changed background color of block inset/underlay
chriskmnds Dec 27, 2017
1b04b5b
Drag & Drop for blocks: Fixed visible grayed inset area when dragging.
chriskmnds Dec 27, 2017
9f25b10
Drag & Drop for blocks: Removed unnecessary block container element.
chriskmnds Dec 27, 2017
0a23609
Drag & Drop for blocks: Updated cursor when hovering/dragging a
chriskmnds Dec 27, 2017
9f3b3af
Drag & Drop for blocks: Added z-index property to placeholder compone…
chriskmnds Dec 28, 2017
52ac1ff
Drag & Drop for blocks: Updated dragging inset margin to be effective…
chriskmnds Dec 28, 2017
c55ebc3
Merge branch 'master' into add/drag-n-drop-for-blocks--dropzone
chriskmnds Dec 29, 2017
6c7a913
Drag & Drop for blocks: Fixed issues that surfaced since merging late…
chriskmnds Dec 30, 2017
8dbc69e
Drag & Drop for blocks: Some cleanup - renamed 'underlay' to 'drag-in…
chriskmnds Dec 30, 2017
35f9923
Drag & Drop for blocks: Changed dataTransfer data type for moving blo…
chriskmnds Dec 30, 2017
9271649
Drag & Drop for blocks: Improved conditioning for onDrop handler.
chriskmnds Jan 4, 2018
8fdcd6e
Drag & Drop for blocks: Added a small margin between cursor and drag
chriskmnds Jan 4, 2018
1e2ae5e
Drag & Drop for blocks: Removed browser prefix for cursor style.
chriskmnds Jan 4, 2018
ec5052c
Drag & Drop for blocks: IE11 tweaks. Added conditional call to
chriskmnds Jan 4, 2018
62afc6d
Drag & Drop for blocks: Some styling cleanup.
chriskmnds Jan 5, 2018
8f13212
Drag & Drop for blocks: IE11/Safari tweaks.
chriskmnds Jan 6, 2018
883ab19
Drag & Drop for blocks: Updated logic for setting drag image.
chriskmnds Jan 7, 2018
6faceef
Drag & Drop for blocks: Updated drag image logic to be consistent across
chriskmnds Jan 9, 2018
4978348
Drag & Drop for blocks: Updated drag image shadows and styling.
chriskmnds Jan 13, 2018
b512271
Drag & Drop for blocks: Updated logic for controlling the visibility of
chriskmnds Jan 14, 2018
e2c2e61
Drag & Drop for blocks: Some cleanup after code review.
chriskmnds Jan 16, 2018
8436f21
Drag & Drop for blocks: Added more draggable handles to the block, in…
chriskmnds Jan 17, 2018
f131740
Drag & Drop for blocks: Some cleanup.
chriskmnds Jan 17, 2018
2599d42
Drag & Drop for blocks: Updated cursor to move/grab for the block-set…
chriskmnds Jan 18, 2018
6ad1c44
Drag & Drop for blocks: Cleanup from previous commit - set the cursor…
chriskmnds Jan 18, 2018
6fc98de
Drag & Drop for blocks: Updated dragging logic to move clone around i…
chriskmnds Jan 19, 2018
178cc60
Drag & Drop for blocks: Some cleanup - removed logging, and added a h…
chriskmnds Jan 19, 2018
c98ea9c
Drag & Drop for blocks: Cleared linting errors.
chriskmnds Jan 19, 2018
6c2bf61
Drag & Drop for blocks: Updated drag start/end handlers to stop propa…
chriskmnds Jan 21, 2018
ab68b11
Drag & Drop for blocks: Added a fake/invisible drag image to avoid
chriskmnds Jan 21, 2018
702eec3
Drag & Drop for blocks: Added transformation to the block clone if
chriskmnds Jan 22, 2018
7cbdfde
Drag & Drop for blocks: Some code cleanup.
chriskmnds Jan 22, 2018
b0f0b0e
Drag & Drop for blocks: Updated block-mover and dropdown components to
chriskmnds Jan 23, 2018
43ec29a
Drag & Drop for blocks: Some cleanup - inline comments, new lines, etc.
chriskmnds Jan 28, 2018
ec53f4f
Drag & Drop for blocks: Making sure the DOM is not updated prior to r…
chriskmnds Feb 9, 2018
2e0b13a
Drag & Drop for blocks: Moved drag init/end logic to a higher order
chriskmnds Feb 12, 2018
a2246f4
Drag & Drop for blocks: Cleanup.
chriskmnds Feb 12, 2018
db7a34b
Drag & Drop for blocks: Resolved conflicts with master. Several updat…
chriskmnds Feb 12, 2018
d8bedee
Drag & Drop for blocks: Cleanup. Removed leftover styles.
chriskmnds Feb 12, 2018
25485f9
Drag & Drop for blocks: Code cleanup.
chriskmnds Feb 13, 2018
05f2d10
Drag & Drop for blocks: Code cleanup. Further extracted drag init/end
chriskmnds Feb 13, 2018
f33d4db
Drag & Drop for blocks: Resolved conflicts.
chriskmnds Feb 13, 2018
a83c738
Drag & Drop for blocks: Updated cursor styling to grab for the new bl…
chriskmnds Feb 13, 2018
30ee308
Drag & Drop for blocks: Updates after PR feedback.
chriskmnds Feb 24, 2018
da001c9
Drag & Drop for blocks: Some cleanup after PR feedback. removed unnec…
chriskmnds Feb 24, 2018
b071cc4
Drag & Drop for blocks: Updated cursor styling after PR feedback to g…
chriskmnds Feb 24, 2018
46ed7a2
Drag & Drop for blocks: Updated index of reusable block edit panel to…
chriskmnds Feb 25, 2018
129dac9
Drag & Drop for blocks: Some cleanup after PR feedback. Renamed _stat…
chriskmnds Feb 25, 2018
d6897ce
Drag & Drop for blocks: Added support for inner blocks.
chriskmnds Mar 4, 2018
4e66a4a
Drag & Drop for blocks: Cleanup - lint errors.
chriskmnds Mar 4, 2018
bfb78c1
Drag & Drop for blocks: Updates after PR review. No longer passing drag
chriskmnds Mar 10, 2018
0a6a05d
Drag & Drop for blocks: Got rid of BLOCK_REORDER constant and selector.
chriskmnds Mar 10, 2018
f72e05d
Drag & Drop for blocks: Updates and cleanup after PR review.
chriskmnds Mar 10, 2018
f03e4ec
Drag & Drop for blocks: Cleanup. Removed commented out code.
chriskmnds Mar 10, 2018
650f613
Merge remote-tracking branch 'origin/master' into add/drag-n-drop-for…
youknowriad Mar 23, 2018
5c1d339
Drag And Drop: Refactor using a component instead of Higher Order Com…
youknowriad Mar 23, 2018
5c1613d
Clarify Drop Events
youknowriad Mar 23, 2018
4d3b8b8
Fix dragging between nested and not nested blocks
youknowriad Mar 23, 2018
d731edf
Fix Drag Area and Styling
youknowriad Mar 23, 2018
9624dca
More cleaning and fix insert position
youknowriad Mar 23, 2018
94dd1cc
Extract BlockDraggable Component
youknowriad Mar 23, 2018
7f01121
Destructre the uid block prop
youknowriad Mar 26, 2018
9982bcd
Clarify BlockDraggable classnames
youknowriad Mar 26, 2018
5623a71
Clarify Draggable Component Docs
youknowriad Mar 26, 2018
f48d65a
Avoid creating a new block object if the layout didn't change when mo…
youknowriad Mar 26, 2018
d415a1f
Less generic body className
youknowriad Mar 26, 2018
0eca12c
Bind BlockDropZone event handlers to avoid rerenderings
youknowriad Mar 26, 2018
e763e28
Remove Block UI on the cloned block element which fixes drag and scroll
youknowriad Mar 26, 2018
f19dcd0
Decrease the opacity of the cloned draggable
youknowriad Mar 26, 2018
48f8087
Updating ReactTextareraAutosize to fix a bug on initial load (long po…
youknowriad Mar 26, 2018
2759481
Remove iframes from the clone to fix embed's drag and drop
youknowriad Mar 26, 2018
51c1cef
Fix multiple dropzones showing up in Gallery block
youknowriad Mar 27, 2018
794dfd0
Merge remote-tracking branch 'origin/master' into add/drag-n-drop-for…
youknowriad Mar 30, 2018
4a817c5
Changes per review
youknowriad Mar 30, 2018
7dcc860
Fix scrolling when dragging
youknowriad Mar 30, 2018
b20b033
Remove useless styles
youknowriad Mar 30, 2018
c61c770
Merge remote-tracking branch 'origin/master' into add/drag-n-drop-for…
youknowriad Apr 2, 2018
d5daf30
Fix wide blocks scroll
youknowriad Apr 3, 2018
a648f67
Fix typos in documentation
youknowriad Apr 3, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions components/draggable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Draggable

`Draggable` is a Component that can wrap any element to make it draggable. When used, a cross-browser (including IE) customisable drag image is created. The component clones the specified element on drag-start and uses the clone as a drag image during drag-over. Discards the clone on drag-end.

## Props

The component accepts the following props:

### elementId

The HTML id of the element to clone on drag

- Type: `string`
- Required: Yes

### transferData

Arbitrary data object attached to the drag and drop event.

- Type: `Object`
- Required: Yes

### onDragStart

The function called when dragging starts.

- Type: `Function`
- Required: No
- Default: `noop`

### onDragEnd

The function called when dragging ends.

- Type: `Function`
- Required: No
- Default: `noop`
161 changes: 161 additions & 0 deletions components/draggable/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* External dependencies
*/
import { noop } from 'lodash';
import classnames from 'classnames';

/**
* WordPress Dependencies
*/
import { Component } from '@wordpress/element';

/**
* Internal Dependencies
*/
import withSafeTimeout from '../higher-order/with-safe-timeout';
import './style.scss';

const dragImageClass = 'components-draggable__invisible-drag-image';
const cloneWrapperClass = 'components-draggable__clone';
const cloneHeightTransformationBreakpoint = 700;
const clonePadding = 20;

class Draggable extends Component {
constructor() {
super( ...arguments );
this.onDragStart = this.onDragStart.bind( this );
this.onDragOver = this.onDragOver.bind( this );
this.onDragEnd = this.onDragEnd.bind( this );
}

componentWillUnmount() {
this.removeDragClone();
}

/**
* Removes the element clone, resets cursor, and removes drag listener.
* @param {Object} event The non-custom DragEvent.
*/
onDragEnd( event ) {
const { onDragEnd = noop } = this.props;
this.removeDragClone();
// Reset cursor.
document.body.classList.remove( 'is-dragging-components-draggable' );
event.preventDefault();

this.props.setTimeout( onDragEnd );
}

/*
* Updates positioning of element clone based on mouse movement during dragging.
* @param {Object} event The non-custom DragEvent.
*/
onDragOver( event ) {
this.cloneWrapper.style.top =
`${ parseInt( this.cloneWrapper.style.top, 10 ) + event.clientY - this.cursorTop }px`;
this.cloneWrapper.style.left =
`${ parseInt( this.cloneWrapper.style.left, 10 ) + event.clientX - this.cursorLeft }px`;

// Update cursor coordinates.
this.cursorLeft = event.clientX;
this.cursorTop = event.clientY;
}

/**
* - Clones the current element and spawns clone over original element.
* - Adds a fake temporary drag image to avoid browser defaults.
* - Sets transfer data.
* - Adds dragover listener.
* @param {Object} event The non-custom DragEvent.
* @param {string} elementId The HTML id of the element to be dragged.
* @param {Object} transferData The data to be set to the event's dataTransfer - to be accessible in any later drop logic.
*/
onDragStart( event ) {
const { elementId, transferData, onDragStart = noop } = this.props;
const element = document.getElementById( elementId );
Copy link
Contributor

Choose a reason for hiding this comment

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

Normally, in React, we'd use ref for this sort of thing. I understand that BlockListBlock is a bit different in that BlockDraggable is nested in the element itself via IgnoreNestedEvents, thus making it trickier to grab and pass the right ref. Edit: Actually, passing the ID string may make this component more easily reusable 👍.

If we stick to passing elementId, which seems fine, should we make sure that element exists?

if ( ! element ) {
event.preventDefault();
return;
}

// Set a fake drag image to avoid browser defaults. Remove from DOM
// right after. event.dataTransfer.setDragImage is not supported yet in
// IE, we need to check for its existence first.
if ( 'function' === typeof event.dataTransfer.setDragImage ) {
const dragImage = document.createElement( 'div' );
dragImage.id = `drag-image-${ elementId }`;
dragImage.classList.add( dragImageClass );
document.body.appendChild( dragImage );
event.dataTransfer.setDragImage( dragImage, 0, 0 );
this.props.setTimeout( () => {
document.body.removeChild( dragImage );
} );
}

event.dataTransfer.setData( 'text', JSON.stringify( transferData ) );

// Prepare element clone and append to element wrapper.
const elementRect = element.getBoundingClientRect();
const elementWrapper = element.parentNode;
const elementTopOffset = parseInt( elementRect.top, 10 );
const elementLeftOffset = parseInt( elementRect.left, 10 );
const clone = element.cloneNode( true );
clone.id = `clone-${ elementId }`;
this.cloneWrapper = document.createElement( 'div' );
this.cloneWrapper.classList.add( cloneWrapperClass );
this.cloneWrapper.style.width = `${ elementRect.width + ( clonePadding * 2 ) }px`;

if ( elementRect.height > cloneHeightTransformationBreakpoint ) {
// Scale down clone if original element is larger than 700px.
this.cloneWrapper.style.transform = 'scale(0.5)';
this.cloneWrapper.style.transformOrigin = 'top left';
// Position clone near the cursor.
this.cloneWrapper.style.top = `${ event.clientY - 100 }px`;
this.cloneWrapper.style.left = `${ event.clientX }px`;
} else {
// Position clone right over the original element (20px padding).
this.cloneWrapper.style.top = `${ elementTopOffset - clonePadding }px`;
this.cloneWrapper.style.left = `${ elementLeftOffset - clonePadding }px`;
}

// Hack: Remove iFrames as it's causing the embeds drag clone to freeze
[ ...clone.querySelectorAll( 'iframe' ) ].forEach( child => child.parentNode.removeChild( child ) );

this.cloneWrapper.appendChild( clone );
elementWrapper.appendChild( this.cloneWrapper );

// Mark the current cursor coordinates.
this.cursorLeft = event.clientX;
this.cursorTop = event.clientY;
// Update cursor to 'grabbing', document wide.
document.body.classList.add( 'is-dragging-components-draggable' );
document.addEventListener( 'dragover', this.onDragOver );

this.props.setTimeout( onDragStart );
}

removeDragClone() {
document.removeEventListener( 'dragover', this.onDragOver );
if ( this.cloneWrapper && this.cloneWrapper.parentNode ) {
// Remove clone.
this.cloneWrapper.parentNode.removeChild( this.cloneWrapper );
this.cloneWrapper = null;
}
}

render() {
const { children, className } = this.props;
return (
<div
className={ classnames( 'components-draggable', className ) }
onDragStart={ this.onDragStart }
onDragEnd={ this.onDragEnd }
draggable
>
{ children }
</div>
);
}
}

export default withSafeTimeout( Draggable );
20 changes: 20 additions & 0 deletions components/draggable/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
body.is-dragging-components-draggable {
cursor: move;/* Fallback for IE/Edge < 14 */
cursor: grabbing !important;
}

.components-draggable__invisible-drag-image {
position: fixed;
left: -1000px;
height: 50px;
width: 50px;
}

.components-draggable__clone {
position: fixed;
padding: 20px;
background: transparent;
pointer-events: none;
z-index: z-index( '.components-draggable__clone' );
opacity: 0.8;
}
47 changes: 47 additions & 0 deletions components/drop-zone/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# DropZone

`DropZone` is a Component creating a drop zone area taking the full size of its parent element. It supports dropping files, HTML content or any other HTML drop event. To work properly this components needs to be wrapped in a `DropZoneProvider`.

## Usage
Copy link
Member

Choose a reason for hiding this comment

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

Duplicate heading.


```jsx
import { DropZoneProvider, DropZone } from '@wordpress/components';

function MyComponent() {
return (
<DropZoneProvider>
<div>
Copy link
Member

Choose a reason for hiding this comment

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

Is the intermediate div necessary here?

<DropZone onDrop={ () => console.log( 'do something' ) } />
</div>
</DropZoneProvider>
);
}
```

## Props

The component accepts the following props:

### onFilesDrop

The function is called when dropping a file into the `DropZone`. It receives two arguments: an array of dropped files and a position object which the following shape: `{ x: 'left|right', y: 'top|bottom' }`. The position object indicates whether the drop event happened closer to the top or bottom edges and left or right ones.

- Type: `Function`
- Required: No
- Default: `noop`

### onHTMLDrop

The function is called when dropping a file into the `DropZone`. It receives two arguments: the HTML being dropped and a position object.

- Type: `Function`
- Required: No
- Default: `noop`

### onDrop

The function is generic drop handler called if the `onFilesDrop` or `onHTMLDrop` are not called. It receives two arguments: The drop `event` object and the position object.

- Type: `Function`
- Required: No
- Default: `noop`
27 changes: 3 additions & 24 deletions components/drop-zone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ class DropZone extends Component {
super( ...arguments );

this.setZoneNode = this.setZoneNode.bind( this );
this.onDrop = this.onDrop.bind( this );
this.onFilesDrop = this.onFilesDrop.bind( this );
this.onHTMLDrop = this.onHTMLDrop.bind( this );

this.state = {
isDraggingOverDocument: false,
Expand All @@ -36,34 +33,16 @@ class DropZone extends Component {
this.context.dropzones.add( {
element: this.zone,
updateState: this.setState.bind( this ),
onDrop: this.onDrop,
onFilesDrop: this.onFilesDrop,
onHTMLDrop: this.onHTMLDrop,
onDrop: this.props.onDrop,
onFilesDrop: this.props.onFilesDrop,
onHTMLDrop: this.props.onHTMLDrop,
} );
}

componentWillUnmount() {
this.context.dropzones.remove( this.zone );
}

onDrop() {
if ( this.props.onDrop ) {
this.props.onDrop( ...arguments );
}
}

onFilesDrop() {
if ( this.props.onFilesDrop ) {
this.props.onFilesDrop( ...arguments );
}
}

onHTMLDrop() {
if ( this.props.onHTMLDrop ) {
this.props.onHTMLDrop( ...arguments );
}
}

setZoneNode( node ) {
this.zone = node;
}
Expand Down
Loading