1 |
|
2 |
|
3 |
|
4 | import classnames from 'classnames';
|
5 | import FastAverageColor from 'fast-average-color';
|
6 | import tinycolor from 'tinycolor2';
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | import {
|
12 | FocalPointPicker,
|
13 | IconButton,
|
14 | PanelBody,
|
15 | RangeControl,
|
16 | ToggleControl,
|
17 | Toolbar,
|
18 | withNotices,
|
19 | } from '@wordpress/components';
|
20 | import { compose } from '@wordpress/compose';
|
21 | import {
|
22 | BlockControls,
|
23 | BlockIcon,
|
24 | InnerBlocks,
|
25 | InspectorControls,
|
26 | MediaPlaceholder,
|
27 | MediaUpload,
|
28 | MediaUploadCheck,
|
29 | PanelColorSettings,
|
30 | withColors,
|
31 | } from '@wordpress/editor';
|
32 | import { Component, createRef, Fragment } from '@wordpress/element';
|
33 | import { __ } from '@wordpress/i18n';
|
34 |
|
35 |
|
36 |
|
37 |
|
38 | import icon from './icon';
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | export const IMAGE_BACKGROUND_TYPE = 'image';
|
44 | export const VIDEO_BACKGROUND_TYPE = 'video';
|
45 | const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ];
|
46 | const INNER_BLOCKS_TEMPLATE = [
|
47 | [ 'core/paragraph', {
|
48 | align: 'center',
|
49 | fontSize: 'large',
|
50 | placeholder: __( 'Write title…' ),
|
51 | } ],
|
52 | ];
|
53 | const INNER_BLOCKS_ALLOWED_BLOCKS = [ 'core/button', 'core/heading', 'core/paragraph' ];
|
54 |
|
55 | function retrieveFastAverageColor() {
|
56 | if ( ! retrieveFastAverageColor.fastAverageColor ) {
|
57 | retrieveFastAverageColor.fastAverageColor = new FastAverageColor();
|
58 | }
|
59 | return retrieveFastAverageColor.fastAverageColor;
|
60 | }
|
61 |
|
62 | export function backgroundImageStyles( url ) {
|
63 | return url ?
|
64 | { backgroundImage: `url(${ url })` } :
|
65 | {};
|
66 | }
|
67 |
|
68 | export function dimRatioToClass( ratio ) {
|
69 | return ( ratio === 0 || ratio === 50 ) ?
|
70 | null :
|
71 | 'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) );
|
72 | }
|
73 |
|
74 | class 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 |
|
118 | if ( media.media_type ) {
|
119 | if ( media.media_type === IMAGE_BACKGROUND_TYPE ) {
|
120 | mediaType = IMAGE_BACKGROUND_TYPE;
|
121 | } else {
|
122 |
|
123 |
|
124 | mediaType = VIDEO_BACKGROUND_TYPE;
|
125 | }
|
126 | } else {
|
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 |
|
365 | export default compose( [
|
366 | withColors( { overlayColor: 'background-color' } ),
|
367 | withNotices,
|
368 | ] )( CoverEdit );
|