@wordpress/block-library
Version:
Block library for the WordPress editor.
322 lines (292 loc) • 8.27 kB
JavaScript
/**
* External dependencies
*/
import classnames from 'classnames';
import { filter, pick, map, get } from 'lodash';
/**
* WordPress dependencies
*/
import {
DropZone,
FormFileUpload,
IconButton,
PanelBody,
RangeControl,
SelectControl,
ToggleControl,
Toolbar,
withNotices,
} from '@wordpress/components';
import {
BlockControls,
BlockIcon,
MediaPlaceholder,
MediaUpload,
InspectorControls,
} from '@wordpress/block-editor';
import { mediaUpload } from '@wordpress/editor';
import { Component, Fragment } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import GalleryImage from './gallery-image';
import icon from './icon';
const MAX_COLUMNS = 8;
const linkOptions = [
{ value: 'attachment', label: __( 'Attachment Page' ) },
{ value: 'media', label: __( 'Media File' ) },
{ value: 'none', label: __( 'None' ) },
];
const ALLOWED_MEDIA_TYPES = [ 'image' ];
export function defaultColumnsNumber( attributes ) {
return Math.min( 3, attributes.images.length );
}
export const pickRelevantMediaFiles = ( image ) => {
const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] );
imageProps.url = get( image, [ 'sizes', 'large', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) || image.url;
return imageProps;
};
class GalleryEdit extends Component {
constructor() {
super( ...arguments );
this.onSelectImage = this.onSelectImage.bind( this );
this.onSelectImages = this.onSelectImages.bind( this );
this.setLinkTo = this.setLinkTo.bind( this );
this.setColumnsNumber = this.setColumnsNumber.bind( this );
this.toggleImageCrop = this.toggleImageCrop.bind( this );
this.onRemoveImage = this.onRemoveImage.bind( this );
this.setImageAttributes = this.setImageAttributes.bind( this );
this.addFiles = this.addFiles.bind( this );
this.uploadFromFiles = this.uploadFromFiles.bind( this );
this.setAttributes = this.setAttributes.bind( this );
this.state = {
selectedImage: null,
};
}
setAttributes( attributes ) {
if ( attributes.ids ) {
throw new Error( 'The "ids" attribute should not be changed directly. It is managed automatically when "images" attribute changes' );
}
if ( attributes.images ) {
attributes = {
...attributes,
ids: map( attributes.images, 'id' ),
};
}
this.props.setAttributes( attributes );
}
onSelectImage( index ) {
return () => {
if ( this.state.selectedImage !== index ) {
this.setState( {
selectedImage: index,
} );
}
};
}
onRemoveImage( index ) {
return () => {
const images = filter( this.props.attributes.images, ( img, i ) => index !== i );
const { columns } = this.props.attributes;
this.setState( { selectedImage: null } );
this.setAttributes( {
images,
columns: columns ? Math.min( images.length, columns ) : columns,
} );
};
}
onSelectImages( images ) {
const { columns } = this.props.attributes;
this.setAttributes( {
images: images.map( ( image ) => pickRelevantMediaFiles( image ) ),
columns: columns ? Math.min( images.length, columns ) : columns,
} );
}
setLinkTo( value ) {
this.setAttributes( { linkTo: value } );
}
setColumnsNumber( value ) {
this.setAttributes( { columns: value } );
}
toggleImageCrop() {
this.setAttributes( { imageCrop: ! this.props.attributes.imageCrop } );
}
getImageCropHelp( checked ) {
return checked ? __( 'Thumbnails are cropped to align.' ) : __( 'Thumbnails are not cropped.' );
}
setImageAttributes( index, attributes ) {
const { attributes: { images } } = this.props;
const { setAttributes } = this;
if ( ! images[ index ] ) {
return;
}
setAttributes( {
images: [
...images.slice( 0, index ),
{
...images[ index ],
...attributes,
},
...images.slice( index + 1 ),
],
} );
}
uploadFromFiles( event ) {
this.addFiles( event.target.files );
}
addFiles( files ) {
const currentImages = this.props.attributes.images || [];
const { noticeOperations } = this.props;
const { setAttributes } = this;
mediaUpload( {
allowedTypes: ALLOWED_MEDIA_TYPES,
filesList: files,
onFileChange: ( images ) => {
const imagesNormalized = images.map( ( image ) => pickRelevantMediaFiles( image ) );
setAttributes( {
images: currentImages.concat( imagesNormalized ),
} );
},
onError: noticeOperations.createErrorNotice,
} );
}
componentDidUpdate( prevProps ) {
// Deselect images when deselecting the block
if ( ! this.props.isSelected && prevProps.isSelected ) {
this.setState( {
selectedImage: null,
captionSelected: false,
} );
}
}
render() {
const { attributes, isSelected, className, noticeOperations, noticeUI } = this.props;
const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes;
const dropZone = (
<DropZone
onFilesDrop={ this.addFiles }
/>
);
const controls = (
<BlockControls>
{ !! images.length && (
<Toolbar>
<MediaUpload
onSelect={ this.onSelectImages }
allowedTypes={ ALLOWED_MEDIA_TYPES }
multiple
gallery
value={ images.map( ( img ) => img.id ) }
render={ ( { open } ) => (
<IconButton
className="components-toolbar__control"
label={ __( 'Edit gallery' ) }
icon="edit"
onClick={ open }
/>
) }
/>
</Toolbar>
) }
</BlockControls>
);
if ( images.length === 0 ) {
return (
<Fragment>
{ controls }
<MediaPlaceholder
icon={ <BlockIcon icon={ icon } /> }
className={ className }
labels={ {
title: __( 'Gallery' ),
instructions: __( 'Drag images, upload new ones or select files from your library.' ),
} }
onSelect={ this.onSelectImages }
accept="image/*"
allowedTypes={ ALLOWED_MEDIA_TYPES }
multiple
notices={ noticeUI }
onError={ noticeOperations.createErrorNotice }
/>
</Fragment>
);
}
return (
<Fragment>
{ controls }
<InspectorControls>
<PanelBody title={ __( 'Gallery Settings' ) }>
{ images.length > 1 && <RangeControl
label={ __( 'Columns' ) }
value={ columns }
onChange={ this.setColumnsNumber }
min={ 1 }
max={ Math.min( MAX_COLUMNS, images.length ) }
required
/> }
<ToggleControl
label={ __( 'Crop Images' ) }
checked={ !! imageCrop }
onChange={ this.toggleImageCrop }
help={ this.getImageCropHelp }
/>
<SelectControl
label={ __( 'Link To' ) }
value={ linkTo }
onChange={ this.setLinkTo }
options={ linkOptions }
/>
</PanelBody>
</InspectorControls>
{ noticeUI }
<ul
className={ classnames(
className,
{
[ `align${ align }` ]: align,
[ `columns-${ columns }` ]: columns,
'is-cropped': imageCrop,
}
) }
>
{ dropZone }
{ images.map( ( img, index ) => {
/* translators: %1$d is the order number of the image, %2$d is the total number of images. */
const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length );
return (
<li className="blocks-gallery-item" key={ img.id || img.url }>
<GalleryImage
url={ img.url }
alt={ img.alt }
id={ img.id }
isSelected={ isSelected && this.state.selectedImage === index }
onRemove={ this.onRemoveImage( index ) }
onSelect={ this.onSelectImage( index ) }
setAttributes={ ( attrs ) => this.setImageAttributes( index, attrs ) }
caption={ img.caption }
aria-label={ ariaLabel }
/>
</li>
);
} ) }
{ isSelected &&
<li className="blocks-gallery-item has-add-item-button">
<FormFileUpload
multiple
isLarge
className="block-library-gallery-add-item-button"
onChange={ this.uploadFromFiles }
accept="image/*"
icon="insert"
>
{ __( 'Upload an image' ) }
</FormFileUpload>
</li>
}
</ul>
</Fragment>
);
}
}
export default withNotices( GalleryEdit );