diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 296fb704dbfa7e..8d65ef2bab45ac 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -6,207 +6,158 @@ 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: [], +export default function ReusableBlockEdit( { attributes, isSelected } ) { + const { ref } = attributes; + + const { + reusableBlock, + isFetching, + isSaving, + isTemporary, + blocks, + canUpdateBlock, + settings, + title, + } = useSelect( + ( 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 { + reusableBlock: _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 ] + ); - componentDidMount() { - if ( ! this.props.reusableBlock ) { - this.props.fetchReusableBlock(); - } - } + const { + __experimentalFetchReusableBlocks: fetchReusableBlocks, + __experimentalUpdateReusableBlock: updateReusableBlock, + __experimentalSaveReusableBlock: saveReusableBlock, + } = useDispatch( 'core/editor' ); - 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 ), - } ); - } - } + const fetchReusableBlock = partial( fetchReusableBlocks, ref ); + const onChange = partial( updateReusableBlock, ref ); + const onSave = partial( saveReusableBlock, ref ); - startEditing() { - const { reusableBlock } = this.props; - this.setState( { - isEditing: true, - title: reusableBlock.title, - blocks: parse( reusableBlock.content ), - } ); - } + // 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 ); - stopEditing() { - this.setState( { - isEditing: false, - title: null, - blocks: [], - } ); - } + // 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 ?? [] ); - setBlocks( blocks ) { - this.setState( { blocks } ); - } + useEffect( () => { + if ( ! reusableBlock ) { + fetchReusableBlock(); + } + }, [] ); + + // 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 ] ); - setTitle( title ) { - this.setState( { title } ); - } + useEffect( () => { + if ( ! isEditing && ! isSaving ) { + setLocalBlocks( blocks ); + } + }, [ 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 ( ! reusableBlock ) { + if ( isFetching ) { return ( ); } - - if ( ! reusableBlock ) { - return ( - - { __( 'Block has been deleted or is unavailable.' ) } - - ); - } - - let element = ( - - - - - - ); - - if ( ! isEditing ) { - element = { element }; - } - return ( -
- { ( isSelected || isEditing ) && ( - - ) } - { element } -
+ + { __( 'Block has been deleted or is unavailable.' ) } + ); } -} -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 content = ( + + + + + + ); + + if ( ! isEditing ) { + content = { content }; + } + + return ( +
+ { ( isSelected || isEditing ) && ( + { + setIsEditing( true ); + } } + onChangeTitle={ setLocalTitle } + onSave={ save } + onCancel={ () => { + setIsEditing( false ); + } } + /> + ) } + { content } +
+ ); +}