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

[RNMobile] Long-press on inserter to show options for "add above" and "add below" #18791

Merged
merged 36 commits into from
Jan 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9fdbf76
Add onLongPress prop to Button
ceyhun Nov 16, 2019
d1e7ea2
Show inserter menu on long press
ceyhun Nov 16, 2019
4ecb48d
Make onLongPress a prop of toggle
ceyhun Nov 23, 2019
d6b716e
Make withSelect of Inserter return destinationRootClientId
ceyhun Nov 23, 2019
46d8206
Insert default block before the selected one on long press
ceyhun Nov 23, 2019
0d13ef7
Add add canReplaceBlock key to the insertionPoint state
ceyhun Nov 26, 2019
3fee2af
Hide a block in list only if it can be replaced
ceyhun Nov 26, 2019
07cd4fa
Move insertion index logic from menu to inserter
ceyhun Nov 26, 2019
df0c42b
Update selector tests for newly added key in state
ceyhun Nov 30, 2019
cd74cfc
Update docs for newly added key in state
ceyhun Nov 30, 2019
9fcfe3b
Add insertionIndexAfter and isAnyBlockSelected props with selector
ceyhun Nov 30, 2019
eb2ec38
Refactor insertion index logic
ceyhun Dec 1, 2019
8771782
Add insertionType to component state and map to insertion index
ceyhun Nov 30, 2019
c7101b8
Show BottomSheet on long press to choose to insert before or after
ceyhun Nov 30, 2019
4bc8825
Update UI strings to be title case
ceyhun Dec 3, 2019
2430eb3
Hide cancel button from Bottom Sheet on Android
ceyhun Dec 6, 2019
1ccf577
Add icons to Bottom Sheet options on Android
ceyhun Dec 8, 2019
ececc84
Add “Replace Current Block” option to menu on long-press
ceyhun Dec 8, 2019
aa7231b
Scroll to newly added block if it doesn't trigger keyboard to open
ceyhun Dec 8, 2019
a42307a
Change “Replace Current Block” menu icon on Android
ceyhun Dec 16, 2019
20eab25
Use shorter syntax for setState
ceyhun Dec 16, 2019
2914a4b
Rename getShouldReplaceBlock method to shouldReplaceBlock
ceyhun Dec 16, 2019
5c14c4b
Revert "Scroll to newly added block if it doesn't trigger keyboard to…
ceyhun Dec 17, 2019
997a73d
Revert “Add canReplaceBlock key to the insertionPoint state"
ceyhun Dec 17, 2019
70062a0
Remove Block show/hide logic from BlockList
ceyhun Dec 17, 2019
c5a6079
Update Inserter local state to store block insertion information
ceyhun Dec 17, 2019
2ae9e99
Make InserterMenu add/remove unmodified default block during replace
ceyhun Dec 17, 2019
15f7651
Handle replacing last remaining block
ceyhun Dec 17, 2019
682c471
Fix Inserter test
ceyhun Dec 18, 2019
3b05a40
Fix code style issue
ceyhun Dec 18, 2019
8b35936
Move insertion options into getInsertionOptions function
ceyhun Dec 18, 2019
7f08b8d
Add comment about removing last block case
ceyhun Dec 18, 2019
b2c2fc8
Use findByProps instead of toJSON when testing Inserter
ceyhun Dec 18, 2019
5f1aec9
Merge branch 'master' into rnmobile/1433-long-press-on-inserter-to-sh…
ceyhun Jan 28, 2020
5164247
Merge branch 'master' into rnmobile/1433-long-press-on-inserter-to-sh…
ceyhun Jan 29, 2020
e345e42
Merge branch 'master' into rnmobile/1433-long-press-on-inserter-to-sh…
ceyhun Jan 29, 2020
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
51 changes: 10 additions & 41 deletions packages/block-editor/src/components/block-list/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { View, Platform, TouchableWithoutFeedback } from 'react-native';
import { Component } from '@wordpress/element';
import { withDispatch, withSelect } from '@wordpress/data';
import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks';
import { createBlock } from '@wordpress/blocks';
import { KeyboardAwareFlatList, ReadableContentView } from '@wordpress/components';

/**
Expand Down Expand Up @@ -127,17 +127,9 @@ export class BlockList extends Component {
);
}

isReplaceable( block ) {
if ( ! block ) {
return false;
}
return isUnmodifiedDefaultBlock( block );
}

renderItem( { item: clientId, index } ) {
renderItem( { item: clientId } ) {
const {
isReadOnly,
shouldShowBlockAtIndex,
shouldShowInsertionPointBefore,
shouldShowInsertionPointAfter,
} = this.props;
Expand All @@ -146,15 +138,14 @@ export class BlockList extends Component {
<ReadableContentView>
<View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }>
{ shouldShowInsertionPointBefore( clientId ) && <BlockInsertionPoint /> }
{ shouldShowBlockAtIndex( index ) && (
<BlockListBlock
key={ clientId }
showTitle={ false }
clientId={ clientId }
rootClientId={ this.props.rootClientId }
onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange }
isSmallScreen={ ! this.props.isFullyBordered }
/> ) }
<BlockListBlock
key={ clientId }
showTitle={ false }
clientId={ clientId }
rootClientId={ this.props.rootClientId }
onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange }
isSmallScreen={ ! this.props.isFullyBordered }
/>
{ ! this.shouldShowInnerBlockAppender() && shouldShowInsertionPointAfter( clientId ) && <BlockInsertionPoint /> }
</View>
</ReadableContentView>
Expand All @@ -180,25 +171,17 @@ export default compose( [
withSelect( ( select, { rootClientId } ) => {
const {
getBlockCount,
getBlockIndex,
getBlockOrder,
getSelectedBlockClientId,
getBlockInsertionPoint,
isBlockInsertionPointVisible,
getSelectedBlock,
getSettings,
} = select( 'core/block-editor' );

const {
getGroupingBlockName,
} = select( 'core/blocks' );

const selectedBlockClientId = getSelectedBlockClientId();
const blockClientIds = getBlockOrder( rootClientId );
const insertionPoint = getBlockInsertionPoint();
const blockInsertionPointIsVisible = isBlockInsertionPointVisible();
const selectedBlock = getSelectedBlock();
const hasInnerBlocks = selectedBlock && selectedBlock.name === getGroupingBlockName();
const shouldShowInsertionPointBefore = ( clientId ) => {
return (
blockInsertionPointIsVisible &&
Expand All @@ -224,26 +207,12 @@ export default compose( [
);
};

const selectedBlockIndex = getBlockIndex( selectedBlockClientId, rootClientId );

const shouldShowBlockAtIndex = ( index ) => {
const shouldHideBlockAtIndex = (
! hasInnerBlocks && blockInsertionPointIsVisible &&
// if `index` === `insertionPoint.index`, then block is replaceable
index === insertionPoint.index &&
// only hide selected block
index === selectedBlockIndex
);
return ! shouldHideBlockAtIndex;
};

const isReadOnly = getSettings().readOnly;

return {
blockClientIds,
blockCount: getBlockCount( rootClientId ),
isBlockInsertionPointVisible: isBlockInsertionPointVisible(),
shouldShowBlockAtIndex,
shouldShowInsertionPointBefore,
shouldShowInsertionPointAfter,
selectedBlockClientId,
Expand Down
203 changes: 187 additions & 16 deletions packages/block-editor/src/components/inserter/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Dropdown, ToolbarButton, Dashicon } from '@wordpress/components';
import { Dropdown, ToolbarButton, Dashicon, Picker } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { withSelect } from '@wordpress/data';
import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { getUnregisteredTypeHandlerName } from '@wordpress/blocks';
import { isUnmodifiedDefaultBlock } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -15,7 +15,7 @@ import styles from './style.scss';
import InserterMenu from './menu';
import BlockInsertionPoint from '../block-list/insertion-point';

const defaultRenderToggle = ( { onToggle, disabled, style } ) => (
const defaultRenderToggle = ( { onToggle, disabled, style, onLongPress } ) => (
<ToolbarButton
title={ __( 'Add block' ) }
icon={ ( <Dashicon icon="plus-alt" style={ style } color={ style.color } /> ) }
Expand All @@ -25,12 +25,13 @@ const defaultRenderToggle = ( { onToggle, disabled, style } ) => (
// testID is present to disambiguate this element for native UI tests. It's not
// usually required for components. See: https://git.io/JeQ7G.
testID: 'add-block-button',
onLongPress,
} }
isDisabled={ disabled }
/>
);

class Inserter extends Component {
export class Inserter extends Component {
constructor() {
super( ...arguments );

Expand All @@ -39,6 +40,75 @@ class Inserter extends Component {
this.renderContent = this.renderContent.bind( this );
}

getInsertionOptions() {
const addBeforeOption = {
value: 'before',
label: __( 'Add Block Before' ),
icon: 'insert-before',
};

const replaceCurrentOption = {
value: 'replace',
label: __( 'Replace Current Block' ),
icon: 'plus-alt',
};

const addAfterOption = {
value: 'after',
label: __( 'Add Block After' ),
icon: 'insert-after',
};

const addToBeginningOption = {
value: 'before',
label: __( 'Add To Beginning' ),
icon: 'insert-before',
};

const addToEndOption = {
value: 'after',
label: __( 'Add To End' ),
icon: 'insert-after',
};

const { isAnyBlockSelected, isSelectedBlockReplaceable } = this.props;
if ( isAnyBlockSelected ) {
if ( isSelectedBlockReplaceable ) {
return [ addBeforeOption, replaceCurrentOption, addAfterOption ];
}
return [ addBeforeOption, addAfterOption ];
}
return [ addToBeginningOption, addToEndOption ];
}

getInsertionIndex( insertionType ) {
const {
insertionIndexDefault,
insertionIndexBefore,
insertionIndexAfter,
} = this.props;
if ( insertionType === 'before' || insertionType === 'replace' ) {
return insertionIndexBefore;
}
if ( insertionType === 'after' ) {
return insertionIndexAfter;
}
return insertionIndexDefault;
}

shouldReplaceBlock( insertionType ) {
const {
isSelectedBlockReplaceable,
} = this.props;
if ( insertionType === 'replace' ) {
return true;
}
if ( insertionType === 'default' && isSelectedBlockReplaceable ) {
return true;
}
return false;
}

onToggle( isOpen ) {
const { onToggle } = this.props;

Expand Down Expand Up @@ -69,7 +139,46 @@ class Inserter extends Component {
return <BlockInsertionPoint />;
}
const style = getStylesFromColorScheme( styles.addBlockButton, styles.addBlockButtonDark );
return renderToggle( { onToggle, isOpen, disabled, style } );

const onPress = () => {
this.setState( {
destinationRootClientId: this.props.destinationRootClientId,
shouldReplaceBlock: this.shouldReplaceBlock( 'default' ),
insertionIndex: this.getInsertionIndex( 'default' ),
}, onToggle );
};

const onLongPress = () => {
if ( this.picker ) {
this.picker.presentPicker();
}
};

const onPickerSelect = ( insertionType ) => {
this.setState( {
destinationRootClientId: this.props.destinationRootClientId,
shouldReplaceBlock: this.shouldReplaceBlock( insertionType ),
insertionIndex: this.getInsertionIndex( insertionType ),
}, onToggle );
};

return (
<>
{ renderToggle( {
onToggle: onPress,
isOpen,
disabled,
style,
onLongPress,
} ) }
<Picker
ref={ ( instance ) => ( this.picker = instance ) }
options={ this.getInsertionOptions() }
onChange={ onPickerSelect }
hideCancelButton
/>
</>
);
}

/**
Expand All @@ -82,16 +191,25 @@ class Inserter extends Component {
* @return {WPElement} Dropdown content element.
*/
renderContent( { onClose, isOpen } ) {
const { rootClientId, clientId, isAppender } = this.props;

const {
clientId,
isAppender,
} = this.props;
const {
destinationRootClientId,
shouldReplaceBlock,
insertionIndex,
} = this.state;
return (
<InserterMenu
isOpen={ isOpen }
onSelect={ onClose }
onDismiss={ onClose }
rootClientId={ rootClientId }
rootClientId={ destinationRootClientId }
clientId={ clientId }
isAppender={ isAppender }
shouldReplaceBlock={ shouldReplaceBlock }
insertionIndex={ insertionIndex }
/>
);
}
Expand All @@ -111,23 +229,76 @@ class Inserter extends Component {
export default compose( [
withSelect( ( select, { clientId, isAppender, rootClientId } ) => {
const {
getInserterItems,
getBlockRootClientId,
getBlockSelectionEnd,
getBlockOrder,
getBlockIndex,
getBlock,
} = select( 'core/block-editor' );

let destinationRootClientId = rootClientId;
if ( ! destinationRootClientId && ! clientId && ! isAppender ) {
const end = getBlockSelectionEnd();
if ( end ) {
destinationRootClientId = getBlockRootClientId( end ) || undefined;
const end = getBlockSelectionEnd();
// `end` argument (id) can refer to the component which is removed
// due to pressing `undo` button, that's why we need to check
// if `getBlock( end) is valid, otherwise `null` is passed
const isAnyBlockSelected = ( ! isAppender && end && getBlock( end ) );
const destinationRootClientId = isAnyBlockSelected ?
ceyhun marked this conversation as resolved.
Show resolved Hide resolved
getBlockRootClientId( end ) :
rootClientId;
const selectedBlockIndex = getBlockIndex( end, destinationRootClientId );
const endOfRootIndex = getBlockOrder( rootClientId ).length;
const isSelectedUnmodifiedDefaultBlock = isAnyBlockSelected ?
isUnmodifiedDefaultBlock( getBlock( end ) ) :
undefined;

function getDefaultInsertionIndex() {
const {
getSettings,
} = select( 'core/block-editor' );

const { __experimentalShouldInsertAtTheTop: shouldInsertAtTheTop } = getSettings();

// if post title is selected insert as first block
if ( shouldInsertAtTheTop ) {
return 0;
}

// If the clientId is defined, we insert at the position of the block.
if ( clientId ) {
return getBlockIndex( clientId, rootClientId );
}

// If there is a selected block,
if ( isAnyBlockSelected ) {
// and the last selected block is unmodified (empty), it will be replaced
if ( isSelectedUnmodifiedDefaultBlock ) {
return selectedBlockIndex;
}

// we insert after the selected block.
return selectedBlockIndex + 1;
}

// Otherwise, we insert at the end of the current rootClientId
return endOfRootIndex;
}
const inserterItems = getInserterItems( destinationRootClientId );

const insertionIndexBefore = isAnyBlockSelected ?
selectedBlockIndex :
0;

const insertionIndexAfter = isAnyBlockSelected ?
selectedBlockIndex + 1 :
endOfRootIndex;

return {
items: inserterItems.filter( ( { name } ) => name !== getUnregisteredTypeHandlerName() ),
destinationRootClientId,
insertionIndexDefault: getDefaultInsertionIndex(),
insertionIndexBefore,
insertionIndexAfter,
isAnyBlockSelected,
isSelectedBlockReplaceable: isSelectedUnmodifiedDefaultBlock,
};
} ),

withPreferredColorScheme,
] )( Inserter );
Loading