@wordpress/block-library
Version:
Block library for the WordPress editor.
413 lines (376 loc) • 10.4 kB
JavaScript
/**
* External dependencies
*/
import classnames from 'classnames';
import { extend } from 'colord';
import namesPlugin from 'colord/plugins/names';
/**
* WordPress dependencies
*/
import { useEntityProp, store as coreStore } from '@wordpress/core-data';
import { useEffect, useRef } from '@wordpress/element';
import { Placeholder, Spinner } from '@wordpress/components';
import { compose } from '@wordpress/compose';
import {
withColors,
ColorPalette,
useBlockProps,
useSetting,
useInnerBlocksProps,
__experimentalUseGradient,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import { useSelect, useDispatch } from '@wordpress/data';
import { isBlobURL } from '@wordpress/blob';
import { store as noticesStore } from '@wordpress/notices';
/**
* Internal dependencies
*/
import {
attributesFromMedia,
IMAGE_BACKGROUND_TYPE,
VIDEO_BACKGROUND_TYPE,
dimRatioToClass,
isContentPositionCenter,
getPositionClassName,
mediaPosition,
} from '../shared';
import useCoverIsDark from './use-cover-is-dark';
import CoverInspectorControls from './inspector-controls';
import CoverBlockControls from './block-controls';
import CoverPlaceholder from './cover-placeholder';
import ResizableCover from './resizable-cover';
extend( [ namesPlugin ] );
function getInnerBlocksTemplate( attributes ) {
return [
[
'core/paragraph',
{
align: 'center',
placeholder: __( 'Write title…' ),
...attributes,
},
],
];
}
/**
* Is the URL a temporary blob URL? A blob URL is one that is used temporarily while
* the media (image or video) is being uploaded and will not have an id allocated yet.
*
* @param {number} id The id of the media.
* @param {string} url The url of the media.
*
* @return {boolean} Is the URL a Blob URL.
*/
const isTemporaryMedia = ( id, url ) => ! id && isBlobURL( url );
function CoverEdit( {
attributes,
clientId,
isSelected,
overlayColor,
setAttributes,
setOverlayColor,
toggleSelection,
context: { postId, postType },
} ) {
const {
contentPosition,
id,
useFeaturedImage,
dimRatio,
focalPoint,
hasParallax,
isDark,
isRepeated,
minHeight,
minHeightUnit,
alt,
allowedBlocks,
templateLock,
} = attributes;
const [ featuredImage ] = useEntityProp(
'postType',
postType,
'featured_media',
postId
);
const media = useSelect(
( select ) =>
featuredImage &&
select( coreStore ).getMedia( featuredImage, { context: 'view' } ),
[ featuredImage ]
);
const mediaUrl = media?.source_url;
// instead of destructuring the attributes
// we define the url and background type
// depending on the value of the useFeaturedImage flag
// to preview in edit the dynamic featured image
const url = useFeaturedImage ? mediaUrl : attributes.url;
const backgroundType = useFeaturedImage
? IMAGE_BACKGROUND_TYPE
: attributes.backgroundType;
const { __unstableMarkNextChangeAsNotPersistent } =
useDispatch( blockEditorStore );
const { createErrorNotice } = useDispatch( noticesStore );
const { gradientClass, gradientValue } = __experimentalUseGradient();
const onSelectMedia = attributesFromMedia( setAttributes, dimRatio );
const isUploadingMedia = isTemporaryMedia( id, url );
const onUploadError = ( message ) => {
createErrorNotice( message, { type: 'snackbar' } );
};
const mediaElement = useRef();
const isCoverDark = useCoverIsDark(
url,
dimRatio,
overlayColor.color,
mediaElement
);
useEffect( () => {
// This side-effect should not create an undo level.
__unstableMarkNextChangeAsNotPersistent();
setAttributes( { isDark: isCoverDark } );
}, [ isCoverDark ] );
const isImageBackground = IMAGE_BACKGROUND_TYPE === backgroundType;
const isVideoBackground = VIDEO_BACKGROUND_TYPE === backgroundType;
const minHeightWithUnit =
minHeight && minHeightUnit
? `${ minHeight }${ minHeightUnit }`
: minHeight;
const isImgElement = ! ( hasParallax || isRepeated );
const style = {
minHeight: minHeightWithUnit || undefined,
};
const backgroundImage = url ? `url(${ url })` : undefined;
const backgroundPosition = mediaPosition( focalPoint );
const bgStyle = { backgroundColor: overlayColor.color };
const mediaStyle = {
objectPosition:
focalPoint && isImgElement
? mediaPosition( focalPoint )
: undefined,
};
const hasBackground = !! ( url || overlayColor.color || gradientValue );
const hasInnerBlocks = useSelect(
( select ) =>
select( blockEditorStore ).getBlock( clientId ).innerBlocks.length >
0,
[ clientId ]
);
const ref = useRef();
const blockProps = useBlockProps( { ref } );
// Check for fontSize support before we pass a fontSize attribute to the innerBlocks.
const hasFontSizes = !! useSetting( 'typography.fontSizes' )?.length;
const innerBlocksTemplate = getInnerBlocksTemplate( {
fontSize: hasFontSizes ? 'large' : undefined,
} );
const innerBlocksProps = useInnerBlocksProps(
{
className: 'wp-block-cover__inner-container',
},
{
template: innerBlocksTemplate,
templateInsertUpdatesSelection: true,
allowedBlocks,
templateLock,
}
);
const currentSettings = {
isVideoBackground,
isImageBackground,
mediaElement,
hasInnerBlocks,
url,
isImgElement,
overlayColor,
};
const toggleUseFeaturedImage = () => {
setAttributes( {
id: undefined,
url: undefined,
useFeaturedImage: ! useFeaturedImage,
dimRatio: dimRatio === 100 ? 50 : dimRatio,
backgroundType: useFeaturedImage
? IMAGE_BACKGROUND_TYPE
: undefined,
} );
};
const blockControls = (
<CoverBlockControls
attributes={ attributes }
setAttributes={ setAttributes }
onSelectMedia={ onSelectMedia }
currentSettings={ currentSettings }
toggleUseFeaturedImage={ toggleUseFeaturedImage }
/>
);
const inspectorControls = (
<CoverInspectorControls
attributes={ attributes }
setAttributes={ setAttributes }
clientId={ clientId }
setOverlayColor={ setOverlayColor }
coverRef={ ref }
currentSettings={ currentSettings }
toggleUseFeaturedImage={ toggleUseFeaturedImage }
/>
);
if ( ! useFeaturedImage && ! hasInnerBlocks && ! hasBackground ) {
return (
<>
{ blockControls }
{ inspectorControls }
<div
{ ...blockProps }
className={ classnames(
'is-placeholder',
blockProps.className
) }
>
<CoverPlaceholder
onSelectMedia={ onSelectMedia }
onError={ onUploadError }
style={ {
minHeight: minHeightWithUnit || undefined,
} }
toggleUseFeaturedImage={ toggleUseFeaturedImage }
>
<div className="wp-block-cover__placeholder-background-options">
<ColorPalette
disableCustomColors={ true }
value={ overlayColor.color }
onChange={ setOverlayColor }
clearable={ false }
/>
</div>
</CoverPlaceholder>
<ResizableCover
className="block-library-cover__resize-container"
onResizeStart={ () => {
setAttributes( { minHeightUnit: 'px' } );
toggleSelection( false );
} }
onResize={ ( value ) => {
setAttributes( { minHeight: value } );
} }
onResizeStop={ ( newMinHeight ) => {
toggleSelection( true );
setAttributes( { minHeight: newMinHeight } );
} }
showHandle={ isSelected }
/>
</div>
</>
);
}
const classes = classnames(
{
'is-dark-theme': isDark,
'is-light': ! isDark,
'is-transient': isUploadingMedia,
'has-parallax': hasParallax,
'is-repeated': isRepeated,
'has-custom-content-position':
! isContentPositionCenter( contentPosition ),
},
getPositionClassName( contentPosition )
);
return (
<>
{ blockControls }
{ inspectorControls }
<div
{ ...blockProps }
className={ classnames( classes, blockProps.className ) }
style={ { ...style, ...blockProps.style } }
data-url={ url }
>
<ResizableCover
className="block-library-cover__resize-container"
onResizeStart={ () => {
setAttributes( { minHeightUnit: 'px' } );
toggleSelection( false );
} }
onResize={ ( value ) => {
setAttributes( { minHeight: value } );
} }
onResizeStop={ ( newMinHeight ) => {
toggleSelection( true );
setAttributes( { minHeight: newMinHeight } );
} }
showHandle={ isSelected }
/>
{ ( ! useFeaturedImage || url ) && (
<span
aria-hidden="true"
className={ classnames(
'wp-block-cover__background',
dimRatioToClass( dimRatio ),
{
[ overlayColor.class ]: overlayColor.class,
'has-background-dim': dimRatio !== undefined,
// For backwards compatibility. Former versions of the Cover Block applied
// `.wp-block-cover__gradient-background` in the presence of
// media, a gradient and a dim.
'wp-block-cover__gradient-background':
url && gradientValue && dimRatio !== 0,
'has-background-gradient': gradientValue,
[ gradientClass ]: gradientClass,
}
) }
style={ { backgroundImage: gradientValue, ...bgStyle } }
/>
) }
{ ! url && useFeaturedImage && (
<Placeholder
className="wp-block-cover__image--placeholder-image"
withIllustration={ true }
/>
) }
{ url &&
isImageBackground &&
( isImgElement ? (
<img
ref={ mediaElement }
className="wp-block-cover__image-background"
alt={ alt }
src={ url }
style={ mediaStyle }
/>
) : (
<div
ref={ mediaElement }
role="img"
className={ classnames(
classes,
'wp-block-cover__image-background'
) }
style={ { backgroundImage, backgroundPosition } }
/>
) ) }
{ url && isVideoBackground && (
<video
ref={ mediaElement }
className="wp-block-cover__video-background"
autoPlay
muted
loop
src={ url }
style={ mediaStyle }
/>
) }
{ isUploadingMedia && <Spinner /> }
<CoverPlaceholder
disableMediaButtons
onSelectMedia={ onSelectMedia }
onError={ onUploadError }
toggleUseFeaturedImage={ toggleUseFeaturedImage }
/>
<div { ...innerBlocksProps } />
</div>
</>
);
}
export default compose( [
withColors( { overlayColor: 'background-color' } ),
] )( CoverEdit );