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

Support live drag and drop #16457

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 4 additions & 13 deletions packages/block-editor/src/components/block-draggable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@
* WordPress dependencies
*/
import { Draggable } from '@wordpress/components';
import { withSelect } from '@wordpress/data';

const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, index, onDragStart, onDragEnd } ) => {
const BlockDraggable = ( { children, clientId, blockElementId, onDragStart, onDragEnd } ) => {
const transferData = {
type: 'block',
srcIndex: index,
srcRootClientId: rootClientId,
srcClientId: clientId,
clientId,
};

return (
Expand All @@ -18,6 +15,7 @@ const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, ind
transferData={ transferData }
onDragStart={ onDragStart }
onDragEnd={ onDragEnd }
showClone={ false }
>
{
( { onDraggableStart, onDraggableEnd } ) => {
Expand All @@ -31,11 +29,4 @@ const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, ind
);
};

export default withSelect( ( select, { clientId } ) => {
const { getBlockIndex, getBlockRootClientId } = select( 'core/block-editor' );
const rootClientId = getBlockRootClientId( clientId );
return {
index: getBlockIndex( clientId, rootClientId ),
rootClientId,
};
} )( BlockDraggable );
export default BlockDraggable;
60 changes: 35 additions & 25 deletions packages/block-editor/src/components/block-drop-zone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,7 @@ class BlockDropZone extends Component {
this.onFilesDrop = this.onFilesDrop.bind( this );
this.onHTMLDrop = this.onHTMLDrop.bind( this );
this.onDrop = this.onDrop.bind( this );
}

getInsertIndex( position ) {
const { clientId, rootClientId, getBlockIndex } = this.props;
if ( clientId !== undefined ) {
const index = getBlockIndex( clientId, rootClientId );
return position.y === 'top' ? index : index + 1;
}
this.onDragOver = this.onDragOver.bind( this );
}

onFilesDrop( files, position ) {
Expand All @@ -83,31 +76,41 @@ class BlockDropZone extends Component {
}
}

onDrop( event, position ) {
const { rootClientId: dstRootClientId, clientId: dstClientId, getClientIdsOfDescendants, getBlockIndex } = this.props;
const { srcRootClientId, srcClientId, srcIndex, type } = parseDropEvent( event );
onDrop( event ) {
this.moveBlock( parseDropEvent( event ) );
}

onDragOver( event ) {
if ( event.type !== 'default' ) {
return;
}
this.moveBlock( event.data );
Copy link
Member

Choose a reason for hiding this comment

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

Calling moveBlock results in a call to this.props.moveBlockToPosition being made, this really modes the block in the store and creates an undo level. We need to make sure all these undo actions are aggregated, otherwise, when moving a block the user may create lots of undesired undo levels.

}

moveBlock( data ) {
const {
rootClientId: dstRootClientId,
clientId: dstClientId,
getClientIdsOfDescendants,
getBlockIndex,
getBlockRootClientId,
} = this.props;

const { clientId, type } = data;

const isBlockDropType = ( dropType ) => dropType === 'block';
const isSameLevel = ( srcRoot, dstRoot ) => {
// Note that rootClientId of top-level blocks will be undefined OR a void string,
// so we also need to account for that case separately.
return ( srcRoot === dstRoot ) || ( ! srcRoot === true && ! dstRoot === true );
};
const isSameBlock = ( src, dst ) => src === dst;
const isSrcBlockAnAncestorOfDstBlock = ( src, dst ) => getClientIdsOfDescendants( [ src ] ).some( ( id ) => id === dst );

if ( ! isBlockDropType( type ) ||
isSameBlock( srcClientId, dstClientId ) ||
isSrcBlockAnAncestorOfDstBlock( srcClientId, dstClientId || dstRootClientId ) ) {
isSameBlock( clientId, dstClientId ) ||
isSrcBlockAnAncestorOfDstBlock( clientId, dstClientId || dstRootClientId ) ) {
return;
}

const dstIndex = dstClientId ? getBlockIndex( dstClientId, dstRootClientId ) : undefined;
const positionIndex = this.getInsertIndex( position );
// If the block is kept at the same level and moved downwards,
// subtract to account for blocks shifting upward to occupy its old position.
const insertIndex = dstIndex && srcIndex < dstIndex && isSameLevel( srcRootClientId, dstRootClientId ) ? positionIndex - 1 : positionIndex;
this.props.moveBlockToPosition( srcClientId, srcRootClientId, insertIndex );
const dstIndex = getBlockIndex( dstClientId, dstRootClientId );
const srcRootClientId = getBlockRootClientId( clientId );
this.props.moveBlockToPosition( clientId, srcRootClientId, dstIndex );
}

render() {
Expand All @@ -126,6 +129,7 @@ class BlockDropZone extends Component {
onFilesDrop={ this.onFilesDrop }
onHTMLDrop={ this.onHTMLDrop }
onDrop={ this.onDrop }
onDragOver={ this.onDragOver }
/>
</MediaUploadCheck>
);
Expand Down Expand Up @@ -156,11 +160,17 @@ export default compose(
};
} ),
withSelect( ( select, { rootClientId } ) => {
const { getClientIdsOfDescendants, getTemplateLock, getBlockIndex } = select( 'core/block-editor' );
const {
getClientIdsOfDescendants,
getTemplateLock,
getBlockIndex,
getBlockRootClientId,
} = select( 'core/block-editor' );
return {
isLockedAll: getTemplateLock( rootClientId ) === 'all',
getClientIdsOfDescendants,
getBlockIndex,
getBlockRootClientId,
};
} ),
withFilters( 'editor.BlockDropZone' )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@

&.is-close-to-bottom {
background: none;
border-bottom: 3px solid theme(primary);
border: none;
}

&.is-close-to-top,
&.is-appender.is-close-to-top,
&.is-appender.is-close-to-bottom {
background: none;
border-top: 3px solid theme(primary);
border-bottom: none;
border: none;
}
}
68 changes: 41 additions & 27 deletions packages/components/src/draggable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import { noop } from 'lodash';
import { Component } from '@wordpress/element';
import { withSafeTimeout } from '@wordpress/compose';

/**
* Internal dependencies
*/
import { withDropZoneProvider } from '../drop-zone/provider';

const dragImageClass = 'components-draggable__invisible-drag-image';
const cloneWrapperClass = 'components-draggable__clone';
const cloneHeightTransformationBreakpoint = 700;
Expand Down Expand Up @@ -70,7 +75,13 @@ class Draggable extends Component {
* @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 {
elementId,
transferData,
onDragStart = noop,
showClone = true,
dropZoneProvider,
} = this.props;
const element = document.getElementById( elementId );
if ( ! element ) {
event.preventDefault();
Expand All @@ -91,37 +102,40 @@ class Draggable extends Component {
} );
}

dropZoneProvider.setDragData( transferData );
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 ) {
if ( showClone ) {
// 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 {
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`;
}
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 ) );
// 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 );
this.cloneWrapper.appendChild( clone );
elementWrapper.appendChild( this.cloneWrapper );
}

// Mark the current cursor coordinates.
this.cursorLeft = event.clientX;
Expand Down Expand Up @@ -159,4 +173,4 @@ class Draggable extends Component {
}
}

export default withSafeTimeout( Draggable );
export default withSafeTimeout( withDropZoneProvider( Draggable ) );
25 changes: 8 additions & 17 deletions packages/components/src/drop-zone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
import { noop } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -13,21 +14,9 @@ import { Component, createRef } from '@wordpress/element';
* Internal dependencies
*/
import Dashicon from '../dashicon';
import { DropZoneConsumer } from './provider';
import { withDropZoneProvider } from './provider';

const DropZone = ( props ) => (
<DropZoneConsumer>
{ ( { addDropZone, removeDropZone } ) => (
<DropZoneComponent
addDropZone={ addDropZone }
removeDropZone={ removeDropZone }
{ ...props }
/>
) }
</DropZoneConsumer>
);

class DropZoneComponent extends Component {
class DropZone extends Component {
constructor() {
super( ...arguments );

Expand All @@ -37,6 +26,8 @@ class DropZoneComponent extends Component {
onDrop: this.props.onDrop,
onFilesDrop: this.props.onFilesDrop,
onHTMLDrop: this.props.onHTMLDrop,
onUniversalDrop: this.props.onUniversalDrop || noop,
onDragOver: this.props.onDragOver || noop,
setState: this.setState.bind( this ),
};
this.state = {
Expand All @@ -50,11 +41,11 @@ class DropZoneComponent extends Component {
componentDidMount() {
// Set element after the component has a node assigned in the DOM
this.dropZone.element = this.dropZoneElement.current;
this.props.addDropZone( this.dropZone );
this.props.dropZoneProvider.addDropZone( this.dropZone );
}

componentWillUnmount() {
this.props.removeDropZone( this.dropZone );
this.props.dropZoneProvider.removeDropZone( this.dropZone );
}

render() {
Expand Down Expand Up @@ -95,4 +86,4 @@ class DropZoneComponent extends Component {
}
}

export default DropZone;
export default withDropZoneProvider( DropZone );
Loading