Skip to content

Commit

Permalink
wip use mouse velocity to understand drag direction
Browse files Browse the repository at this point in the history
  • Loading branch information
gwwar committed Aug 16, 2021
1 parent ac2ab3d commit 1cf8d28
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 69 deletions.
27 changes: 26 additions & 1 deletion packages/block-editor/src/components/list-view/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
__experimentalTreeGridRow as TreeGridRow,
MenuGroup,
MenuItem,
__unstableUseMotionValue as useMotionValue,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { moreVertical } from '@wordpress/icons';
Expand Down Expand Up @@ -48,6 +49,7 @@ export default function ListViewBlock( {
moveItem,
dropItem,
listPosition,
parentId,
} ) {
const cellRef = useRef( null );
const [ isHovered, setIsHovered ] = useState( false );
Expand Down Expand Up @@ -91,6 +93,7 @@ export default function ListViewBlock( {
clientId,
dropContainer: block?.dropContainer ?? false,
dropSibling: block?.dropSibling ?? false,
parentId,
},
} );
}, [ listPosition, draggingId ] );
Expand Down Expand Up @@ -167,9 +170,29 @@ export default function ListViewBlock( {
dropItem();
};

const velocity = useMotionValue( 0 );
const onDrag = ( event, info ) => {
// When swapping items with a neighbor a positive translate value is moving down, and a
// negative value is moving up in the onViewportBoxUpdate callback.
//
// However, when skipping over items, we need mouse velocity to understand if the user is dragging up or down.
// This is because with the view box in the same position, the originPoint is modified and the translate value
// may flip it's sign.
//
// Velocity is not available in onViewportBoxUpdate, so we set this motion value here:
velocity.set( info.velocity.y );
};

const blockDrag = ( box, delta ) => {
if ( draggingId === clientId ) {
moveItem( block, listPosition, delta.y );
moveItem( {
block,
translate: delta.y.translate,
isLastChild: position === rowCount,
isFirstChild: position === 1,
velocity,
listPosition,
} );
}
};

Expand All @@ -191,8 +214,10 @@ export default function ListViewBlock( {
drag="y"
whileDrag={ { scale: 1.1 } }
onDragStart={ onDragStart }
onDrag={ onDrag }
onDragEnd={ onDragEnd }
onViewportBoxUpdate={ blockDrag }
layoutId={ clientId }
>
<TreeGridCell
className="block-editor-list-view-block__contents-cell"
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/src/components/list-view/branch.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export default function ListViewBranch( props ) {
moveItem={ moveItem }
dropItem={ dropItem }
listPosition={ nextPosition }
parentId={ parentBlockClientId }
/>
{ hasNestedBranch && isExpanded && (
<ListViewBranch
Expand Down
186 changes: 118 additions & 68 deletions packages/block-editor/src/components/list-view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,78 +40,83 @@ const expanded = ( state, action ) => {
}
};

function findCurrentPosition( tree, id, parentId = '' ) {
for ( let index = 0; index < tree.length; index++ ) {
const block = tree[ index ];
if ( block.clientId === id ) {
return { parentId, index, block, tree };
}
if ( block.innerBlocks && block.innerBlocks.length > 0 ) {
const match = findCurrentPosition(
block.innerBlocks,
id,
block.clientId
);
if ( match ) {
return match;
}
}
}
return false;
}

function removeItemFromTree( tree, id ) {
function removeItemFromTree( tree, id, parentId = '' ) {
const newTree = [];
let removeParentId = '';
for ( let index = 0; index < tree.length; index++ ) {
const block = tree[ index ];
if ( block.clientId !== id ) {
if ( block.innerBlocks.length > 0 ) {
const {
newTree: innerBlocks,
removeParentId: cRemoveParentId,
} = removeItemFromTree( block.innerBlocks, id, block.clientId );
newTree.push( {
...block,
innerBlocks: removeItemFromTree( block.innerBlocks, id ),
innerBlocks,
} );
removeParentId =
cRemoveParentId !== '' ? cRemoveParentId : removeParentId;
} else {
newTree.push( { ...block } );
}
} else {
removeParentId = parentId;
}
}
return newTree;
return { newTree, removeParentId };
}

function addItemToTree( tree, id, item, insertAfter = true ) {
function addItemToTree( tree, id, item, insertAfter = true, parentId = '' ) {
const newTree = [];
let targetIndex = -1;
let targetId = '';
for ( let index = 0; index < tree.length; index++ ) {
const block = tree[ index ];
if ( block.clientId === id ) {
targetId = parentId;
if ( insertAfter ) {
targetIndex = newTree.length + 1;
newTree.push( { ...block } );
newTree.push( { ...item } );
} else {
targetIndex = newTree.length;
newTree.push( { ...item } );
newTree.push( { ...block } );
}
} else if ( block.clientId !== id ) {
if ( block.innerBlocks.length > 0 ) {
const {
newTree: innerBlocks,
targetIndex: childTargetIndex,
targetId: childTargetId,
} = addItemToTree(
block.innerBlocks,
id,
item,
insertAfter,
block.clientId
);
newTree.push( {
...block,
innerBlocks: addItemToTree(
block.innerBlocks,
id,
item,
insertAfter
),
innerBlocks,
} );
targetIndex = Math.max( targetIndex, childTargetIndex );
targetId = childTargetId !== '' ? childTargetId : targetId;
} else {
newTree.push( { ...block } );
}
}
}
return newTree;
return { newTree, targetId, targetIndex };
}

const UP = 'up';
const DOWN = 'down';

// eslint-disable-next-line no-unused-vars
function findFirstValidPosition( positions, current, translate, moveDown ) {
//TODO: this works, but after skipping an item translate can no longer be used to indicate drag direction.
//TODO: add this back when implementing skipping over invalid items
const ITEM_HEIGHT = 36;
const iterate = moveDown ? 1 : -1;
let index = current + iterate;
Expand Down Expand Up @@ -211,65 +216,110 @@ export default function ListView( {
lastTarget.current = null;
}, [] );

const dropItem = () => {
const dropItem = async () => {
if ( ! lastTarget.current ) {
return;
}
const { targetPosition, clientId, movingDown } = lastTarget.current;
const targetId = targetPosition.clientId;
const target = findCurrentPosition(
removeItemFromTree( clientIdsTree, clientId ),
targetId
);
const current = findCurrentPosition( clientIdsTree, clientId );

const targetIndex = movingDown ? target.index + 1 : target.index;
setDropped( true );
moveBlocksToPosition(
const {
clientId,
originalParent,
targetId,
targetIndex,
} = lastTarget.current;
lastTarget.current = null;
await moveBlocksToPosition(
[ clientId ],
current.parentId,
target.parentId,
originalParent,
targetId,
targetIndex
);
lastTarget.current = null;
// TODO:
// - use cached representation while list view has focus (maybe after the first drag)
// - cache removal of the dragged item in tree
// - try storing parent positions on setPositions
// - see what performance of a flat representation looks like
//TODO: still need to find something more reliable to test if things have settled
timeoutRef.current = setTimeout( () => {
setDropped( false );
}, 200 );
};

const moveItem = ( block, listPosition, { translate } ) => {
const moveItem = ( {
block,
translate,
listPosition,
isLastChild,
isFirstChild,
velocity,
} ) => {
//TODO: support add to container
//TODO: support add to child container
//TODO: simplify state and code
//TODO: either constrain the drag area to the max number of items, or test if we're hovering over the midpoint of next targets
const { clientId } = block;
const ITEM_HEIGHT = 36;

const v = velocity?.get() ?? 0;
if ( v === 0 ) {
return;
}

const direction = v > 0 ? DOWN : UP;

if ( Math.abs( translate ) > ITEM_HEIGHT / 2 ) {
const movingDown = translate > 0;
const targetPosition = movingDown
? positions[ listPosition + 1 ]
: positions[ listPosition - 1 ];
const position = positions[ listPosition ];

// First, check to see if we should break out of a container block:
if (
position.parentId &&
( ( direction === UP && isFirstChild ) ||
( direction === DOWN && isLastChild ) )
) {
const {
newTree: treeWithoutDragItem,
removeParentId,
} = removeItemFromTree( clientIdsTree, clientId );
const { newTree, targetId, targetIndex } = addItemToTree(
treeWithoutDragItem,
position.parentId,
block,
direction === DOWN
);
lastTarget.current = {
clientId,
originalParent: removeParentId,
targetId,
targetIndex,
};
setTree( newTree );
return;
}

// Swap siblings
const targetPosition =
direction === DOWN
? positions[ listPosition + 1 ]
: positions[ listPosition - 1 ];

if ( targetPosition === undefined ) {
return;
}
lastTarget.current = {
clientId,
targetPosition,
movingDown,
};
const newTree = addItemToTree(
removeItemFromTree( clientIdsTree, clientId ),
targetPosition.clientId,
block,
movingDown
);
setTree( newTree );
if ( position.parentId === targetPosition.parentId ) {
//Sibling swap
const {
newTree: treeWithoutDragItem,
removeParentId,
} = removeItemFromTree( clientIdsTree, clientId );
const { newTree, targetIndex, targetId } = addItemToTree(
treeWithoutDragItem,
targetPosition.clientId,
block,
direction === DOWN
);
lastTarget.current = {
clientId,
originalParent: removeParentId,
targetId,
targetIndex,
};
setTree( newTree );
}
}
};

Expand Down

0 comments on commit 1cf8d28

Please sign in to comment.