Skip to content

Commit

Permalink
Refactor reusable block edit component using hooks.
Browse files Browse the repository at this point in the history
  • Loading branch information
ZebulanStanphill committed Apr 11, 2020
1 parent d1d17be commit d0c1873
Showing 1 changed file with 144 additions and 173 deletions.
317 changes: 144 additions & 173 deletions packages/block-library/src/block/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,207 +6,178 @@ import { partial } from 'lodash';
/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { Placeholder, Spinner, Disabled } from '@wordpress/components';
import { withSelect, withDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import {
BlockEditorProvider,
BlockList,
WritingFlow,
} from '@wordpress/block-editor';
import { compose } from '@wordpress/compose';
import { parse, serialize } from '@wordpress/blocks';
import { Placeholder, Spinner, Disabled } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import ReusableBlockEditPanel from './edit-panel';

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

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

if ( reusableBlock ) {
// Start in edit mode when we're working with a newly created reusable block
this.state = {
isEditing: reusableBlock.isTemporary,
title: reusableBlock.title,
blocks: parse( reusableBlock.content ),
};
} else {
// Start in preview mode when we're working with an existing reusable block
this.state = {
isEditing: false,
title: null,
blocks: [],
const EMPTY_ARRAY = [];

export default function ReusableBlockEdit( { attributes, isSelected } ) {
const { ref } = attributes;

const {
theReusableBlock,
isFetching,
isSaving,
isTemporary,
blocks,
canUpdateBlock,
settings,
title,
} = useSelect(
/**
* @typedef {Object} WPReusableBlockEditSelectedData
*
* @property {?Array} blocks Reusable block content parsed as blocks.
* @property {boolean} canUpdateBlock Can the current user edit reusable blocks.
* @property {boolean} isFetching Is the reusable block being fetched.
* @property {boolean} isSaving Is the reusable block being saved.
* @property {?boolean} isTemporary Is the reusable block temporary.
* @property {Object} settings Editor settings.
* @property {?Object} theReusableBlock The reusable block.
* @property {?string} title The reusable block title.
*/

/**
* @param {Function} select
*
* @return {WPReusableBlockEditSelectedData} The data we selected.
*/
( select ) => {
const { canUser } = select( 'core' );
const { getSettings } = select( 'core/block-editor' );
const {
__experimentalGetReusableBlock: getReusableBlock,
__experimentalIsFetchingReusableBlock: isFetchingReusableBlock,
__experimentalIsSavingReusableBlock: isSavingReusableBlock,
} = select( 'core/editor' );
const reusableBlock = getReusableBlock( ref );

return {
theReusableBlock: reusableBlock,
isFetching: isFetchingReusableBlock( ref ),
isSaving: isSavingReusableBlock( ref ),
isTemporary: reusableBlock?.isTemporary ?? null,
blocks: reusableBlock ? parse( reusableBlock.content ) : null,
canUpdateBlock:
!! reusableBlock &&
! reusableBlock.isTemporary &&
!! canUser( 'update', 'blocks', ref ),
settings: getSettings(),
title: reusableBlock?.title ?? null,
};
},
[ ref ]
);

const {
__experimentalFetchReusableBlocks: fetchReusableBlocks,
__experimentalUpdateReusableBlock: updateReusableBlock,
__experimentalSaveReusableBlock: saveReusableBlock,
} = useDispatch( 'core/editor' );

const fetchReusableBlock = partial( fetchReusableBlocks, ref );
const onChange = partial( updateReusableBlock, ref );
const onSave = partial( saveReusableBlock, ref );

// Start in edit mode when working with a newly created reusable block.
// Start in preview mode when we're working with an existing reusable block.
const [ isEditing, setIsEditing ] = useState( isTemporary ?? false );

// Local state so changes can be made to the reusable block without having to save them.
const [ localTitle, setLocalTitle ] = useState( title );
const [ localBlocks, setLocalBlocks ] = useState( blocks ?? EMPTY_ARRAY );

useEffect( () => {
if ( ! theReusableBlock ) {
fetchReusableBlock();
}
}

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

// If the reusable block was changed by saving another instance of the same
// reusable block in the editor, and if this instance is not currently being
// edited, update the local state of this instance to match.
useEffect( () => {
if ( ! isEditing && ! isSaving ) {
setLocalTitle( title );
}
}
}, [ title, isEditing, isSaving ] );

componentDidUpdate( prevProps ) {
if (
prevProps.reusableBlock !== this.props.reusableBlock &&
this.state.title === null
) {
this.setState( {
title: this.props.reusableBlock.title,
blocks: parse( this.props.reusableBlock.content ),
} );
useEffect( () => {
if ( ! isEditing && ! isSaving ) {
setLocalBlocks( blocks );
}
}

startEditing() {
const { reusableBlock } = this.props;
this.setState( {
isEditing: true,
title: reusableBlock.title,
blocks: parse( reusableBlock.content ),
} );
}

stopEditing() {
this.setState( {
isEditing: false,
title: null,
blocks: [],
} );
}

setBlocks( blocks ) {
this.setState( { blocks } );
}

setTitle( title ) {
this.setState( { title } );
}
}, [ blocks, isEditing, isSaving ] );

save() {
const { onChange, onSave } = this.props;
const { blocks, title } = this.state;
const content = serialize( blocks );
onChange( { title, content } );
function save() {
onChange( { title: localTitle, content: serialize( localBlocks ) } );
onSave();

this.stopEditing();
setIsEditing( false );
}

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

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

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

let element = (
<BlockEditorProvider
settings={ settings }
value={ blocks }
onChange={ this.setBlocks }
onInput={ this.setBlocks }
>
<WritingFlow>
<BlockList />
</WritingFlow>
</BlockEditorProvider>
);

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

return (
<div className="block-library-block__reusable-block-container">
{ ( 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 }
/>
) }
{ element }
</div>
<Placeholder>
{ __( 'Block has been deleted or is unavailable.' ) }
</Placeholder>
);
}
}

export default compose( [
withSelect( ( select, ownProps ) => {
const {
__experimentalGetReusableBlock: getReusableBlock,
__experimentalIsFetchingReusableBlock: isFetchingReusableBlock,
__experimentalIsSavingReusableBlock: isSavingReusableBlock,
} = select( 'core/editor' );
const { canUser } = select( 'core' );
const { __experimentalGetParsedReusableBlock, getSettings } = select(
'core/block-editor'
);
const { ref } = ownProps.attributes;
const reusableBlock = getReusableBlock( ref );

return {
reusableBlock,
isFetching: isFetchingReusableBlock( ref ),
isSaving: isSavingReusableBlock( ref ),
blocks: reusableBlock
? __experimentalGetParsedReusableBlock( reusableBlock.id )
: null,
canUpdateBlock:
!! reusableBlock &&
! reusableBlock.isTemporary &&
!! canUser( 'update', 'blocks', ref ),
settings: getSettings(),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
const {
__experimentalFetchReusableBlocks: fetchReusableBlocks,
__experimentalUpdateReusableBlock: updateReusableBlock,
__experimentalSaveReusableBlock: saveReusableBlock,
} = dispatch( 'core/editor' );
const { ref } = ownProps.attributes;

return {
fetchReusableBlock: partial( fetchReusableBlocks, ref ),
onChange: partial( updateReusableBlock, ref ),
onSave: partial( saveReusableBlock, ref ),
};
} ),
] )( ReusableBlockEdit );
let element = (
<BlockEditorProvider
settings={ settings }
value={ localBlocks }
onChange={ setLocalBlocks }
onInput={ setLocalBlocks }
>
<WritingFlow>
<BlockList />
</WritingFlow>
</BlockEditorProvider>
);

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

return (
<div className="block-library-block__reusable-block-container">
{ ( isSelected || isEditing ) && (
<ReusableBlockEditPanel
isEditing={ isEditing }
title={ localTitle ?? title }
isSaving={ isSaving && ! ( isTemporary ?? false ) }
isEditDisabled={ ! canUpdateBlock }
onEdit={ () => {
setIsEditing( true );
} }
onChangeTitle={ setLocalTitle }
onSave={ save }
onCancel={ () => {
setIsEditing( false );
} }
/>
) }
{ element }
</div>
);
}

0 comments on commit d0c1873

Please sign in to comment.