Skip to content

Commit

Permalink
Block Library: Reimplement Reusable Block using EditorProvider for em…
Browse files Browse the repository at this point in the history
…bedded post editor
  • Loading branch information
aduth authored and jorgefilipecosta committed Jun 17, 2019
1 parent cae7bb7 commit edaf84f
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 139 deletions.
37 changes: 35 additions & 2 deletions packages/block-library/src/block/edit-panel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Button } from '@wordpress/components';
import { Component, createRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { ESCAPE } from '@wordpress/keycodes';
import { withInstanceId } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
import { withInstanceId, compose } from '@wordpress/compose';

class ReusableBlockEditPanel extends Component {
constructor() {
Expand Down Expand Up @@ -107,4 +108,36 @@ class ReusableBlockEditPanel extends Component {
}
}

export default withInstanceId( ReusableBlockEditPanel );
export default compose( [
withInstanceId,
withSelect( ( select ) => {
const { getEditedPostAttribute, isSavingPost } = select( 'core/editor' );

return {
title: getEditedPostAttribute( 'title' ),
isSaving: isSavingPost(),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
const {
editPost,
savePost,
clearSelectedBlock,
} = dispatch( 'core/editor' );

return {
onChangeTitle( title ) {
editPost( { title } );
},
onSave() {
clearSelectedBlock();
savePost();
ownProps.onSave();
},
onCancel() {
clearSelectedBlock();
ownProps.onCancel();
},
};
} ),
] )( ReusableBlockEditPanel );
262 changes: 125 additions & 137 deletions packages/block-library/src/block/edit.js
Original file line number Diff line number Diff line change
@@ -1,187 +1,175 @@
/**
* External dependencies
*/
import { noop, partial } from 'lodash';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { Placeholder, Spinner, Disabled } from '@wordpress/components';
import { withSelect, withDispatch } from '@wordpress/data';
import { Placeholder, Spinner, Disabled, SlotFillProvider } from '@wordpress/components';
import {
withSelect,
withDispatch,
withRegistry,
} from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { BlockEdit } from '@wordpress/block-editor';
import {
BlockList,
BlockControls,
BlockFormatControls,
FormatToolbar,
__experimentalBlockSettingsMenuFirstItem,
__experimentalBlockSettingsMenuPluginsExtension,
} from '@wordpress/block-editor';
import { EditorProvider } from '@wordpress/editor';
import { compose } from '@wordpress/compose';

/**
* Internal dependencies
*/
import ReusableBlockEditPanel from './edit-panel';
import ReusableBlockIndicator from './indicator';
import SelectionObserver from './selection-observer';

class ReusableBlockEdit extends Component {
constructor( { reusableBlock } ) {
constructor() {
super( ...arguments );

this.startEditing = this.startEditing.bind( this );
this.stopEditing = this.stopEditing.bind( this );
this.setAttributes = this.setAttributes.bind( this );
this.setTitle = this.setTitle.bind( this );
this.save = this.save.bind( this );

if ( reusableBlock && reusableBlock.isTemporary ) {
// Start in edit mode when we're working with a newly created reusable block
this.state = {
isEditing: true,
title: reusableBlock.title,
changedAttributes: {},
};
} else {
// Start in preview mode when we're working with an existing reusable block
this.state = {
isEditing: false,
title: null,
changedAttributes: null,
};
}
}

componentDidMount() {
if ( ! this.props.reusableBlock ) {
this.props.fetchReusableBlock();
}
}

startEditing() {
const { reusableBlock } = this.props;

this.setState( {
isEditing: true,
title: reusableBlock.title,
changedAttributes: {},
} );
}
this.startEditing = () => this.toggleIsEditing( true );
this.stopEditing = () => this.toggleIsEditing( false );
this.cancelEditing = this.cancelEditing.bind( this );

stopEditing() {
this.setState( {
this.state = {
cancelIncrementKey: 0,
// TODO: Check if this needs to consider reusable block being temporary (this was in original PR)
isEditing: false,
title: null,
changedAttributes: null,
} );
}

setAttributes( attributes ) {
this.setState( ( prevState ) => {
if ( prevState.changedAttributes !== null ) {
return { changedAttributes: { ...prevState.changedAttributes, ...attributes } };
}
} );
};
}

setTitle( title ) {
this.setState( { title } );
/**
* Starts or stops editing, corresponding to the given boolean value.
*
* @param {boolean} isEditing Whether editing mode should be made active.
*/
toggleIsEditing( isEditing ) {
this.setState( { isEditing } );
}

save() {
const { reusableBlock, onUpdateTitle, updateAttributes, block, onSave } = this.props;
const { title, changedAttributes } = this.state;

if ( title !== reusableBlock.title ) {
onUpdateTitle( title );
}

updateAttributes( block.clientId, changedAttributes );
onSave();

/**
* Stops editing and restores the reusable block to its original saved
* state.
*/
cancelEditing() {
this.stopEditing();

// Cancelling takes effect by assigning a new key for the rendered
// EditorProvider which forces a re-mount to reset editing state.
let { cancelIncrementKey } = this.state;
cancelIncrementKey++;
this.setState( { cancelIncrementKey } );
}

render() {
const { isSelected, reusableBlock, block, isFetching, isSaving, canUpdateBlock } = this.props;
const { isEditing, title, changedAttributes } = this.state;

if ( ! reusableBlock && isFetching ) {
return <Placeholder><Spinner /></Placeholder>;
}

if ( ! reusableBlock || ! block ) {
return <Placeholder>{ __( 'Block has been deleted or is unavailable.' ) }</Placeholder>;
const {
isSelected,
reusableBlock,
isFetching,
canUpdateBlock,
settings,
} = this.props;
const { cancelIncrementKey, isEditing } = this.state;

if ( ! reusableBlock ) {
return (
<Placeholder>
{
isFetching ?
<Spinner /> :
__( 'Block has been deleted or is unavailable.' )
}
</Placeholder>
);
}

let element = (
<BlockEdit
{ ...this.props }
isSelected={ isEditing && isSelected }
clientId={ block.clientId }
name={ block.name }
attributes={ { ...block.attributes, ...changedAttributes } }
setAttributes={ isEditing ? this.setAttributes : noop }
/>
);

let list = <BlockList />;
if ( ! isEditing ) {
element = <Disabled>{ element }</Disabled>;
list = <Disabled>{ list }</Disabled>;
}

return (
<>
{ ( isSelected || isEditing ) && (
<ReusableBlockEditPanel
isEditing={ isEditing }
title={ title !== null ? title : reusableBlock.title }
isSaving={ isSaving && ! reusableBlock.isTemporary }
isEditDisabled={ ! canUpdateBlock }
onEdit={ this.startEditing }
onChangeTitle={ this.setTitle }
onSave={ this.save }
onCancel={ this.stopEditing }
<SlotFillProvider
slots={ [
FormatToolbar.Slot,
BlockControls.Slot,
BlockFormatControls.Slot,
__experimentalBlockSettingsMenuFirstItem.Slot,
__experimentalBlockSettingsMenuPluginsExtension.Slot,
] }
>
<EditorProvider
key={ cancelIncrementKey }
post={ reusableBlock }
settings={ { ...settings, templateLock: ! isEditing } }
>
<SelectionObserver
isParentSelected={ isSelected }
onBlockSelected={ this.props.selectBlock }
/>
) }
{ ! isSelected && ! isEditing && <ReusableBlockIndicator title={ reusableBlock.title } /> }
{ element }
</>
{ ( isSelected || isEditing ) && (
<ReusableBlockEditPanel
isEditing={ isEditing }
isEditDisabled={ ! canUpdateBlock }
onEdit={ this.startEditing }
onSave={ this.stopEditing }
onCancel={ this.cancelEditing }
/>
) }
{ ! isSelected && ! isEditing && (
<ReusableBlockIndicator title={ reusableBlock.title } />
) }
{ list }
</EditorProvider>
</SlotFillProvider>
);
}
}

export default compose( [
withRegistry,
withSelect( ( select, ownProps ) => {
const {
__experimentalGetReusableBlock: getReusableBlock,
__experimentalIsFetchingReusableBlock: isFetchingReusableBlock,
__experimentalIsSavingReusableBlock: isSavingReusableBlock,
} = select( 'core/editor' );
const { canUser } = select( 'core' );
const {
getBlock,
} = select( 'core/block-editor' );
const { ref } = ownProps.attributes;
const reusableBlock = getReusableBlock( ref );
const { clientId, attributes } = ownProps;
const { ref } = attributes;
const { canUser, getEntityRecord } = select( 'core' );
const { isResolving } = select( 'core/data' );
const { getEditorSettings } = select( 'core/editor' );
const { isBlockSelected } = select( 'core/block-editor' );

const isTemporaryReusableBlock = ! Number.isFinite( ref );

let reusableBlock;
if ( ! isTemporaryReusableBlock ) {
reusableBlock = getEntityRecord( 'postType', 'wp_block', ref );
}

return {
reusableBlock,
isFetching: isFetchingReusableBlock( ref ),
isSaving: isSavingReusableBlock( ref ),
block: reusableBlock ? getBlock( reusableBlock.clientId ) : null,
canUpdateBlock: !! reusableBlock && ! reusableBlock.isTemporary && !! canUser( 'update', 'blocks', ref ),
isSelected: isBlockSelected( clientId ),
isFetching: isResolving(
'core',
'getEntityRecord',
[ 'postType', 'wp_block', ref ]
),
canUpdateBlock: (
!! reusableBlock &&
! isTemporaryReusableBlock &&
!! canUser( 'update', 'blocks', ref )
),
settings: getEditorSettings(),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
const {
__experimentalFetchReusableBlocks: fetchReusableBlocks,
__experimentalUpdateReusableBlockTitle: updateReusableBlockTitle,
__experimentalSaveReusableBlock: saveReusableBlock,
} = dispatch( 'core/editor' );
const {
updateBlockAttributes,
} = dispatch( 'core/block-editor' );
const { ref } = ownProps.attributes;
const { selectBlock } = dispatch( 'core/block-editor' );

return {
fetchReusableBlock: partial( fetchReusableBlocks, ref ),
updateAttributes: updateBlockAttributes,
onUpdateTitle: partial( updateReusableBlockTitle, ref ),
onSave: partial( saveReusableBlock, ref ),
selectBlock() {
selectBlock( ownProps.clientId );
},
};
} ),
] )( ReusableBlockEdit );
9 changes: 9 additions & 0 deletions packages/block-library/src/block/editor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.block-editor-block-list__block[data-type="core/block"] {
.block-editor-block-list__layout > .block-editor-block-list__block:first-child > .block-editor-block-list__block-edit {
margin-top: 0;
}

.block-editor-block-list__layout > .block-editor-block-list__block:last-child > .block-editor-block-list__block-edit {
margin-bottom: 0;
}
}
Loading

0 comments on commit edaf84f

Please sign in to comment.