@gechiui/block-editor
Version:
268 lines (244 loc) • 7.25 kB
JavaScript
/**
* GeChiUI dependencies
*/
import {
cloneBlock,
findTransform,
getBlockTransforms,
pasteHandler,
} from '@gechiui/blocks';
import { useDispatch, useSelect } from '@gechiui/data';
import { getFilesFromDataTransfer } from '@gechiui/dom';
/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
/** @typedef {import('@gechiui/element').GCSyntheticEvent} GCSyntheticEvent */
/**
* Retrieve the data for a block drop event.
*
* @param {GCSyntheticEvent} event The drop event.
*
* @return {Object} An object with block drag and drop data.
*/
export function parseDropEvent( event ) {
let result = {
srcRootClientId: null,
srcClientIds: null,
srcIndex: null,
type: null,
blocks: null,
};
if ( ! event.dataTransfer ) {
return result;
}
try {
result = Object.assign(
result,
JSON.parse( event.dataTransfer.getData( 'gc-blocks' ) )
);
} catch ( err ) {
return result;
}
return result;
}
/**
* A function that returns an event handler function for block drop events.
*
* @param {string} targetRootClientId The root client id where the block(s) will be inserted.
* @param {number} targetBlockIndex The index where the block(s) will be inserted.
* @param {Function} getBlockIndex A function that gets the index of a block.
* @param {Function} getClientIdsOfDescendants A function that gets the client ids of descendant blocks.
* @param {Function} moveBlocksToPosition A function that moves blocks.
* @param {Function} insertBlocks A function that inserts blocks.
* @param {Function} clearSelectedBlock A function that clears block selection.
* @return {Function} The event handler for a block drop event.
*/
export function onBlockDrop(
targetRootClientId,
targetBlockIndex,
getBlockIndex,
getClientIdsOfDescendants,
moveBlocksToPosition,
insertBlocks,
clearSelectedBlock
) {
return ( event ) => {
const {
srcRootClientId: sourceRootClientId,
srcClientIds: sourceClientIds,
type: dropType,
blocks,
} = parseDropEvent( event );
// If the user is inserting a block
if ( dropType === 'inserter' ) {
clearSelectedBlock();
const blocksToInsert = blocks.map( ( block ) =>
cloneBlock( block )
);
insertBlocks(
blocksToInsert,
targetBlockIndex,
targetRootClientId,
true,
null
);
}
// If the user is moving a block
if ( dropType === 'block' ) {
const sourceBlockIndex = getBlockIndex( sourceClientIds[ 0 ] );
// If the user is dropping to the same position, return early.
if (
sourceRootClientId === targetRootClientId &&
sourceBlockIndex === targetBlockIndex
) {
return;
}
// If the user is attempting to drop a block within its own
// nested blocks, return early as this would create infinite
// recursion.
if (
sourceClientIds.includes( targetRootClientId ) ||
getClientIdsOfDescendants( sourceClientIds ).some(
( id ) => id === targetRootClientId
)
) {
return;
}
const isAtSameLevel = sourceRootClientId === targetRootClientId;
const draggedBlockCount = sourceClientIds.length;
// If the block is kept at the same level and moved downwards,
// subtract to take into account that the blocks being dragged
// were removed from the block list above the insertion point.
const insertIndex =
isAtSameLevel && sourceBlockIndex < targetBlockIndex
? targetBlockIndex - draggedBlockCount
: targetBlockIndex;
moveBlocksToPosition(
sourceClientIds,
sourceRootClientId,
targetRootClientId,
insertIndex
);
}
};
}
/**
* A function that returns an event handler function for block-related file drop events.
*
* @param {string} targetRootClientId The root client id where the block(s) will be inserted.
* @param {number} targetBlockIndex The index where the block(s) will be inserted.
* @param {boolean} hasUploadPermissions Whether the user has upload permissions.
* @param {Function} updateBlockAttributes A function that updates a block's attributes.
* @param {Function} canInsertBlockType A function that returns checks whether a block type can be inserted.
* @param {Function} insertBlocks A function that inserts blocks.
*
* @return {Function} The event handler for a block-related file drop event.
*/
export function onFilesDrop(
targetRootClientId,
targetBlockIndex,
hasUploadPermissions,
updateBlockAttributes,
canInsertBlockType,
insertBlocks
) {
return ( files ) => {
if ( ! hasUploadPermissions ) {
return;
}
const transformation = findTransform(
getBlockTransforms( 'from' ),
( transform ) =>
transform.type === 'files' &&
canInsertBlockType( transform.blockName, targetRootClientId ) &&
transform.isMatch( files )
);
if ( transformation ) {
const blocks = transformation.transform(
files,
updateBlockAttributes
);
insertBlocks( blocks, targetBlockIndex, targetRootClientId );
}
};
}
/**
* A function that returns an event handler function for block-related HTML drop events.
*
* @param {string} targetRootClientId The root client id where the block(s) will be inserted.
* @param {number} targetBlockIndex The index where the block(s) will be inserted.
* @param {Function} insertBlocks A function that inserts blocks.
*
* @return {Function} The event handler for a block-related HTML drop event.
*/
export function onHTMLDrop(
targetRootClientId,
targetBlockIndex,
insertBlocks
) {
return ( HTML ) => {
const blocks = pasteHandler( { HTML, mode: 'BLOCKS' } );
if ( blocks.length ) {
insertBlocks( blocks, targetBlockIndex, targetRootClientId );
}
};
}
/**
* A React hook for handling block drop events.
*
* @param {string} targetRootClientId The root client id where the block(s) will be inserted.
* @param {number} targetBlockIndex The index where the block(s) will be inserted.
*
* @return {Object} An object that contains the event handlers `onDrop`, `onFilesDrop` and `onHTMLDrop`.
*/
export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) {
const hasUploadPermissions = useSelect(
( select ) => select( blockEditorStore ).getSettings().mediaUpload,
[]
);
const {
canInsertBlockType,
getBlockIndex,
getClientIdsOfDescendants,
} = useSelect( blockEditorStore );
const {
insertBlocks,
moveBlocksToPosition,
updateBlockAttributes,
clearSelectedBlock,
} = useDispatch( blockEditorStore );
const _onDrop = onBlockDrop(
targetRootClientId,
targetBlockIndex,
getBlockIndex,
getClientIdsOfDescendants,
moveBlocksToPosition,
insertBlocks,
clearSelectedBlock
);
const _onFilesDrop = onFilesDrop(
targetRootClientId,
targetBlockIndex,
hasUploadPermissions,
updateBlockAttributes,
canInsertBlockType,
insertBlocks
);
const _onHTMLDrop = onHTMLDrop(
targetRootClientId,
targetBlockIndex,
insertBlocks
);
return ( event ) => {
const files = getFilesFromDataTransfer( event.dataTransfer );
const html = event.dataTransfer.getData( 'text/html' );
if ( files.length ) {
_onFilesDrop( files );
} else if ( html ) {
_onHTMLDrop( html );
} else {
_onDrop( event );
}
};
}