diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index ac77851911a44..91a9a18679b5a 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -7,14 +7,13 @@ import { get, omit } from 'lodash'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { Button, Spinner, ButtonGroup } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BACKSPACE, DELETE } from '@wordpress/keycodes'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { RichText, MediaPlaceholder } from '@wordpress/block-editor'; import { isBlobURL } from '@wordpress/blob'; -import { compose } from '@wordpress/compose'; import { closeSmall, chevronLeft, @@ -34,105 +33,93 @@ import { const isTemporaryImage = ( id, url ) => ! id && isBlobURL( url ); -class GalleryImage extends Component { - constructor() { - super( ...arguments ); - - this.onSelectImage = this.onSelectImage.bind( this ); - this.onSelectCaption = this.onSelectCaption.bind( this ); - this.onRemoveImage = this.onRemoveImage.bind( this ); - this.bindContainer = this.bindContainer.bind( this ); - this.onEdit = this.onEdit.bind( this ); - this.onSelectImageFromLibrary = this.onSelectImageFromLibrary.bind( - this - ); - this.onSelectCustomURL = this.onSelectCustomURL.bind( this ); - this.state = { - captionSelected: false, - isEditing: false, - }; - } - - bindContainer( ref ) { - this.container = ref; - } - - onSelectCaption() { - if ( ! this.state.captionSelected ) { - this.setState( { - captionSelected: true, +export default function GalleryImage( { + url, + alt, + id, + linkTo, + link, + isFirstItem, + isLastItem, + isSelected, + caption, + sizeSlug, + onSelect, + onRemove, + onMoveForward, + onMoveBackward, + setAttributes, + 'aria-label': ariaLabel, +} ) { + const [ captionSelected, setCaptionSelected ] = useState( false ); + const [ isEditing, setIsEditing ] = useState( false ); + + const container = useRef( null ); + + const image = useSelect( + ( select ) => { + const { getMedia } = select( 'core' ); + + return id ? getMedia( parseInt( id, 10 ) ) : null; + }, + [ id ] + ); + + const { __unstableMarkNextChangeAsNotPersistent } = useDispatch( + 'core/block-editor' + ); + + useEffect( () => { + if ( image && ! url ) { + __unstableMarkNextChangeAsNotPersistent(); + setAttributes( { + url: image.source_url, + alt: image.alt_text, } ); } - if ( ! this.props.isSelected ) { - this.props.onSelect(); + // Unselect the caption so when the user selects other image and comeback + // the caption is not immediately selected. + if ( captionSelected && ! isSelected ) { + setCaptionSelected( false ); } - } + }, [ isSelected, image, url, captionSelected ] ); - onSelectImage() { - if ( ! this.props.isSelected ) { - this.props.onSelect(); + const onSelectCaption = () => { + if ( ! captionSelected ) { + setCaptionSelected( true ); } - if ( this.state.captionSelected ) { - this.setState( { - captionSelected: false, - } ); + if ( ! isSelected ) { + onSelect(); } - } + }; - onRemoveImage( event ) { - if ( - this.container === document.activeElement && - this.props.isSelected && - [ BACKSPACE, DELETE ].indexOf( event.keyCode ) !== -1 - ) { - event.stopPropagation(); - event.preventDefault(); - this.props.onRemove(); + const onSelectImage = () => { + if ( ! isSelected ) { + onSelect(); } - } - onEdit() { - this.setState( { - isEditing: true, - } ); - } - - componentDidUpdate( prevProps ) { - const { - isSelected, - image, - url, - __unstableMarkNextChangeAsNotPersistent, - } = this.props; - if ( image && ! url ) { - __unstableMarkNextChangeAsNotPersistent(); - this.props.setAttributes( { - url: image.source_url, - alt: image.alt_text, - } ); + if ( captionSelected ) { + setCaptionSelected( false ); } + }; - // unselect the caption so when the user selects other image and comeback - // the caption is not immediately selected + const onRemoveImage = ( event ) => { if ( - this.state.captionSelected && - ! isSelected && - prevProps.isSelected + container.current === document.activeElement && + isSelected && + [ BACKSPACE, DELETE ].includes( event.keyCode ) ) { - this.setState( { - captionSelected: false, - } ); + event.stopPropagation(); + event.preventDefault(); + onRemove(); } - } + }; - deselectOnBlur() { - this.props.onDeselect(); - } + const onEdit = () => setIsEditing( true ); - onSelectImageFromLibrary( media ) { - const { setAttributes, id, url, alt, caption, sizeSlug } = this.props; + const onSelectImageFromLibrary = ( media ) => { if ( ! media || ! media.url ) { return; } @@ -154,159 +141,113 @@ class GalleryImage extends Component { } setAttributes( mediaAttributes ); - this.setState( { - isEditing: false, - } ); - } + setIsEditing( false ); + }; - onSelectCustomURL( newURL ) { - const { setAttributes, url } = this.props; + const onSelectCustomURL = ( newURL ) => { if ( newURL !== url ) { setAttributes( { url: newURL, id: undefined, } ); - this.setState( { - isEditing: false, - } ); + setIsEditing( false ); } - } - - render() { - const { - url, - alt, - id, - linkTo, - link, - isFirstItem, - isLastItem, - isSelected, - caption, - onRemove, - onMoveForward, - onMoveBackward, - setAttributes, - 'aria-label': ariaLabel, - } = this.props; - const { isEditing } = this.state; + }; - let href; + let href; - switch ( linkTo ) { - case LINK_DESTINATION_MEDIA: - href = url; - break; - case LINK_DESTINATION_ATTACHMENT: - href = link; - break; - } + switch ( linkTo ) { + case LINK_DESTINATION_MEDIA: + href = url; + break; + case LINK_DESTINATION_ATTACHMENT: + href = link; + break; + } - const img = ( - // Disable reason: Image itself is not meant to be interactive, but should - // direct image selection and unfocus caption fields. - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ - <> - { + { + { isBlobURL( url ) && } + + /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ + ); + + const className = classnames( { + 'is-selected': isSelected, + 'is-transient': isBlobURL( url ), + } ); + + return ( +
+ { ! isEditing && ( href ? { img } : img ) } + { isEditing && ( + - { isBlobURL( url ) && } - - /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ - ); - - const className = classnames( { - 'is-selected': isSelected, - 'is-transient': isBlobURL( url ), - } ); - - return ( -
- { ! isEditing && ( href ? { img } : img ) } - { isEditing && ( - - ) } - -
- ); - } + ) } + +
+ ); } - -export default compose( [ - withSelect( ( select, ownProps ) => { - const { getMedia } = select( 'core' ); - const { id } = ownProps; - - return { - image: id ? getMedia( parseInt( id, 10 ) ) : null, - }; - } ), - withDispatch( ( dispatch ) => { - const { __unstableMarkNextChangeAsNotPersistent } = dispatch( - 'core/block-editor' - ); - return { - __unstableMarkNextChangeAsNotPersistent, - }; - } ), -] )( GalleryImage ); diff --git a/packages/block-library/src/gallery/gallery-image.native.js b/packages/block-library/src/gallery/gallery-image.native.js index 4289485b7ac3c..2350c0077480b 100644 --- a/packages/block-library/src/gallery/gallery-image.native.js +++ b/packages/block-library/src/gallery/gallery-image.native.js @@ -17,7 +17,7 @@ import { requestImageUploadCancelDialog, requestImageFullscreenPreview, } from '@wordpress/react-native-bridge'; -import { Component } from '@wordpress/element'; +import { useEffect, useState } from '@wordpress/element'; import { Image } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { Caption, MediaUploadProgress } from '@wordpress/block-editor'; @@ -39,53 +39,57 @@ const separatorStyle = compose( style.separator, { const buttonStyle = compose( style.button, { aspectRatio: 1 } ); const ICON_SIZE_ARROW = 15; -class GalleryImage extends Component { - constructor() { - super( ...arguments ); - - this.onSelectImage = this.onSelectImage.bind( this ); - this.onSelectCaption = this.onSelectCaption.bind( this ); - this.onMediaPressed = this.onMediaPressed.bind( this ); - this.onCaptionChange = this.onCaptionChange.bind( this ); - this.onSelectMedia = this.onSelectMedia.bind( this ); - - this.updateMediaProgress = this.updateMediaProgress.bind( this ); - this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( - this - ); - this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( - this - ); - this.renderContent = this.renderContent.bind( this ); - - this.state = { - captionSelected: false, - isUploadInProgress: false, - didUploadFail: false, - }; - } - - onSelectCaption() { - if ( ! this.state.captionSelected ) { - this.setState( { - captionSelected: true, +function GalleryImage( { + id, + url, + image, + isSelected, + isBlockSelected, + isFirstItem, + isLastItem, + caption, + isCropped, + getStylesFromColorScheme, + isRTL, + 'aria-label': ariaLabel, + onSelect, + onSelectBlock, + onRemove, + onMoveForward, + onMoveBackward, + setAttributes, +} ) { + const [ captionSelected, setCaptionSelected ] = useState( false ); + const [ isUploadInProgress, setIsUploadInProgress ] = useState( false ); + const [ didUploadFail, setDidUploadFail ] = useState( false ); + + useEffect( () => { + if ( image && ! url ) { + setAttributes( { + url: image.source_url, + alt: image.alt_text, } ); } - if ( ! this.props.isSelected ) { - this.props.onSelect(); + // unselect the caption so when the user selects other image and comeback + // the caption is not immediately selected + if ( captionSelected && ! isSelected ) { + setCaptionSelected( false ); } - } + }, [ isSelected, image, url, captionSelected ] ); - onMediaPressed() { - const { id, url, isSelected } = this.props; - const { - captionSelected, - isUploadInProgress, - didUploadFail, - } = this.state; + const onSelectCaption = () => { + if ( ! captionSelected ) { + setCaptionSelected( true ); + } + + if ( ! isSelected ) { + onSelect(); + } + }; - this.onSelectImage(); + const onMediaPressed = () => { + onSelectImage(); if ( isUploadInProgress ) { requestImageUploadCancelDialog( id ); @@ -97,98 +101,52 @@ class GalleryImage extends Component { } else if ( isSelected && ! captionSelected ) { requestImageFullscreenPreview( url ); } - } + }; - onSelectImage() { - if ( ! this.props.isBlockSelected ) { - this.props.onSelectBlock(); + const onSelectImage = () => { + if ( ! isBlockSelected ) { + onSelectBlock(); } - if ( ! this.props.isSelected ) { - this.props.onSelect(); + if ( ! isSelected ) { + onSelect(); } - if ( this.state.captionSelected ) { - this.setState( { - captionSelected: false, - } ); + if ( captionSelected ) { + setCaptionSelected( false ); } - } + }; - onSelectMedia( media ) { - const { setAttributes } = this.props; + const onSelectMedia = ( media ) => { setAttributes( media ); - } + }; - onCaptionChange( caption ) { - const { setAttributes } = this.props; - setAttributes( { caption } ); - } + const onCaptionChange = ( newCaption ) => { + setAttributes( { newCaption } ); + }; - componentDidUpdate( prevProps ) { - const { isSelected, image, url } = this.props; - if ( image && ! url ) { - this.props.setAttributes( { - url: image.source_url, - alt: image.alt_text, - } ); + const updateMediaProgress = () => { + if ( ! isUploadInProgress ) { + setIsUploadInProgress( true ); } + }; - // unselect the caption so when the user selects other image and comeback - // the caption is not immediately selected - if ( - this.state.captionSelected && - ! isSelected && - prevProps.isSelected - ) { - this.setState( { - captionSelected: false, - } ); - } - } - - updateMediaProgress() { - if ( ! this.state.isUploadInProgress ) { - this.setState( { isUploadInProgress: true } ); - } - } - - finishMediaUploadWithSuccess( payload ) { - this.setState( { - isUploadInProgress: false, - didUploadFail: false, - } ); + const finishMediaUploadWithSuccess = ( payload ) => { + setIsUploadInProgress( false ); + setDidUploadFail( false ); - this.props.setAttributes( { + setAttributes( { id: payload.mediaServerId, url: payload.mediaUrl, } ); - } + }; - finishMediaUploadWithFailure() { - this.setState( { - isUploadInProgress: false, - didUploadFail: true, - } ); - } - - renderContent( params ) { - const { - url, - isFirstItem, - isLastItem, - isSelected, - caption, - onRemove, - onMoveForward, - onMoveBackward, - 'aria-label': ariaLabel, - isCropped, - getStylesFromColorScheme, - isRTL, - } = this.props; - - const { isUploadInProgress, captionSelected } = this.state; + const finishMediaUploadWithFailure = () => { + setIsUploadInProgress( false ); + setDidUploadFail( true ); + }; + + const renderContent = ( params ) => { const { isUploadFailed, retryMessage } = params; const resizeMode = isCropped ? 'cover' : 'contain'; @@ -229,7 +187,7 @@ class GalleryImage extends Component { isUploadFailed={ isUploadFailed } isUploadInProgress={ isUploadInProgress } mediaPickerOptions={ mediaPickerOptions } - onSelectMediaUploadOption={ this.onSelectMedia } + onSelectMediaUploadOption={ onSelectMedia } resizeMode={ resizeMode } url={ url } retryMessage={ retryMessage } @@ -284,8 +242,8 @@ class GalleryImage extends Component { ); - } - - render() { - const { - id, - onRemove, - getStylesFromColorScheme, - isSelected, - } = this.props; - - const containerStyle = getStylesFromColorScheme( - style.galleryImageContainer, - style.galleryImageContainerDark - ); + }; - return ( - - - - - - ); - } - - accessibilityLabelImageContainer() { - const { caption, 'aria-label': ariaLabel } = this.props; + const containerStyle = getStylesFromColorScheme( + style.galleryImageContainer, + style.galleryImageContainerDark + ); + const accessibilityLabelImageContainer = () => { return isEmpty( caption ) ? ariaLabel : ariaLabel + @@ -354,7 +277,31 @@ class GalleryImage extends Component { __( 'Image caption. %s' ), caption ); - } + }; + + return ( + + + + + + ); } export default withPreferredColorScheme( GalleryImage );