-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add native support for BlockList to @wordpress/block-editor (Ported f…
…rom gutenberg mobile) (#16239) * Add native support for BlockList to @wordpress/block-editor (Ported from gutenberg mobile) * Fix tests * Exclude flow check from BlockList
- Loading branch information
Showing
9 changed files
with
493 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
283 changes: 283 additions & 0 deletions
283
packages/block-editor/src/components/block-list/index.native.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { identity } from 'lodash'; | ||
import { Text, View, Keyboard, SafeAreaView, Platform } from 'react-native'; | ||
import { subscribeMediaAppend } from 'react-native-gutenberg-bridge'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Component, Fragment } from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { withDispatch, withSelect } from '@wordpress/data'; | ||
import { compose } from '@wordpress/compose'; | ||
import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; | ||
import { HTMLTextInput, KeyboardAvoidingView, KeyboardAwareFlatList, ReadableContentView } from '@wordpress/components'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import styles from './style.scss'; | ||
import BlockListBlock from './block'; | ||
import BlockToolbar from '../block-toolbar'; | ||
import DefaultBlockAppender from '../default-block-appender'; | ||
import Inserter from '../inserter'; | ||
|
||
const blockMobileToolbarHeight = 44; | ||
const toolbarHeight = 44; | ||
|
||
export class BlockList extends Component { | ||
constructor() { | ||
super( ...arguments ); | ||
|
||
this.renderItem = this.renderItem.bind( this ); | ||
this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( this ); | ||
this.renderDefaultBlockAppender = this.renderDefaultBlockAppender.bind( this ); | ||
this.onBlockTypeSelected = this.onBlockTypeSelected.bind( this ); | ||
this.keyboardDidShow = this.keyboardDidShow.bind( this ); | ||
this.keyboardDidHide = this.keyboardDidHide.bind( this ); | ||
this.onCaretVerticalPositionChange = this.onCaretVerticalPositionChange.bind( this ); | ||
this.scrollViewInnerRef = this.scrollViewInnerRef.bind( this ); | ||
|
||
this.state = { | ||
blockTypePickerVisible: false, | ||
isKeyboardVisible: false, | ||
}; | ||
} | ||
|
||
// TODO: in the near future this will likely be changed to onShowBlockTypePicker and bound to this.props | ||
// once we move the action to the toolbar | ||
showBlockTypePicker( show ) { | ||
this.setState( { blockTypePickerVisible: show } ); | ||
} | ||
|
||
onBlockTypeSelected( itemValue ) { | ||
this.setState( { blockTypePickerVisible: false } ); | ||
|
||
// create an empty block of the selected type | ||
const newBlock = createBlock( itemValue ); | ||
|
||
this.finishBlockAppendingOrReplacing( newBlock ); | ||
} | ||
|
||
finishBlockAppendingOrReplacing( newBlock ) { | ||
// now determine whether we need to replace the currently selected block (if it's empty) | ||
// or just add a new block as usual | ||
if ( this.isReplaceable( this.props.selectedBlock ) ) { | ||
// do replace here | ||
this.props.replaceBlock( this.props.selectedBlockClientId, newBlock ); | ||
} else { | ||
const indexAfterSelected = this.props.selectedBlockOrder + 1; | ||
const insertionIndex = indexAfterSelected || this.props.blockCount; | ||
this.props.insertBlock( newBlock, insertionIndex ); | ||
} | ||
} | ||
|
||
blockHolderBorderStyle() { | ||
return this.state.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; | ||
} | ||
|
||
componentDidMount() { | ||
this._isMounted = true; | ||
Keyboard.addListener( 'keyboardDidShow', this.keyboardDidShow ); | ||
Keyboard.addListener( 'keyboardDidHide', this.keyboardDidHide ); | ||
|
||
this.subscriptionParentMediaAppend = subscribeMediaAppend( ( payload ) => { | ||
// create an empty media block | ||
const newMediaBlock = createBlock( 'core/' + payload.mediaType ); | ||
|
||
// now set the url and id | ||
if ( payload.mediaType === 'image' ) { | ||
newMediaBlock.attributes.url = payload.mediaUrl; | ||
} else if ( payload.mediaType === 'video' ) { | ||
newMediaBlock.attributes.src = payload.mediaUrl; | ||
} | ||
|
||
newMediaBlock.attributes.id = payload.mediaId; | ||
|
||
// finally append or replace as appropriate | ||
this.finishBlockAppendingOrReplacing( newMediaBlock ); | ||
} ); | ||
} | ||
|
||
componentWillUnmount() { | ||
Keyboard.removeListener( 'keyboardDidShow', this.keyboardDidShow ); | ||
Keyboard.removeListener( 'keyboardDidHide', this.keyboardDidHide ); | ||
|
||
if ( this.subscriptionParentMediaAppend ) { | ||
this.subscriptionParentMediaAppend.remove(); | ||
} | ||
this._isMounted = false; | ||
} | ||
|
||
keyboardDidShow() { | ||
this.setState( { isKeyboardVisible: true } ); | ||
} | ||
|
||
keyboardDidHide() { | ||
this.setState( { isKeyboardVisible: false } ); | ||
} | ||
|
||
onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { | ||
KeyboardAwareFlatList.handleCaretVerticalPositionChange( this.scrollViewRef, targetId, caretY, previousCaretY ); | ||
} | ||
|
||
scrollViewInnerRef( ref ) { | ||
this.scrollViewRef = ref; | ||
} | ||
|
||
shouldFlatListPreventAutomaticScroll() { | ||
return this.state.blockTypePickerVisible; | ||
} | ||
|
||
renderDefaultBlockAppender() { | ||
return ( | ||
<ReadableContentView> | ||
<DefaultBlockAppender | ||
rootClientId={ this.props.rootClientId } | ||
containerStyle={ [ | ||
styles.blockContainerFocused, | ||
this.blockHolderBorderStyle(), | ||
{ borderColor: 'transparent' }, | ||
] } | ||
/> | ||
</ReadableContentView> | ||
); | ||
} | ||
|
||
renderList() { | ||
return ( | ||
<View | ||
style={ { flex: 1 } } | ||
onAccessibilityEscape={ this.props.clearSelectedBlock } | ||
> | ||
<KeyboardAwareFlatList | ||
{ ...( Platform.OS === 'android' ? { removeClippedSubviews: false } : {} ) } // Disable clipping on Android to fix focus losing. See https://github.com/wordpress-mobile/gutenberg-mobile/pull/741#issuecomment-472746541 | ||
accessibilityLabel="block-list" | ||
innerRef={ this.scrollViewInnerRef } | ||
blockToolbarHeight={ toolbarHeight } | ||
innerToolbarHeight={ blockMobileToolbarHeight } | ||
safeAreaBottomInset={ this.props.safeAreaBottomInset } | ||
parentHeight={ this.props.rootViewHeight } | ||
keyboardShouldPersistTaps="always" | ||
style={ styles.list } | ||
data={ this.props.blockClientIds } | ||
extraData={ [ this.props.isFullyBordered ] } | ||
keyExtractor={ identity } | ||
renderItem={ this.renderItem } | ||
shouldPreventAutomaticScroll={ this.shouldFlatListPreventAutomaticScroll } | ||
title={ this.props.title } | ||
ListHeaderComponent={ this.props.header } | ||
ListEmptyComponent={ this.renderDefaultBlockAppender } | ||
/> | ||
<SafeAreaView> | ||
<View style={ { height: toolbarHeight } } /> | ||
</SafeAreaView> | ||
<KeyboardAvoidingView | ||
style={ styles.blockToolbarKeyboardAvoidingView } | ||
parentHeight={ this.props.rootViewHeight } | ||
> | ||
<BlockToolbar | ||
onInsertClick={ () => { | ||
this.showBlockTypePicker( true ); | ||
} } | ||
showKeyboardHideButton={ this.state.isKeyboardVisible } | ||
/> | ||
</KeyboardAvoidingView> | ||
</View> | ||
); | ||
} | ||
|
||
render() { | ||
return ( | ||
<Fragment> | ||
{ this.renderList() } | ||
{ this.state.blockTypePickerVisible && ( | ||
<Inserter | ||
onDismiss={ () => this.showBlockTypePicker( false ) } | ||
onValueSelected={ this.onBlockTypeSelected } | ||
isReplacement={ this.isReplaceable( this.props.selectedBlock ) } | ||
addExtraBottomPadding={ this.props.safeAreaBottomInset === 0 } | ||
/> | ||
) } | ||
</Fragment> | ||
); | ||
} | ||
|
||
isReplaceable( block ) { | ||
if ( ! block ) { | ||
return false; | ||
} | ||
return isUnmodifiedDefaultBlock( block ); | ||
} | ||
|
||
renderItem( { item: clientId } ) { | ||
return ( | ||
<ReadableContentView> | ||
<BlockListBlock | ||
key={ clientId } | ||
showTitle={ false } | ||
clientId={ clientId } | ||
rootClientId={ this.props.rootClientId } | ||
onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange } | ||
borderStyle={ this.blockHolderBorderStyle() } | ||
focusedBorderColor={ styles.blockHolderFocused.borderColor } | ||
/> | ||
{ this.state.blockTypePickerVisible && this.props.isBlockSelected( clientId ) && ( | ||
<View style={ styles.containerStyleAddHere } > | ||
<View style={ styles.lineStyleAddHere }></View> | ||
<Text style={ styles.labelStyleAddHere } >{ __( 'ADD BLOCK HERE' ) }</Text> | ||
<View style={ styles.lineStyleAddHere }></View> | ||
</View> | ||
) } | ||
</ReadableContentView> | ||
); | ||
} | ||
|
||
renderHTML() { | ||
return ( | ||
<HTMLTextInput { ...this.props } parentHeight={ this.props.rootViewHeight } /> | ||
); | ||
} | ||
} | ||
|
||
export default compose( [ | ||
withSelect( ( select, { rootClientId } ) => { | ||
const { | ||
getBlockCount, | ||
getBlockName, | ||
getBlockIndex, | ||
getBlockOrder, | ||
getSelectedBlock, | ||
getSelectedBlockClientId, | ||
isBlockSelected, | ||
} = select( 'core/block-editor' ); | ||
|
||
const selectedBlockClientId = getSelectedBlockClientId(); | ||
|
||
return { | ||
blockClientIds: getBlockOrder( rootClientId ), | ||
blockCount: getBlockCount( rootClientId ), | ||
getBlockName, | ||
isBlockSelected, | ||
selectedBlock: getSelectedBlock(), | ||
selectedBlockClientId, | ||
selectedBlockOrder: getBlockIndex( selectedBlockClientId ), | ||
}; | ||
} ), | ||
withDispatch( ( dispatch ) => { | ||
const { | ||
insertBlock, | ||
replaceBlock, | ||
clearSelectedBlock, | ||
} = dispatch( 'core/block-editor' ); | ||
|
||
return { | ||
clearSelectedBlock, | ||
insertBlock, | ||
replaceBlock, | ||
}; | ||
} ), | ||
] )( BlockList ); | ||
|
75 changes: 75 additions & 0 deletions
75
packages/block-editor/src/components/block-list/style.native.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
.container { | ||
flex: 1; | ||
justify-content: flex-start; | ||
background-color: #fff; | ||
} | ||
|
||
.list { | ||
flex: 1; | ||
} | ||
|
||
.switch { | ||
flex-direction: row; | ||
justify-content: flex-start; | ||
align-items: center; | ||
margin: 10px; | ||
} | ||
|
||
.switchLabel { | ||
margin-left: 10px; | ||
} | ||
|
||
.lineStyleAddHere { | ||
flex: 1; | ||
background-color: #0087be; // blue_wordpress | ||
align-self: center; | ||
height: 2px; | ||
} | ||
|
||
.labelStyleAddHere { | ||
flex: 1; | ||
text-align: center; | ||
font-family: $default-monospace-font; | ||
font-size: 12px; | ||
font-weight: bold; | ||
} | ||
|
||
.containerStyleAddHere { | ||
flex: 1; | ||
flex-direction: row; | ||
background-color: $white; | ||
} | ||
|
||
.blockToolbarKeyboardAvoidingView { | ||
position: absolute; | ||
bottom: 0; | ||
right: 0; | ||
left: 0; | ||
} | ||
|
||
.blockHolderSemiBordered { | ||
border-top-width: 1px; | ||
border-bottom-width: 1px; | ||
border-left-width: 0; | ||
border-right-width: 0; | ||
} | ||
|
||
.blockHolderFullBordered { | ||
border-top-width: 1px; | ||
border-bottom-width: 1px; | ||
border-left-width: 1px; | ||
border-right-width: 1px; | ||
} | ||
|
||
|
||
.blockContainerFocused { | ||
background-color: $white; | ||
padding-left: 16; | ||
padding-right: 16; | ||
padding-top: 12; | ||
padding-bottom: 0; // will be flushed into inline toolbar height | ||
} | ||
|
||
.blockHolderFocused { | ||
border-color: $gray-lighten-30; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.