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

Add native support for BlockList to @wordpress/block-editor (Ported from gutenberg mobile) #16239

Merged
merged 3 commits into from
Jun 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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