Skip to content

Commit

Permalink
[Mobile] - Copy, Cut, Paste, Duplicate blocks (#23110)
Browse files Browse the repository at this point in the history
* RNMobile - Copy / Duplicate functionality and Menu Inserter refactor: Move items rendering to its own component

* RNMobile - Store selector fix after merge

* RNMobile - Cut feature

* RNMobile - Verify if block can be inserted from clipboard

* RNMobile - Cut functionality

* RNMobile - Remove unused async

* Add Notice view for Copy/Cut blocks (#23113)

* Add notices

* Remove duplicate function

* Fix errors

* Update NoticeList

* Fix default val

* Bind on notice hidden

* Add timer and fix formatting

* Prevent NoticeList from appearing inside nested blocks

* Update animation

* RNMobile - Notice - Fix animation

* Add onDimensionsChange listener

* RNMobile - FloatingToolbar - Disable pointer Events if its not visible

* Make zIndex values get effective

* Update packages/components/src/notice/index.native.js

Co-authored-by: Gerardo Pacheco <[email protected]>

* Update packages/components/src/notice/list.native.js

Co-authored-by: Gerardo Pacheco <[email protected]>

* Simplify reducer

Co-authored-by: Gerardo Pacheco <[email protected]>

* RNMobile - Fix lint error and change the default value of the Notice component animation

* Mobile - Notice component - Blur effect for iOS and change styling for Android

* Mobile - Add react-native-blur

* Mobile - Mock @react-native-community/blur

* Mobile - Update Podfile lock

Co-authored-by: Pinar Olguc <[email protected]>
  • Loading branch information
geriux and pinarol committed Jun 30, 2020
1 parent 12a5977 commit dface0b
Show file tree
Hide file tree
Showing 19 changed files with 706 additions and 95 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,6 @@ export class BlockList extends Component {
ListEmptyComponent={ ! isReadOnly && this.renderEmptyList }
ListFooterComponent={ this.renderBlockListFooter }
/>

{ this.shouldShowInnerBlockAppender() && (
<View
style={ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ import { partial, first, castArray, last, compact } from 'lodash';
* WordPress dependencies
*/
import { ToolbarButton, Picker } from '@wordpress/components';
import { getBlockType, getDefaultBlockName } from '@wordpress/blocks';
import {
getBlockType,
getDefaultBlockName,
serialize,
rawHandler,
createBlock,
isUnmodifiedDefaultBlock,
} from '@wordpress/blocks';
import { __, sprintf } from '@wordpress/i18n';
import { withDispatch, withSelect } from '@wordpress/data';
import { withInstanceId, compose } from '@wordpress/compose';
import { moreHorizontalMobile, trash, cog } from '@wordpress/icons';
import { moreHorizontalMobile } from '@wordpress/icons';
import { useRef } from '@wordpress/element';
/**
* Internal dependencies
Expand All @@ -31,12 +38,19 @@ const BlockActionsMenu = ( {
blockTitle,
isEmptyDefaultBlock,
anchorNodeRef,
getBlocksByClientId,
selectedBlockClientId,
updateClipboard,
createInfoNotice,
duplicateBlock,
removeBlocks,
pasteBlock,
isPasteEnabled,
} ) => {
const pickerRef = useRef();
const moversOptions = { keys: [ 'icon', 'actionTitle' ] };

const {
icon: { backward: backwardButtonIcon, forward: forwardButtonIcon },
actionTitle: {
backward: backwardButtonTitle,
forward: forwardButtonTitle,
Expand All @@ -47,7 +61,6 @@ const BlockActionsMenu = ( {
id: 'deleteOption',
label: __( 'Remove block' ),
value: 'deleteOption',
icon: trash,
separated: true,
disabled: isEmptyDefaultBlock,
};
Expand All @@ -56,29 +69,54 @@ const BlockActionsMenu = ( {
id: 'settingsOption',
label: __( 'Block settings' ),
value: 'settingsOption',
icon: cog,
};

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

const forwardButtonOption = {
id: 'forwardButtonOption',
label: forwardButtonTitle,
value: 'forwardButtonOption',
icon: forwardButtonIcon,
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' ),
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,
] );

Expand All @@ -96,6 +134,37 @@ const BlockActionsMenu = ( {
case backwardButtonOption.value:
onMoveUp();
break;
case copyButtonOption.value:
const copyBlock = getBlocksByClientId( selectedBlockClientId );
updateClipboard( serialize( copyBlock ) );
createInfoNotice(
// translators: displayed right after the block is copied.
__( 'Block copied' )
);
break;
case cutButtonOption.value:
const cutBlock = getBlocksByClientId( selectedBlockClientId );
updateClipboard( serialize( cutBlock ) );
removeBlocks( selectedBlockClientId );
createInfoNotice(
// translators: displayed right after the block is cut.
__( 'Block cut' )
);
break;
case pasteButtonOption.value:
pasteBlock();
createInfoNotice(
// translators: displayed right after the block is pasted.
__( 'Block pasted' )
);
break;
case duplicateButtonOption.value:
duplicateBlock();
createInfoNotice(
// translators: displayed right after the block is duplicated.
__( 'Block duplicated' )
);
break;
}
}

Expand Down Expand Up @@ -131,6 +200,7 @@ const BlockActionsMenu = ( {
destructiveButtonIndex={ options.length }
disabledButtonIndices={ disabledButtonIndices }
hideCancelButton={ Platform.OS !== 'ios' }
leftAlign={ true }
anchor={
anchorNodeRef ? findNodeHandle( anchorNodeRef ) : undefined
}
Expand All @@ -149,7 +219,11 @@ export default compose(
getBlockOrder,
getBlockName,
getBlock,
getBlocksByClientId,
getSelectedBlockClientIds,
canInsertBlockType,
} = select( 'core/block-editor' );
const { getClipboard } = select( 'core/editor' );
const normalizedClientIds = castArray( clientIds );
const block = getBlock( normalizedClientIds );
const blockName = getBlockName( normalizedClientIds );
Expand All @@ -171,25 +245,82 @@ export default compose(
const isEmptyDefaultBlock =
isExactlyOneBlock && isDefaultBlock && isEmptyContent;

const clipboard = getClipboard();
const clipboardBlock =
clipboard && rawHandler( { HTML: clipboard } )[ 0 ];
const isPasteEnabled =
clipboardBlock &&
canInsertBlockType( clipboardBlock.name, rootClientId );

return {
isFirst: firstIndex === 0,
isLast: lastIndex === blockOrder.length - 1,
rootClientId,
blockTitle,
isEmptyDefaultBlock,
getBlocksByClientId,
selectedBlockClientId: getSelectedBlockClientIds(),
currentIndex: firstIndex,
isPasteEnabled,
clipboardBlock,
};
} ),
withDispatch( ( dispatch, { clientIds, rootClientId } ) => {
const { moveBlocksDown, moveBlocksUp } = dispatch(
'core/block-editor'
);
const { openGeneralSidebar } = dispatch( 'core/edit-post' );
withDispatch(
(
dispatch,
{ clientIds, rootClientId, currentIndex, clipboardBlock },
{ select }
) => {
const {
moveBlocksDown,
moveBlocksUp,
duplicateBlocks,
removeBlocks,
insertBlock,
replaceBlocks,
} = dispatch( 'core/block-editor' );
const { openGeneralSidebar } = dispatch( 'core/edit-post' );
const { updateClipboard, createInfoNotice } = dispatch(
'core/editor'
);
const { getBlockSelectionEnd, getBlock } = select(
'core/block-editor'
);

return {
onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ),
onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ),
openGeneralSidebar: () => openGeneralSidebar( 'edit-post/block' ),
};
} ),
return {
onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ),
onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ),
openGeneralSidebar: () =>
openGeneralSidebar( 'edit-post/block' ),
updateClipboard,
createInfoNotice,
duplicateBlock() {
return duplicateBlocks( clientIds );
},
removeBlocks,
pasteBlock: () => {
const canReplaceBlock = isUnmodifiedDefaultBlock(
getBlock( getBlockSelectionEnd() )
);

if ( ! canReplaceBlock ) {
const insertedBlock = createBlock(
clipboardBlock.name,
clipboardBlock.attributes,
clipboardBlock.innerBlocks
);

insertBlock(
insertedBlock,
currentIndex + 1,
rootClientId
);
} else {
replaceBlocks( clientIds, clipboardBlock );
}
},
};
}
),
withInstanceId
)( BlockActionsMenu );
94 changes: 94 additions & 0 deletions packages/block-editor/src/components/inserter/menu-item.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* External dependencies
*/
import { View, TouchableHighlight, Text } from 'react-native';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { Icon } from '@wordpress/components';
import { withPreferredColorScheme } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import styles from './style.scss';

class MenuItem extends Component {
constructor() {
super( ...arguments );

this.onPress = this.onPress.bind( this );
}

onPress() {
const { onSelect, item } = this.props;
onSelect( item );
}

render() {
const {
getStylesFromColorScheme,
item,
itemWidth,
maxWidth,
} = this.props;

const modalIconWrapperStyle = getStylesFromColorScheme(
styles.modalIconWrapper,
styles.modalIconWrapperDark
);
const modalIconStyle = getStylesFromColorScheme(
styles.modalIcon,
styles.modalIconDark
);
const modalItemLabelStyle = getStylesFromColorScheme(
styles.modalItemLabel,
styles.modalItemLabelDark
);

const clipboardBlockStyles = getStylesFromColorScheme(
styles.clipboardBlock,
styles.clipboardBlockDark
);

const isClipboardBlock = item.id === 'clipboard';

return (
<TouchableHighlight
style={ styles.touchableArea }
underlayColor="transparent"
activeOpacity={ 0.5 }
accessibilityLabel={ item.title }
onPress={ this.onPress }
>
<View style={ [ styles.modalItem, { width: maxWidth } ] }>
<View
style={ [
modalIconWrapperStyle,
itemWidth && {
width: itemWidth,
},
isClipboardBlock && clipboardBlockStyles,
] }
>
<View style={ modalIconStyle }>
<Icon
icon={ item.icon.src }
fill={ modalIconStyle.fill }
size={ modalIconStyle.width }
/>
</View>
</View>
<Text style={ modalItemLabelStyle }>
{ isClipboardBlock ? __( 'Copied block' ) : item.title }
</Text>
</View>
</TouchableHighlight>
);
}
}

export default withPreferredColorScheme( MenuItem );
Loading

0 comments on commit dface0b

Please sign in to comment.