Skip to content

Commit

Permalink
Add native support for BlockList to @wordpress/block-editor (Ported f…
Browse files Browse the repository at this point in the history
…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
Tug authored Jun 21, 2019
1 parent 284bb17 commit a18ba18
Show file tree
Hide file tree
Showing 9 changed files with 493 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import { Component } from '@wordpress/element';
import { withDispatch, withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { getBlockType } from '@wordpress/blocks';
import { BlockEdit, BlockInvalidWarning, BlockMobileToolbar } from '@wordpress/block-editor';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import styles from './block.scss';
import BlockEdit from '../block-edit';
import BlockInvalidWarning from './block-invalid-warning';
import BlockMobileToolbar from './block-mobile-toolbar';

class BlockListBlock extends Component {
constructor() {
Expand Down
283 changes: 283 additions & 0 deletions packages/block-editor/src/components/block-list/index.native.js
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 packages/block-editor/src/components/block-list/style.native.scss
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;
}
3 changes: 1 addition & 2 deletions packages/block-editor/src/components/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export { default as URLInput } from './url-input';
export { default as BlockInvalidWarning } from './block-list/block-invalid-warning';

// Content Related Components
export { default as BlockListBlock } from './block-list/block';
export { default as BlockMobileToolbar } from './block-list/block-mobile-toolbar';
export { default as BlockList } from './block-list';
export { default as BlockMover } from './block-mover';
export { default as BlockToolbar } from './block-toolbar';
export { default as DefaultBlockAppender } from './default-block-appender';
Expand Down
Loading

0 comments on commit a18ba18

Please sign in to comment.