@wordpress/block-library
Version:
Block library for the WordPress editor.
287 lines (261 loc) • 7 kB
JavaScript
/**
* External dependencies
*/
import classnames from 'classnames';
import { get } from 'lodash';
/**
* WordPress dependencies
*/
import { Component } 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 {
RichText,
MediaPlaceholder,
store as blockEditorStore,
__experimentalGetElementClassName,
} from '@wordpress/block-editor';
import { isBlobURL } from '@wordpress/blob';
import { compose } from '@wordpress/compose';
import {
closeSmall,
chevronLeft,
chevronRight,
edit,
image as imageIcon,
} from '@wordpress/icons';
import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
import { pickRelevantMediaFiles } from './shared';
import {
LINK_DESTINATION_ATTACHMENT,
LINK_DESTINATION_MEDIA,
} from './constants';
const isTemporaryImage = ( id, url ) => ! id && isBlobURL( url );
class GalleryImage extends Component {
constructor() {
super( ...arguments );
this.onSelectImage = this.onSelectImage.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 = {
isEditing: false,
};
}
bindContainer( ref ) {
this.container = ref;
}
onSelectImage() {
if ( ! this.props.isSelected ) {
this.props.onSelect();
}
}
onRemoveImage( event ) {
if (
this.container === this.container.ownerDocument.activeElement &&
this.props.isSelected &&
[ BACKSPACE, DELETE ].indexOf( event.keyCode ) !== -1
) {
event.preventDefault();
this.props.onRemove();
}
}
onEdit() {
this.setState( {
isEditing: true,
} );
}
componentDidUpdate() {
const { image, url, __unstableMarkNextChangeAsNotPersistent } =
this.props;
if ( image && ! url ) {
__unstableMarkNextChangeAsNotPersistent();
this.props.setAttributes( {
url: image.source_url,
alt: image.alt_text,
} );
}
}
deselectOnBlur() {
this.props.onDeselect();
}
onSelectImageFromLibrary( media ) {
const { setAttributes, id, url, alt, caption, sizeSlug } = this.props;
if ( ! media || ! media.url ) {
return;
}
let mediaAttributes = pickRelevantMediaFiles( media, sizeSlug );
// If the current image is temporary but an alt text was meanwhile
// written by the user, make sure the text is not overwritten.
if ( isTemporaryImage( id, url ) ) {
if ( alt ) {
const { alt: omittedAlt, ...restMediaAttributes } =
mediaAttributes;
mediaAttributes = restMediaAttributes;
}
}
// If a caption text was meanwhile written by the user,
// make sure the text is not overwritten by empty captions.
if ( caption && ! get( mediaAttributes, [ 'caption' ] ) ) {
const { caption: omittedCaption, ...restMediaAttributes } =
mediaAttributes;
mediaAttributes = restMediaAttributes;
}
setAttributes( mediaAttributes );
this.setState( {
isEditing: false,
} );
}
onSelectCustomURL( newURL ) {
const { setAttributes, url } = this.props;
if ( newURL !== url ) {
setAttributes( {
url: newURL,
id: undefined,
} );
this.setState( {
isEditing: 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;
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 */
<>
<img
src={ url }
alt={ alt }
data-id={ id }
onKeyDown={ this.onRemoveImage }
tabIndex="0"
aria-label={ ariaLabel }
ref={ this.bindContainer }
/>
{ isBlobURL( url ) && <Spinner /> }
</>
/* eslint-enable jsx-a11y/no-noninteractive-element-interactions */
);
const className = classnames( {
'is-selected': isSelected,
'is-transient': isBlobURL( url ),
} );
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
<figure
className={ className }
onClick={ this.onSelectImage }
onFocus={ this.onSelectImage }
>
{ ! isEditing && ( href ? <a href={ href }>{ img }</a> : img ) }
{ isEditing && (
<MediaPlaceholder
labels={ { title: __( 'Edit gallery image' ) } }
icon={ imageIcon }
onSelect={ this.onSelectImageFromLibrary }
onSelectURL={ this.onSelectCustomURL }
accept="image/*"
allowedTypes={ [ 'image' ] }
value={ { id, src: url } }
/>
) }
<ButtonGroup className="block-library-gallery-item__inline-menu is-left">
<Button
icon={ chevronLeft }
onClick={ isFirstItem ? undefined : onMoveBackward }
label={ __( 'Move image backward' ) }
aria-disabled={ isFirstItem }
disabled={ ! isSelected }
/>
<Button
icon={ chevronRight }
onClick={ isLastItem ? undefined : onMoveForward }
label={ __( 'Move image forward' ) }
aria-disabled={ isLastItem }
disabled={ ! isSelected }
/>
</ButtonGroup>
<ButtonGroup className="block-library-gallery-item__inline-menu is-right">
<Button
icon={ edit }
onClick={ this.onEdit }
label={ __( 'Replace image' ) }
disabled={ ! isSelected }
/>
<Button
icon={ closeSmall }
onClick={ onRemove }
label={ __( 'Remove image' ) }
disabled={ ! isSelected }
/>
</ButtonGroup>
{ ! isEditing && ( isSelected || caption ) && (
<RichText
tagName="figcaption"
className={ __experimentalGetElementClassName(
'caption'
) }
aria-label={ __( 'Image caption text' ) }
placeholder={ isSelected ? __( 'Add caption' ) : null }
value={ caption }
onChange={ ( newCaption ) =>
setAttributes( { caption: newCaption } )
}
inlineToolbar
/>
) }
</figure>
);
}
}
export default compose( [
withSelect( ( select, ownProps ) => {
const { getMedia } = select( coreStore );
const { id } = ownProps;
return {
image: id ? getMedia( parseInt( id, 10 ) ) : null,
};
} ),
withDispatch( ( dispatch ) => {
const { __unstableMarkNextChangeAsNotPersistent } =
dispatch( blockEditorStore );
return {
__unstableMarkNextChangeAsNotPersistent,
};
} ),
] )( GalleryImage );