-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add conditional dark theme styles depending on image/video colors and…
… selected overlay colors
- Loading branch information
1 parent
7f9448e
commit ef5d061
Showing
5 changed files
with
386 additions
and
235 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,367 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import classnames from 'classnames'; | ||
import FastAverageColor from 'fast-average-color'; | ||
import tinycolor from 'tinycolor2'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
FocalPointPicker, | ||
IconButton, | ||
PanelBody, | ||
RangeControl, | ||
ToggleControl, | ||
Toolbar, | ||
withNotices, | ||
} from '@wordpress/components'; | ||
import { compose } from '@wordpress/compose'; | ||
import { | ||
BlockControls, | ||
BlockIcon, | ||
InnerBlocks, | ||
InspectorControls, | ||
MediaPlaceholder, | ||
MediaUpload, | ||
MediaUploadCheck, | ||
PanelColorSettings, | ||
withColors, | ||
} from '@wordpress/editor'; | ||
import { Component, createRef, Fragment } from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import icon from './icon'; | ||
|
||
/** | ||
* Module Constants | ||
*/ | ||
export const IMAGE_BACKGROUND_TYPE = 'image'; | ||
export const VIDEO_BACKGROUND_TYPE = 'video'; | ||
const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ]; | ||
const INNER_BLOCKS_TEMPLATE = [ | ||
[ 'core/paragraph', { | ||
align: 'center', | ||
fontSize: 'large', | ||
placeholder: __( 'Write title…' ), | ||
} ], | ||
]; | ||
const INNER_BLOCKS_ALLOWED_BLOCKS = [ 'core/button', 'core/heading', 'core/paragraph' ]; | ||
|
||
function retrieveFastAverageColor() { | ||
if ( ! retrieveFastAverageColor.fastAverageColor ) { | ||
retrieveFastAverageColor.fastAverageColor = new FastAverageColor(); | ||
} | ||
return retrieveFastAverageColor.fastAverageColor; | ||
} | ||
|
||
export function backgroundImageStyles( url ) { | ||
return url ? | ||
{ backgroundImage: `url(${ url })` } : | ||
{}; | ||
} | ||
|
||
export function dimRatioToClass( ratio ) { | ||
return ( ratio === 0 || ratio === 50 ) ? | ||
null : | ||
'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) ); | ||
} | ||
|
||
class CoverEdit extends Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.state = { | ||
isDark: false, | ||
}; | ||
this.imageRef = createRef(); | ||
this.videoRef = createRef(); | ||
this.changeIsDarkIfRequired = this.changeIsDarkIfRequired.bind( this ); | ||
} | ||
|
||
componentDidMount() { | ||
this.handleBackgroundMode(); | ||
} | ||
|
||
componentDidUpdate( prevProps ) { | ||
this.handleBackgroundMode( prevProps ); | ||
} | ||
|
||
render() { | ||
const { | ||
attributes, | ||
setAttributes, | ||
className, | ||
noticeOperations, | ||
noticeUI, | ||
overlayColor, | ||
setOverlayColor, | ||
} = this.props; | ||
const { | ||
backgroundType, | ||
dimRatio, | ||
focalPoint, | ||
hasParallax, | ||
id, | ||
url, | ||
} = attributes; | ||
const onSelectMedia = ( media ) => { | ||
if ( ! media || ! media.url ) { | ||
setAttributes( { url: undefined, id: undefined } ); | ||
return; | ||
} | ||
let mediaType; | ||
// for media selections originated from a file upload. | ||
if ( media.media_type ) { | ||
if ( media.media_type === IMAGE_BACKGROUND_TYPE ) { | ||
mediaType = IMAGE_BACKGROUND_TYPE; | ||
} else { | ||
// only images and videos are accepted so if the media_type is not an image we can assume it is a video. | ||
// Videos contain the media type of 'file' in the object returned from the rest api. | ||
mediaType = VIDEO_BACKGROUND_TYPE; | ||
} | ||
} else { // for media selections originated from existing files in the media library. | ||
if ( | ||
media.type !== IMAGE_BACKGROUND_TYPE && | ||
media.type !== VIDEO_BACKGROUND_TYPE | ||
) { | ||
return; | ||
} | ||
mediaType = media.type; | ||
} | ||
|
||
setAttributes( { | ||
url: media.url, | ||
id: media.id, | ||
backgroundType: mediaType, | ||
} ); | ||
}; | ||
const toggleParallax = () => setAttributes( { hasParallax: ! hasParallax } ); | ||
const setDimRatio = ( ratio ) => setAttributes( { dimRatio: ratio } ); | ||
|
||
const style = { | ||
...( | ||
backgroundType === IMAGE_BACKGROUND_TYPE ? | ||
backgroundImageStyles( url ) : | ||
{} | ||
), | ||
backgroundColor: overlayColor.color, | ||
}; | ||
|
||
if ( focalPoint ) { | ||
style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; | ||
} | ||
|
||
const controls = ( | ||
<Fragment> | ||
<BlockControls> | ||
{ !! url && ( | ||
<Fragment> | ||
<MediaUploadCheck> | ||
<Toolbar> | ||
<MediaUpload | ||
onSelect={ onSelectMedia } | ||
allowedTypes={ ALLOWED_MEDIA_TYPES } | ||
value={ id } | ||
render={ ( { open } ) => ( | ||
<IconButton | ||
className="components-toolbar__control" | ||
label={ __( 'Edit media' ) } | ||
icon="edit" | ||
onClick={ open } | ||
/> | ||
) } | ||
/> | ||
</Toolbar> | ||
</MediaUploadCheck> | ||
</Fragment> | ||
) } | ||
</BlockControls> | ||
{ !! url && ( | ||
<InspectorControls> | ||
<PanelBody title={ __( 'Cover Settings' ) }> | ||
{ IMAGE_BACKGROUND_TYPE === backgroundType && ( | ||
<ToggleControl | ||
label={ __( 'Fixed Background' ) } | ||
checked={ hasParallax } | ||
onChange={ toggleParallax } | ||
/> | ||
) } | ||
{ IMAGE_BACKGROUND_TYPE === backgroundType && ! hasParallax && ( | ||
<FocalPointPicker | ||
label={ __( 'Focal Point Picker' ) } | ||
url={ url } | ||
value={ focalPoint } | ||
onChange={ ( value ) => setAttributes( { focalPoint: value } ) } | ||
/> | ||
) } | ||
<PanelColorSettings | ||
title={ __( 'Overlay' ) } | ||
initialOpen={ true } | ||
colorSettings={ [ { | ||
value: overlayColor.color, | ||
onChange: setOverlayColor, | ||
label: __( 'Overlay Color' ), | ||
} ] } | ||
> | ||
<RangeControl | ||
label={ __( 'Background Opacity' ) } | ||
value={ dimRatio } | ||
onChange={ setDimRatio } | ||
min={ 0 } | ||
max={ 100 } | ||
step={ 10 } | ||
/> | ||
</PanelColorSettings> | ||
</PanelBody> | ||
</InspectorControls> | ||
) } | ||
</Fragment> | ||
); | ||
|
||
if ( ! url ) { | ||
const placeholderIcon = <BlockIcon icon={ icon } />; | ||
const label = __( 'Cover' ); | ||
|
||
return ( | ||
<Fragment> | ||
{ controls } | ||
<MediaPlaceholder | ||
icon={ placeholderIcon } | ||
className={ className } | ||
labels={ { | ||
title: label, | ||
instructions: __( 'Drag an image or a video, upload a new one or select a file from your library.' ), | ||
} } | ||
onSelect={ onSelectMedia } | ||
accept="image/*,video/*" | ||
allowedTypes={ ALLOWED_MEDIA_TYPES } | ||
notices={ noticeUI } | ||
onError={ noticeOperations.createErrorNotice } | ||
/> | ||
</Fragment> | ||
); | ||
} | ||
|
||
const classes = classnames( | ||
className, | ||
dimRatioToClass( dimRatio ), | ||
{ | ||
'is-dark-theme': this.state.isDark, | ||
'has-background-dim': dimRatio !== 0, | ||
'has-parallax': hasParallax, | ||
} | ||
); | ||
|
||
return ( | ||
<Fragment> | ||
{ controls } | ||
<div | ||
data-url={ url } | ||
style={ style } | ||
className={ classes } | ||
> | ||
{ IMAGE_BACKGROUND_TYPE === backgroundType && ( | ||
// Used only to programmatically check if the image is dark or not | ||
<img | ||
ref={ this.imageRef } | ||
aria-hidden | ||
alt="" | ||
style={ { | ||
display: 'none', | ||
} } | ||
src={ url } | ||
/> | ||
) } | ||
{ VIDEO_BACKGROUND_TYPE === backgroundType && ( | ||
<video | ||
ref={ this.videoRef } | ||
className="wp-block-cover__video-background" | ||
autoPlay | ||
muted | ||
loop | ||
src={ url } | ||
/> | ||
) } | ||
<div className="wp-block-cover__inner-container"> | ||
<InnerBlocks | ||
template={ INNER_BLOCKS_TEMPLATE } | ||
allowedBlocks={ INNER_BLOCKS_ALLOWED_BLOCKS } | ||
/> | ||
</div> | ||
</div> | ||
</Fragment> | ||
); | ||
} | ||
|
||
handleBackgroundMode( prevProps ) { | ||
const { attributes, overlayColor } = this.props; | ||
const { dimRatio, url } = attributes; | ||
// If opacity is greater than 50 the dominant color is the overlay color, | ||
// so use that color for the dark mode computation. | ||
if ( dimRatio > 50 ) { | ||
if ( | ||
prevProps && | ||
prevProps.attributes.dimRatio > 50 && | ||
prevProps.overlayColor.color === overlayColor.color | ||
) { | ||
// No relevant prop changes happened there is no need to apply any change. | ||
return; | ||
} | ||
if ( ! overlayColor.color ) { | ||
// If no overlay color exists the overlay color is block (isDark ) | ||
this.changeIsDarkIfRequired( true ); | ||
return; | ||
} | ||
this.changeIsDarkIfRequired( | ||
tinycolor( overlayColor.color ).isDark() | ||
); | ||
return; | ||
} | ||
// If opacity is lower than 50 the dominant color is the image or video color, | ||
// so use that color for the dark mode computation. | ||
|
||
if ( | ||
prevProps && | ||
prevProps.attributes.dimRatio <= 50 && | ||
prevProps.attributes.url === url | ||
) { | ||
// No relevant prop changes happened there is no need to apply any change. | ||
return; | ||
} | ||
const { backgroundType } = attributes; | ||
|
||
let element; | ||
|
||
switch ( backgroundType ) { | ||
case IMAGE_BACKGROUND_TYPE: | ||
element = this.imageRef.current; | ||
break; | ||
case VIDEO_BACKGROUND_TYPE: | ||
element = this.videoRef.current; | ||
break; | ||
} | ||
if ( ! element ) { | ||
return; | ||
} | ||
retrieveFastAverageColor().getColorAsync( element, ( color ) => { | ||
this.changeIsDarkIfRequired( color.isDark ); | ||
} ); | ||
} | ||
|
||
changeIsDarkIfRequired( newIsDark ) { | ||
if ( this.state.isDark !== newIsDark ) { | ||
this.setState( { | ||
isDark: newIsDark, | ||
} ); | ||
} | ||
} | ||
} | ||
|
||
export default compose( [ | ||
withColors( { overlayColor: 'background-color' } ), | ||
withNotices, | ||
] )( CoverEdit ); |
Oops, something went wrong.