UNPKG

@wordpress/block-library

Version:
322 lines (292 loc) 8.27 kB
/** * 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 );