1 |
|
2 |
|
3 |
|
4 | import classnames from 'classnames';
|
5 | import { filter, pick, map, get } from 'lodash';
|
6 |
|
7 | /**
|
8 | * WordPress dependencies
|
9 | */
|
10 | import {
|
11 | DropZone,
|
12 | FormFileUpload,
|
13 | IconButton,
|
14 | PanelBody,
|
15 | RangeControl,
|
16 | SelectControl,
|
17 | ToggleControl,
|
18 | Toolbar,
|
19 | withNotices,
|
20 | } from '@wordpress/components';
|
21 | import {
|
22 | BlockControls,
|
23 | BlockIcon,
|
24 | MediaPlaceholder,
|
25 | MediaUpload,
|
26 | InspectorControls,
|
27 | } from '@wordpress/block-editor';
|
28 | import { mediaUpload } from '@wordpress/editor';
|
29 | import { Component, Fragment } from '@wordpress/element';
|
30 | import { __, sprintf } from '@wordpress/i18n';
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | import GalleryImage from './gallery-image';
|
36 | import icon from './icon';
|
37 |
|
38 | const MAX_COLUMNS = 8;
|
39 | const linkOptions = [
|
40 | { value: 'attachment', label: __( 'Attachment Page' ) },
|
41 | { value: 'media', label: __( 'Media File' ) },
|
42 | { value: 'none', label: __( 'None' ) },
|
43 | ];
|
44 | const ALLOWED_MEDIA_TYPES = [ 'image' ];
|
45 |
|
46 | export function defaultColumnsNumber( attributes ) {
|
47 | return Math.min( 3, attributes.images.length );
|
48 | }
|
49 |
|
50 | export 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 |
|
56 | class 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 |
|
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 |
|
321 | export default withNotices( GalleryEdit );
|
322 |
|
\ | No newline at end of file |