UNPKG

8.91 kBJavaScriptView Raw
1/**
2 * External dependencies
3 */
4import classnames from 'classnames';
5import FastAverageColor from 'fast-average-color';
6import tinycolor from 'tinycolor2';
7
8/**
9 * WordPress dependencies
10 */
11import {
12 FocalPointPicker,
13 IconButton,
14 PanelBody,
15 RangeControl,
16 ToggleControl,
17 Toolbar,
18 withNotices,
19} from '@wordpress/components';
20import { compose } from '@wordpress/compose';
21import {
22 BlockControls,
23 BlockIcon,
24 InnerBlocks,
25 InspectorControls,
26 MediaPlaceholder,
27 MediaUpload,
28 MediaUploadCheck,
29 PanelColorSettings,
30 withColors,
31} from '@wordpress/editor';
32import { Component, createRef, Fragment } from '@wordpress/element';
33import { __ } from '@wordpress/i18n';
34
35/**
36 * Internal dependencies
37 */
38import icon from './icon';
39
40/**
41 * Module Constants
42 */
43export const IMAGE_BACKGROUND_TYPE = 'image';
44export const VIDEO_BACKGROUND_TYPE = 'video';
45const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ];
46const INNER_BLOCKS_TEMPLATE = [
47 [ 'core/paragraph', {
48 align: 'center',
49 fontSize: 'large',
50 placeholder: __( 'Write title…' ),
51 } ],
52];
53const INNER_BLOCKS_ALLOWED_BLOCKS = [ 'core/button', 'core/heading', 'core/paragraph' ];
54
55function retrieveFastAverageColor() {
56 if ( ! retrieveFastAverageColor.fastAverageColor ) {
57 retrieveFastAverageColor.fastAverageColor = new FastAverageColor();
58 }
59 return retrieveFastAverageColor.fastAverageColor;
60}
61
62export function backgroundImageStyles( url ) {
63 return url ?
64 { backgroundImage: `url(${ url })` } :
65 {};
66}
67
68export function dimRatioToClass( ratio ) {
69 return ( ratio === 0 || ratio === 50 ) ?
70 null :
71 'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) );
72}
73
74class CoverEdit extends Component {
75 constructor() {
76 super( ...arguments );
77 this.state = {
78 isDark: false,
79 };
80 this.imageRef = createRef();
81 this.videoRef = createRef();
82 this.changeIsDarkIfRequired = this.changeIsDarkIfRequired.bind( this );
83 }
84
85 componentDidMount() {
86 this.handleBackgroundMode();
87 }
88
89 componentDidUpdate( prevProps ) {
90 this.handleBackgroundMode( prevProps );
91 }
92
93 render() {
94 const {
95 attributes,
96 setAttributes,
97 className,
98 noticeOperations,
99 noticeUI,
100 overlayColor,
101 setOverlayColor,
102 } = this.props;
103 const {
104 backgroundType,
105 dimRatio,
106 focalPoint,
107 hasParallax,
108 id,
109 url,
110 } = attributes;
111 const onSelectMedia = ( media ) => {
112 if ( ! media || ! media.url ) {
113 setAttributes( { url: undefined, id: undefined } );
114 return;
115 }
116 let mediaType;
117 // for media selections originated from a file upload.
118 if ( media.media_type ) {
119 if ( media.media_type === IMAGE_BACKGROUND_TYPE ) {
120 mediaType = IMAGE_BACKGROUND_TYPE;
121 } else {
122 // only images and videos are accepted so if the media_type is not an image we can assume it is a video.
123 // Videos contain the media type of 'file' in the object returned from the rest api.
124 mediaType = VIDEO_BACKGROUND_TYPE;
125 }
126 } else { // for media selections originated from existing files in the media library.
127 if (
128 media.type !== IMAGE_BACKGROUND_TYPE &&
129 media.type !== VIDEO_BACKGROUND_TYPE
130 ) {
131 return;
132 }
133 mediaType = media.type;
134 }
135
136 setAttributes( {
137 url: media.url,
138 id: media.id,
139 backgroundType: mediaType,
140 } );
141 };
142 const toggleParallax = () => setAttributes( { hasParallax: ! hasParallax } );
143 const setDimRatio = ( ratio ) => setAttributes( { dimRatio: ratio } );
144
145 const style = {
146 ...(
147 backgroundType === IMAGE_BACKGROUND_TYPE ?
148 backgroundImageStyles( url ) :
149 {}
150 ),
151 backgroundColor: overlayColor.color,
152 };
153
154 if ( focalPoint ) {
155 style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`;
156 }
157
158 const controls = (
159 <Fragment>
160 <BlockControls>
161 { !! url && (
162 <Fragment>
163 <MediaUploadCheck>
164 <Toolbar>
165 <MediaUpload
166 onSelect={ onSelectMedia }
167 allowedTypes={ ALLOWED_MEDIA_TYPES }
168 value={ id }
169 render={ ( { open } ) => (
170 <IconButton
171 className="components-toolbar__control"
172 label={ __( 'Edit media' ) }
173 icon="edit"
174 onClick={ open }
175 />
176 ) }
177 />
178 </Toolbar>
179 </MediaUploadCheck>
180 </Fragment>
181 ) }
182 </BlockControls>
183 { !! url && (
184 <InspectorControls>
185 <PanelBody title={ __( 'Cover Settings' ) }>
186 { IMAGE_BACKGROUND_TYPE === backgroundType && (
187 <ToggleControl
188 label={ __( 'Fixed Background' ) }
189 checked={ hasParallax }
190 onChange={ toggleParallax }
191 />
192 ) }
193 { IMAGE_BACKGROUND_TYPE === backgroundType && ! hasParallax && (
194 <FocalPointPicker
195 label={ __( 'Focal Point Picker' ) }
196 url={ url }
197 value={ focalPoint }
198 onChange={ ( value ) => setAttributes( { focalPoint: value } ) }
199 />
200 ) }
201 <PanelColorSettings
202 title={ __( 'Overlay' ) }
203 initialOpen={ true }
204 colorSettings={ [ {
205 value: overlayColor.color,
206 onChange: setOverlayColor,
207 label: __( 'Overlay Color' ),
208 } ] }
209 >
210 <RangeControl
211 label={ __( 'Background Opacity' ) }
212 value={ dimRatio }
213 onChange={ setDimRatio }
214 min={ 0 }
215 max={ 100 }
216 step={ 10 }
217 required
218 />
219 </PanelColorSettings>
220 </PanelBody>
221 </InspectorControls>
222 ) }
223 </Fragment>
224 );
225
226 if ( ! url ) {
227 const placeholderIcon = <BlockIcon icon={ icon } />;
228 const label = __( 'Cover' );
229
230 return (
231 <Fragment>
232 { controls }
233 <MediaPlaceholder
234 icon={ placeholderIcon }
235 className={ className }
236 labels={ {
237 title: label,
238 instructions: __( 'Drag an image or a video, upload a new one or select a file from your library.' ),
239 } }
240 onSelect={ onSelectMedia }
241 accept="image/*,video/*"
242 allowedTypes={ ALLOWED_MEDIA_TYPES }
243 notices={ noticeUI }
244 onError={ noticeOperations.createErrorNotice }
245 />
246 </Fragment>
247 );
248 }
249
250 const classes = classnames(
251 className,
252 dimRatioToClass( dimRatio ),
253 {
254 'is-dark-theme': this.state.isDark,
255 'has-background-dim': dimRatio !== 0,
256 'has-parallax': hasParallax,
257 }
258 );
259
260 return (
261 <Fragment>
262 { controls }
263 <div
264 data-url={ url }
265 style={ style }
266 className={ classes }
267 >
268 { IMAGE_BACKGROUND_TYPE === backgroundType && (
269 // Used only to programmatically check if the image is dark or not
270 <img
271 ref={ this.imageRef }
272 aria-hidden
273 alt=""
274 style={ {
275 display: 'none',
276 } }
277 src={ url }
278 />
279 ) }
280 { VIDEO_BACKGROUND_TYPE === backgroundType && (
281 <video
282 ref={ this.videoRef }
283 className="wp-block-cover__video-background"
284 autoPlay
285 muted
286 loop
287 src={ url }
288 />
289 ) }
290 <div className="wp-block-cover__inner-container">
291 <InnerBlocks
292 template={ INNER_BLOCKS_TEMPLATE }
293 allowedBlocks={ INNER_BLOCKS_ALLOWED_BLOCKS }
294 />
295 </div>
296 </div>
297 </Fragment>
298 );
299 }
300
301 handleBackgroundMode( prevProps ) {
302 const { attributes, overlayColor } = this.props;
303 const { dimRatio, url } = attributes;
304 // If opacity is greater than 50 the dominant color is the overlay color,
305 // so use that color for the dark mode computation.
306 if ( dimRatio > 50 ) {
307 if (
308 prevProps &&
309 prevProps.attributes.dimRatio > 50 &&
310 prevProps.overlayColor.color === overlayColor.color
311 ) {
312 // No relevant prop changes happened there is no need to apply any change.
313 return;
314 }
315 if ( ! overlayColor.color ) {
316 // If no overlay color exists the overlay color is black (isDark )
317 this.changeIsDarkIfRequired( true );
318 return;
319 }
320 this.changeIsDarkIfRequired(
321 tinycolor( overlayColor.color ).isDark()
322 );
323 return;
324 }
325 // If opacity is lower than 50 the dominant color is the image or video color,
326 // so use that color for the dark mode computation.
327
328 if (
329 prevProps &&
330 prevProps.attributes.dimRatio <= 50 &&
331 prevProps.attributes.url === url
332 ) {
333 // No relevant prop changes happened there is no need to apply any change.
334 return;
335 }
336 const { backgroundType } = attributes;
337
338 let element;
339
340 switch ( backgroundType ) {
341 case IMAGE_BACKGROUND_TYPE:
342 element = this.imageRef.current;
343 break;
344 case VIDEO_BACKGROUND_TYPE:
345 element = this.videoRef.current;
346 break;
347 }
348 if ( ! element ) {
349 return;
350 }
351 retrieveFastAverageColor().getColorAsync( element, ( color ) => {
352 this.changeIsDarkIfRequired( color.isDark );
353 } );
354 }
355
356 changeIsDarkIfRequired( newIsDark ) {
357 if ( this.state.isDark !== newIsDark ) {
358 this.setState( {
359 isDark: newIsDark,
360 } );
361 }
362 }
363}
364
365export default compose( [
366 withColors( { overlayColor: 'background-color' } ),
367 withNotices,
368] )( CoverEdit );