Skip to content

Commit

Permalink
Transform Blocks (#28453)
Browse files Browse the repository at this point in the history
* WC: WIP: Adding transform block ability with no filters. Also refactored options picker for block settings to have options in one object for better filtering and separated out props to the three different types.

* Moving getPossibleBLockTransformations to block menu so that we can tell if there are no valid transformation options and if so, not pop up transform option.

* Adding filter hook to modify transform.isMatch for each registered block depending on if they are allowed to transform.

* Separating out transform logic into transforms.native.js. Adding all block transformation categories in.

* Removing extraneous transforms.native.js file. Adding a fix for transforming group -> column block that resulted in crash.

* Adding ' blocks' to end of transformations which split one block into multiple.

Co-authored-by: Wendy Chen <[email protected]>
  • Loading branch information
illusaen and Wendy Chen authored Apr 7, 2021
1 parent 3041158 commit 666cbe8
Show file tree
Hide file tree
Showing 32 changed files with 646 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class BlockList extends Component {
renderFooterAppender: this.props.renderFooterAppender,
renderAppender: this.props.renderAppender,
onDeleteBlock: this.props.onDeleteBlock,
contentStyle: this.props.contentstyle,
contentStyle: this.props.contentStyle,
};
this.renderItem = this.renderItem.bind( this );
this.renderBlockListFooter = this.renderBlockListFooter.bind( this );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,31 +31,37 @@ import { store as noticesStore } from '@wordpress/notices';
*/
import { getMoversSetup } from '../block-mover/mover-description';
import { store as blockEditorStore } from '../../store';
import BlockTransformationsMenu from '../block-switcher/block-transformations-menu';

const BlockActionsMenu = ( {
onDelete,
isStackedHorizontally,
wrapBlockSettings,
wrapBlockMover,
openGeneralSidebar,
onMoveDown,
onMoveUp,
isFirst,
isLast,
// Select
blockTitle,
isEmptyDefaultBlock,
anchorNodeRef,
canInsertBlockType,
getBlocksByClientId,
isEmptyDefaultBlock,
isFirst,
isLast,
rootClientId,
selectedBlockClientId,
selectedBlockPossibleTransformations,
// Dispatch
createSuccessNotice,
duplicateBlock,
removeBlocks,
onMoveDown,
onMoveUp,
openGeneralSidebar,
pasteBlock,
canInsertBlockType,
rootClientId,
removeBlocks,
// Passed in
anchorNodeRef,
isStackedHorizontally,
onDelete,
wrapBlockMover,
wrapBlockSettings,
} ) => {
const [ clipboard, setCurrentClipboard ] = useState( getClipboard() );
const pickerRef = useRef();
const blockActionsMenuPickerRef = useRef();
const blockTransformationMenuPickerRef = useRef();
const moversOptions = { keys: [ 'icon', 'actionTitle' ] };
const clipboardBlock = clipboard && rawHandler( { HTML: clipboard } )[ 0 ];
const isPasteEnabled =
Expand All @@ -69,134 +75,137 @@ const BlockActionsMenu = ( {
},
} = getMoversSetup( isStackedHorizontally, moversOptions );

const deleteOption = {
id: 'deleteOption',
label: __( 'Remove block' ),
value: 'deleteOption',
separated: true,
disabled: isEmptyDefaultBlock,
};

const settingsOption = {
id: 'settingsOption',
label: __( 'Block settings' ),
value: 'settingsOption',
};

const backwardButtonOption = {
id: 'backwardButtonOption',
label: backwardButtonTitle,
value: 'backwardButtonOption',
disabled: isFirst,
};

const forwardButtonOption = {
id: 'forwardButtonOption',
label: forwardButtonTitle,
value: 'forwardButtonOption',
disabled: isLast,
};

const copyButtonOption = {
id: 'copyButtonOption',
label: __( 'Copy block' ),
value: 'copyButtonOption',
};

const cutButtonOption = {
id: 'cutButtonOption',
label: __( 'Cut block' ),
value: 'cutButtonOption',
};

const pasteButtonOption = {
id: 'pasteButtonOption',
label: __( 'Paste block after' ),
value: 'pasteButtonOption',
};

const duplicateButtonOption = {
id: 'duplicateButtonOption',
label: __( 'Duplicate block' ),
value: 'duplicateButtonOption',
};

const options = compact( [
wrapBlockMover && backwardButtonOption,
wrapBlockMover && forwardButtonOption,
wrapBlockSettings && settingsOption,
copyButtonOption,
cutButtonOption,
isPasteEnabled && pasteButtonOption,
duplicateButtonOption,
deleteOption,
] );

function onPasteBlock() {
if ( ! clipboard ) {
return;
}

pasteBlock( rawHandler( { HTML: clipboard } )[ 0 ] );
}

function onPickerSelect( value ) {
switch ( value ) {
case deleteOption.value:
const allOptions = {
settings: {
id: 'settingsOption',
label: __( 'Block settings' ),
value: 'settingsOption',
onSelect: openGeneralSidebar,
},
backwardButton: {
id: 'backwardButtonOption',
label: backwardButtonTitle,
value: 'backwardButtonOption',
disabled: isFirst,
onSelect: onMoveUp,
},
forwardButton: {
id: 'forwardButtonOption',
label: forwardButtonTitle,
value: 'forwardButtonOption',
disabled: isLast,
onSelect: onMoveDown,
},
delete: {
id: 'deleteOption',
label: __( 'Remove block' ),
value: 'deleteOption',
separated: true,
disabled: isEmptyDefaultBlock,
onSelect: () => {
onDelete();
createSuccessNotice(
// translators: displayed right after the block is removed.
__( 'Block removed' )
);
break;
case settingsOption.value:
openGeneralSidebar();
break;
case forwardButtonOption.value:
onMoveDown();
break;
case backwardButtonOption.value:
onMoveUp();
break;
case copyButtonOption.value:
const copyBlock = getBlocksByClientId( selectedBlockClientId );
const serializedBlock = serialize( copyBlock );
},
},
transformButton: {
id: 'transformButtonOption',
label: __( 'Transform block…' ),
value: 'transformButtonOption',
onSelect: () => {
if ( blockTransformationMenuPickerRef.current ) {
blockTransformationMenuPickerRef.current.presentPicker();
}
},
},
copyButton: {
id: 'copyButtonOption',
label: __( 'Copy block' ),
value: 'copyButtonOption',
onSelect: () => {
const serializedBlock = serialize(
getBlocksByClientId( selectedBlockClientId )
);
setCurrentClipboard( serializedBlock );
setClipboard( serializedBlock );
createSuccessNotice(
// translators: displayed right after the block is copied.
__( 'Block copied' )
);
break;
case cutButtonOption.value:
const cutBlock = getBlocksByClientId( selectedBlockClientId );
setClipboard( serialize( cutBlock ) );
},
},
cutButton: {
id: 'cutButtonOption',
label: __( 'Cut block' ),
value: 'cutButtonOption',
onSelect: () => {
setClipboard(
serialize( getBlocksByClientId( selectedBlockClientId ) )
);
removeBlocks( selectedBlockClientId );
createSuccessNotice(
// translators: displayed right after the block is cut.
__( 'Block cut' )
);
break;
case pasteButtonOption.value:
},
},
pasteButton: {
id: 'pasteButtonOption',
label: __( 'Paste block after' ),
value: 'pasteButtonOption',
onSelect: () => {
onPasteBlock();
createSuccessNotice(
// translators: displayed right after the block is pasted.
__( 'Block pasted' )
);
break;
case duplicateButtonOption.value:
},
},
duplicateButton: {
id: 'duplicateButtonOption',
label: __( 'Duplicate block' ),
value: 'duplicateButtonOption',
onSelect: () => {
duplicateBlock();
createSuccessNotice(
// translators: displayed right after the block is duplicated.
__( 'Block duplicated' )
);
break;
},
},
};

const options = compact( [
wrapBlockMover && allOptions.backwardButton,
wrapBlockMover && allOptions.forwardButton,
wrapBlockSettings && allOptions.settings,
selectedBlockPossibleTransformations.length &&
allOptions.transformButton,
allOptions.copyButton,
allOptions.cutButton,
isPasteEnabled && allOptions.pasteButton,
allOptions.duplicateButton,
allOptions.delete,
] );

function onPasteBlock() {
if ( ! clipboard ) {
return;
}

pasteBlock( rawHandler( { HTML: clipboard } )[ 0 ] );
}

function onPickerSelect( value ) {
const selectedItem = options.find( ( item ) => item.value === value );
selectedItem.onSelect();
}

function onPickerPresent() {
if ( pickerRef.current ) {
pickerRef.current.presentPicker();
if ( blockActionsMenuPickerRef.current ) {
blockActionsMenuPickerRef.current.presentPicker();
}
}

Expand All @@ -223,7 +232,7 @@ const BlockActionsMenu = ( {
} }
/>
<Picker
ref={ pickerRef }
ref={ blockActionsMenuPickerRef }
options={ options }
onChange={ onPickerSelect }
destructiveButtonIndex={ options.length }
Expand All @@ -234,6 +243,14 @@ const BlockActionsMenu = ( {
// translators: %s: block title e.g: "Paragraph".
title={ sprintf( __( '%s block options' ), blockTitle ) }
/>
<BlockTransformationsMenu
anchorNodeRef={ anchorNodeRef }
blockTitle={ blockTitle }
pickerRef={ blockTransformationMenuPickerRef }
possibleTransformations={ selectedBlockPossibleTransformations }
selectedBlock={ getBlocksByClientId( selectedBlockClientId ) }
selectedBlockClientId={ selectedBlockClientId }
/>
</>
);
};
Expand All @@ -245,6 +262,7 @@ export default compose(
getBlockRootClientId,
getBlockOrder,
getBlockName,
getBlockTransformItems,
getBlock,
getBlocksByClientId,
getSelectedBlockClientIds,
Expand All @@ -271,16 +289,24 @@ export default compose(
const isEmptyDefaultBlock =
isExactlyOneBlock && isDefaultBlock && isEmptyContent;

const selectedBlockClientId = getSelectedBlockClientIds();
const selectedBlock = getBlocksByClientId( selectedBlockClientId );
const selectedBlockPossibleTransformations = getBlockTransformItems(
selectedBlock,
rootClientId
);

return {
blockTitle,
canInsertBlockType,
currentIndex: firstIndex,
getBlocksByClientId,
isEmptyDefaultBlock,
isFirst: firstIndex === 0,
isLast: lastIndex === blockOrder.length - 1,
rootClientId,
blockTitle,
isEmptyDefaultBlock,
getBlocksByClientId,
selectedBlockClientId: getSelectedBlockClientIds(),
currentIndex: firstIndex,
canInsertBlockType,
selectedBlockClientId,
selectedBlockPossibleTransformations,
};
} ),
withDispatch(
Expand All @@ -300,15 +326,14 @@ export default compose(
const { createSuccessNotice } = dispatch( noticesStore );

return {
onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ),
onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ),
openGeneralSidebar: () =>
openGeneralSidebar( 'edit-post/block' ),
createSuccessNotice,
duplicateBlock() {
return duplicateBlocks( clientIds );
},
removeBlocks,
onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ),
onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ),
openGeneralSidebar: () =>
openGeneralSidebar( 'edit-post/block' ),
pasteBlock: ( clipboardBlock ) => {
const canReplaceBlock = isUnmodifiedDefaultBlock(
getBlock( getBlockSelectionEnd() )
Expand All @@ -330,6 +355,7 @@ export default compose(
replaceBlocks( clientIds, clipboardBlock );
}
},
removeBlocks,
};
}
),
Expand Down
Loading

0 comments on commit 666cbe8

Please sign in to comment.