UNPKG

8.27 kBJavaScriptView Raw
1/**
2 * External dependencies
3 */
4import classnames from 'classnames';
5import { filter, pick, map, get } from 'lodash';
6
7/**
8 * WordPress dependencies
9 */
10import {
11 DropZone,
12 FormFileUpload,
13 IconButton,
14 PanelBody,
15 RangeControl,
16 SelectControl,
17 ToggleControl,
18 Toolbar,
19 withNotices,
20} from '@wordpress/components';
21import {
22 BlockControls,
23 BlockIcon,
24 MediaPlaceholder,
25 MediaUpload,
26 InspectorControls,
27} from '@wordpress/block-editor';
28import { mediaUpload } from '@wordpress/editor';
29import { Component, Fragment } from '@wordpress/element';
30import { __, sprintf } from '@wordpress/i18n';
31
32/**
33 * Internal dependencies
34 */
35import GalleryImage from './gallery-image';
36import icon from './icon';
37
38const MAX_COLUMNS = 8;
39const linkOptions = [
40 { value: 'attachment', label: __( 'Attachment Page' ) },
41 { value: 'media', label: __( 'Media File' ) },
42 { value: 'none', label: __( 'None' ) },
43];
44const ALLOWED_MEDIA_TYPES = [ 'image' ];
45
46export function defaultColumnsNumber( attributes ) {
47 return Math.min( 3, attributes.images.length );
48}
49
50export const pickRelevantMediaFiles = ( image ) => {
51 const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] );
52 imageProps.url = get( image, [ 'sizes', 'large', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) || image.url;
53 return imageProps;
54};
55
56class GalleryEdit extends Component {
57 constructor() {
58 super( ...arguments );
59
60 this.onSelectImage = this.onSelectImage.bind( this );
61 this.onSelectImages = this.onSelectImages.bind( this );
62 this.setLinkTo = this.setLinkTo.bind( this );
63 this.setColumnsNumber = this.setColumnsNumber.bind( this );
64 this.toggleImageCrop = this.toggleImageCrop.bind( this );
65 this.onRemoveImage = this.onRemoveImage.bind( this );
66 this.setImageAttributes = this.setImageAttributes.bind( this );
67 this.addFiles = this.addFiles.bind( this );
68 this.uploadFromFiles = this.uploadFromFiles.bind( this );
69 this.setAttributes = this.setAttributes.bind( this );
70
71 this.state = {
72 selectedImage: null,
73 };
74 }
75
76 setAttributes( attributes ) {
77 if ( attributes.ids ) {
78 throw new Error( 'The "ids" attribute should not be changed directly. It is managed automatically when "images" attribute changes' );
79 }
80
81 if ( attributes.images ) {
82 attributes = {
83 ...attributes,
84 ids: map( attributes.images, 'id' ),
85 };
86 }
87
88 this.props.setAttributes( attributes );
89 }
90
91 onSelectImage( index ) {
92 return () => {
93 if ( this.state.selectedImage !== index ) {
94 this.setState( {
95 selectedImage: index,
96 } );
97 }
98 };
99 }
100
101 onRemoveImage( index ) {
102 return () => {
103 const images = filter( this.props.attributes.images, ( img, i ) => index !== i );
104 const { columns } = this.props.attributes;
105 this.setState( { selectedImage: null } );
106 this.setAttributes( {
107 images,
108 columns: columns ? Math.min( images.length, columns ) : columns,
109 } );
110 };
111 }
112
113 onSelectImages( images ) {
114 const { columns } = this.props.attributes;
115 this.setAttributes( {
116 images: images.map( ( image ) => pickRelevantMediaFiles( image ) ),
117 columns: columns ? Math.min( images.length, columns ) : columns,
118 } );
119 }
120
121 setLinkTo( value ) {
122 this.setAttributes( { linkTo: value } );
123 }
124
125 setColumnsNumber( value ) {
126 this.setAttributes( { columns: value } );
127 }
128
129 toggleImageCrop() {
130 this.setAttributes( { imageCrop: ! this.props.attributes.imageCrop } );
131 }
132
133 getImageCropHelp( checked ) {
134 return checked ? __( 'Thumbnails are cropped to align.' ) : __( 'Thumbnails are not cropped.' );
135 }
136
137 setImageAttributes( index, attributes ) {
138 const { attributes: { images } } = this.props;
139 const { setAttributes } = this;
140 if ( ! images[ index ] ) {
141 return;
142 }
143 setAttributes( {
144 images: [
145 ...images.slice( 0, index ),
146 {
147 ...images[ index ],
148 ...attributes,
149 },
150 ...images.slice( index + 1 ),
151 ],
152 } );
153 }
154
155 uploadFromFiles( event ) {
156 this.addFiles( event.target.files );
157 }
158
159 addFiles( files ) {
160 const currentImages = this.props.attributes.images || [];
161 const { noticeOperations } = this.props;
162 const { setAttributes } = this;
163 mediaUpload( {
164 allowedTypes: ALLOWED_MEDIA_TYPES,
165 filesList: files,
166 onFileChange: ( images ) => {
167 const imagesNormalized = images.map( ( image ) => pickRelevantMediaFiles( image ) );
168 setAttributes( {
169 images: currentImages.concat( imagesNormalized ),
170 } );
171 },
172 onError: noticeOperations.createErrorNotice,
173 } );
174 }
175
176 componentDidUpdate( prevProps ) {
177 // Deselect images when deselecting the block
178 if ( ! this.props.isSelected && prevProps.isSelected ) {
179 this.setState( {
180 selectedImage: null,
181 captionSelected: false,
182 } );
183 }
184 }
185
186 render() {
187 const { attributes, isSelected, className, noticeOperations, noticeUI } = this.props;
188 const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes;
189
190 const dropZone = (
191 <DropZone
192 onFilesDrop={ this.addFiles }
193 />
194 );
195
196 const controls = (
197 <BlockControls>
198 { !! images.length && (
199 <Toolbar>
200 <MediaUpload
201 onSelect={ this.onSelectImages }
202 allowedTypes={ ALLOWED_MEDIA_TYPES }
203 multiple
204 gallery
205 value={ images.map( ( img ) => img.id ) }
206 render={ ( { open } ) => (
207 <IconButton
208 className="components-toolbar__control"
209 label={ __( 'Edit gallery' ) }
210 icon="edit"
211 onClick={ open }
212 />
213 ) }
214 />
215 </Toolbar>
216 ) }
217 </BlockControls>
218 );
219
220 if ( images.length === 0 ) {
221 return (
222 <Fragment>
223 { controls }
224 <MediaPlaceholder
225 icon={ <BlockIcon icon={ icon } /> }
226 className={ className }
227 labels={ {
228 title: __( 'Gallery' ),
229 instructions: __( 'Drag images, upload new ones or select files from your library.' ),
230 } }
231 onSelect={ this.onSelectImages }
232 accept="image/*"
233 allowedTypes={ ALLOWED_MEDIA_TYPES }
234 multiple
235 notices={ noticeUI }
236 onError={ noticeOperations.createErrorNotice }
237 />
238 </Fragment>
239 );
240 }
241
242 return (
243 <Fragment>
244 { controls }
245 <InspectorControls>
246 <PanelBody title={ __( 'Gallery Settings' ) }>
247 { images.length > 1 && <RangeControl
248 label={ __( 'Columns' ) }
249 value={ columns }
250 onChange={ this.setColumnsNumber }
251 min={ 1 }
252 max={ Math.min( MAX_COLUMNS, images.length ) }
253 required
254 /> }
255 <ToggleControl
256 label={ __( 'Crop Images' ) }
257 checked={ !! imageCrop }
258 onChange={ this.toggleImageCrop }
259 help={ this.getImageCropHelp }
260 />
261 <SelectControl
262 label={ __( 'Link To' ) }
263 value={ linkTo }
264 onChange={ this.setLinkTo }
265 options={ linkOptions }
266 />
267 </PanelBody>
268 </InspectorControls>
269 { noticeUI }
270 <ul
271 className={ classnames(
272 className,
273 {
274 [ `align${ align }` ]: align,
275 [ `columns-${ columns }` ]: columns,
276 'is-cropped': imageCrop,
277 }
278 ) }
279 >
280 { dropZone }
281 { images.map( ( img, index ) => {
282 /* translators: %1$d is the order number of the image, %2$d is the total number of images. */
283 const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length );
284
285 return (
286 <li className="blocks-gallery-item" key={ img.id || img.url }>
287 <GalleryImage
288 url={ img.url }
289 alt={ img.alt }
290 id={ img.id }
291 isSelected={ isSelected && this.state.selectedImage === index }
292 onRemove={ this.onRemoveImage( index ) }
293 onSelect={ this.onSelectImage( index ) }
294 setAttributes={ ( attrs ) => this.setImageAttributes( index, attrs ) }
295 caption={ img.caption }
296 aria-label={ ariaLabel }
297 />
298 </li>
299 );
300 } ) }
301 { isSelected &&
302 <li className="blocks-gallery-item has-add-item-button">
303 <FormFileUpload
304 multiple
305 isLarge
306 className="block-library-gallery-add-item-button"
307 onChange={ this.uploadFromFiles }
308 accept="image/*"
309 icon="insert"
310 >
311 { __( 'Upload an image' ) }
312 </FormFileUpload>
313 </li>
314 }
315 </ul>
316 </Fragment>
317 );
318 }
319}
320
321export default withNotices( GalleryEdit );
322
\No newline at end of file