Skip to content

Commit

Permalink
Merge pull request #123 from wordpress-mobile/feature/inserter
Browse files Browse the repository at this point in the history
Feature: Inserter - 1st iteration with Picker
  • Loading branch information
mzorz authored Aug 17, 2018
2 parents 48fc800 + 46cb83a commit 46ac1d0
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 7 deletions.
5 changes: 4 additions & 1 deletion src/app/AppContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
moveBlockUpAction,
moveBlockDownAction,
deleteBlockAction,
createBlockAction,
parseBlocksAction,
} from '../store/actions';
import MainApp from './MainApp';
Expand All @@ -34,10 +35,12 @@ const mapDispatchToProps = ( dispatch, ownProps ) => {
deleteBlockAction: ( clientId ) => {
dispatch( deleteBlockAction( clientId ) );
},
createBlockAction: ( clientId, block, clientIdAbove ) => {
dispatch( createBlockAction( clientId, block, clientIdAbove ) );
},
parseBlocksAction: ( html ) => {
dispatch( parseBlocksAction( html ) );
},

};
};

Expand Down
68 changes: 62 additions & 6 deletions src/block-management/block-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,22 @@
*/

import React from 'react';
import { Platform, Switch, Text, View, FlatList, TextInput, KeyboardAvoidingView } from 'react-native';
import { Platform, Switch, Text, View, FlatList, Picker, TextInput, KeyboardAvoidingView } from 'react-native';
import RecyclerViewList, { DataSource } from 'react-native-recyclerview-list';
import BlockHolder from './block-holder';
import { ToolbarButton } from './constants';

import type { BlockType } from '../store/';

import styles from './block-manager.scss';

// Gutenberg imports
import { getBlockType, serialize } from '@wordpress/blocks';
import { getBlockType, getBlockTypes, serialize, createBlock } from '@wordpress/blocks';

export type BlockListType = {
onChange: ( clientId: string, attributes: mixed ) => void,
focusBlockAction: string => mixed,
moveBlockUpAction: string => mixed,
moveBlockDownAction: string => mixed,
deleteBlockAction: string => mixed,
createBlockAction: ( string, BlockType, string ) => mixed,
parseBlocksAction: string => mixed,
blocks: Array<BlockType>,
aztechtml: string,
Expand All @@ -32,17 +30,22 @@ type PropsType = BlockListType;
type StateType = {
dataSource: DataSource,
showHtml: boolean,
blockTypePickerVisible: boolean,
selectedBlockType: string,
html: string,
};

export default class BlockManager extends React.Component<PropsType, StateType> {
_recycler = null;
availableBlockTypes = getBlockTypes();

constructor( props: PropsType ) {
super( props );
this.state = {
dataSource: new DataSource( this.props.blocks, ( item: BlockType ) => item.clientId ),
showHtml: false,
blockTypePickerVisible: false,
selectedBlockType: 'core/paragraph', // just any valid type to start from
html: '',
};
}
Expand All @@ -61,6 +64,41 @@ export default class BlockManager extends React.Component<PropsType, StateType>
return -1;
}

findDataSourceIndexForFocusedItem() {
for ( let i = 0; i < this.state.dataSource.size(); ++i ) {
const block = this.state.dataSource.get( i );
if ( block.focused === true ) {
return i;
}
}
return -1;
}

// 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() {
this.setState( { ...this.state, blockTypePickerVisible: true } );
}

onBlockTypeSelected( itemValue: string ) {
this.setState( { ...this.state, selectedBlockType: itemValue, blockTypePickerVisible: false } );

// find currently focused block
const focusedItemIndex = this.findDataSourceIndexForFocusedItem();
const clientIdFocused = this.state.dataSource.get( focusedItemIndex ).clientId;

// create an empty block of the selected type
const newBlock = createBlock( itemValue, { content: 'new test text for a ' + itemValue + ' block' } );
newBlock.focused = false;

// set it into the datasource, and use the same object instance to send it to props/redux
this.state.dataSource.splice( focusedItemIndex + 1, 0, newBlock );
this.props.createBlockAction( newBlock.clientId, newBlock, clientIdFocused );

// now set the focus
this.props.focusBlockAction( newBlock.clientId );
}

static getDerivedStateFromProps( props: PropsType, state: StateType ) {
if ( props.fullparse === true ) {
return {
Expand All @@ -87,6 +125,9 @@ export default class BlockManager extends React.Component<PropsType, StateType>
this.state.dataSource.splice( dataSourceBlockIndex, 1 );
this.props.deleteBlockAction( clientId );
break;
case ToolbarButton.PLUS:
this.showBlockTypePicker();
break;
case ToolbarButton.SETTINGS:
// TODO: implement settings
break;
Expand Down Expand Up @@ -125,7 +166,7 @@ export default class BlockManager extends React.Component<PropsType, StateType>
// Update datasource UI
const index = this.getDataSourceIndexFromUid( clientId );
const dataSource = this.state.dataSource;
const block = dataSource.get( this.getDataSourceIndexFromUid( clientId ) );
const block = dataSource.get( index );
block.attributes = attributes;
dataSource.set( index, block );
// Update Redux store
Expand Down Expand Up @@ -161,6 +202,20 @@ export default class BlockManager extends React.Component<PropsType, StateType>
);
}

const blockTypePicker = (
<View>
<Picker
selectedValue={ this.state.selectedBlockType }
onValueChange={ ( itemValue ) => {
this.onBlockTypeSelected( itemValue );
} } >
{ this.availableBlockTypes.map( ( item, index ) => {
return ( <Picker.Item label={ item.title } value={ item.name } key={ index + 1 } /> );
} ) }
</Picker>
</View>
);

return (
<View style={ styles.container }>
<View style={ { height: 30 } } />
Expand All @@ -175,6 +230,7 @@ export default class BlockManager extends React.Component<PropsType, StateType>
</View>
{ this.state.showHtml && this.renderHTML() }
{ ! this.state.showHtml && list }
{ this.state.blockTypePickerVisible && blockTypePicker }
</View>
);
}
Expand Down
1 change: 1 addition & 0 deletions src/store/actions/ActionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default {
MOVE_UP: 'BLOCK_MOVE_UP_ACTION',
MOVE_DOWN: 'BLOCK_MOVE_DOWN_ACTION',
DELETE: 'BLOCK_DELETE_ACTION',
CREATE: 'BLOCK_CREATE_ACTION',
PARSE: 'BLOCK_PARSE_ACTION',
},
};
15 changes: 15 additions & 0 deletions src/store/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@
*/

import ActionTypes from './ActionTypes';
import type { BlockType } from '../';

export type BlockActionType = string => {
type: $Values<typeof ActionTypes.BLOCK>,
clientId: string,
};

export type CreateActionType = ( string, BlockType, string ) => {
type: $Values<typeof ActionTypes.BLOCK>,
clientId: string,
block: BlockType,
clientIdAbove: string,
};

export type ParseActionType = string => {
type: $Values<typeof ActionTypes.BLOCK>,
html: string,
Expand Down Expand Up @@ -43,6 +51,13 @@ export const deleteBlockAction: BlockActionType = ( clientId ) => ( {
clientId,
} );

export const createBlockAction: CreateActionType = ( clientId, block, clientIdAbove ) => ( {
type: ActionTypes.BLOCK.CREATE,
clientId,
block: block,
clientIdAbove,
} );

export const parseBlocksAction: ParseActionType = ( html ) => ( {
type: ActionTypes.BLOCK.PARSE,
html,
Expand Down
16 changes: 16 additions & 0 deletions src/store/actions/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

import * as actions from './';
import ActionTypes from './ActionTypes';
// Gutenberg imports
import { createBlock } from '@wordpress/blocks';
import { registerCoreBlocks } from '@wordpress/block-library';

describe( 'Store', () => {
describe( 'actions', () => {
beforeAll( () => {
registerCoreBlocks();
} );

it( 'should create an action to focus a block', () => {
const action = actions.focusBlockAction( '1' );
expect( action.type ).toBeDefined();
Expand Down Expand Up @@ -32,5 +39,14 @@ describe( 'Store', () => {
expect( action.type ).toEqual( ActionTypes.BLOCK.DELETE );
expect( action.clientId ).toEqual( '1' );
} );

it( 'should create an action to create a block', () => {
const newBlock = createBlock( 'core/code', { content: 'new test text for a core/code block' } );
const action = actions.createBlockAction( '1', newBlock, '0' );
expect( action.type ).toEqual( ActionTypes.BLOCK.CREATE );
expect( action.clientId ).toEqual( '1' );
expect( action.block ).toEqual( newBlock );
expect( action.clientIdAbove ).toEqual( '0' );
} );
} );
} );
15 changes: 15 additions & 0 deletions src/store/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ function findBlockIndex( blocks, clientId: string ) {
} );
}

/*
* insert block into blocks[], below / after block having clientIdAbove
*/
function insertBlock( blocks, block, clientIdAbove ) {
// TODO we need to set focused: true and search for the currently focused block and
// set that one to `focused: false`.
blocks.splice( findBlockIndex( blocks, clientIdAbove ) + 1, 0, block );
}

export const reducer = (
state: StateType = { blocks: [], refresh: false },
action: BlockActionType
Expand Down Expand Up @@ -109,6 +118,12 @@ export const reducer = (
blocks.splice( index, 1 );
return { blocks: blocks, refresh: ! state.refresh };
}
case ActionTypes.BLOCK.CREATE: {
// TODO we need to set focused: true and search for the currently focused block and
// set that one to `focused: false`.
insertBlock( blocks, action.block, action.clientIdAbove );
return { blocks: blocks, refresh: ! state.refresh };
}
case ActionTypes.BLOCK.PARSE: {
const parsed = parse( action.html );
return { blocks: parsed, refresh: ! state.refresh, fullparse: true };
Expand Down

0 comments on commit 46ac1d0

Please sign in to comment.