UNPKG

@wordpress/block-library

Version:
299 lines (283 loc) • 7.86 kB
import clsx from 'clsx'; import { isBlobURL } from '@wordpress/blob'; import { Spinner, Placeholder, __experimentalToolsPanel as ToolsPanel, } from '@wordpress/components'; import { BlockControls, BlockIcon, InspectorControls, MediaPlaceholder, MediaReplaceFlow, useBlockProps, useBlockEditingMode, __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, } from '@wordpress/block-editor'; import { useRef, useEffect, useState } from '@wordpress/element'; import { useReducedMotion } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; import { inertValue } from '@wordpress/react-inert-value'; import { useDispatch } from '@wordpress/data'; import { video as icon } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; import { prependHTTPS } from '@wordpress/url'; import { createUpgradedEmbedBlock } from '../embed/util'; import { useUploadMediaFromBlobURL, useToolsPanelDropdownMenuProps, } from '../utils/hooks'; import VideoCommonSettings from './edit-common-settings'; import TracksEditor from './tracks-editor'; import Tracks from './tracks'; import { Caption } from '../utils/caption'; import PosterImage from '../utils/poster-image'; const ALLOWED_MEDIA_TYPES = [ 'video' ]; function VideoEdit( { isSelected: isSingleSelected, attributes, className, setAttributes, insertBlocksAfter, onReplace, } ) { const videoPlayer = useRef(); const { id, controls, poster, src, tracks, width, height, autoplay, loop, muted, playsInline, } = attributes; // Give the <video> an explicit (non-`auto`) aspect ratio derived from the // stored dimensions. The width/height attributes alone only yield // `aspect-ratio: auto W/H`, whose `auto` keyword defers to the element's // natural ratio while the poster/metadata load - during which Chrome briefly // computes a runaway height (tens of thousands of pixels) before settling. // That spike is what reads as a duplicated image during the GIF-to-video // swap. A non-`auto` ratio governs the box height throughout the load. const aspectRatio = width && height ? `${ width } / ${ height }` : undefined; const videoStyle = { ...( aspectRatio && { aspectRatio } ), ...getShadowClassesAndStyles( attributes ).style, }; const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob ); const dropdownMenuProps = useToolsPanelDropdownMenuProps(); const blockEditingMode = useBlockEditingMode(); const hasNonContentControls = blockEditingMode === 'default'; const prefersReducedMotion = useReducedMotion(); useUploadMediaFromBlobURL( { url: temporaryURL, allowedTypes: ALLOWED_MEDIA_TYPES, onChange: onSelectVideo, onError: onUploadError, } ); useEffect( () => { // Placeholder may be rendered. if ( videoPlayer.current ) { videoPlayer.current.load(); } }, [ poster ] ); // `autoPlay` only applies when the video loads, so pause a video that is // already playing when the user turns on reduced motion. useEffect( () => { if ( prefersReducedMotion ) { videoPlayer.current?.pause(); } }, [ prefersReducedMotion ] ); // TODO: Whether the video was obtained from the media library or was provided by URL, obtain the `videoWidth` and `videoHeight` of the video once its metadata has loaded and persist in the block attributes. function onSelectVideo( media ) { if ( ! media || ! media.url ) { // In this case there was an error // previous attributes should be removed // because they may be temporary blob urls. setAttributes( { src: undefined, id: undefined, poster: undefined, caption: undefined, blob: undefined, } ); setTemporaryURL(); return; } if ( isBlobURL( media.url ) ) { setTemporaryURL( media.url ); return; } // Sets the block's attribute and updates the edit component from the // selected media. setAttributes( { blob: undefined, src: media.url, id: media.id, poster: media.image?.src !== media.icon ? media.image?.src : undefined, caption: media.caption, } ); setTemporaryURL(); } function onSelectURL( newSrc ) { if ( newSrc !== src ) { const url = prependHTTPS( newSrc ); // Check if there's an embed block that handles this URL. const embedBlock = createUpgradedEmbedBlock( { attributes: { url }, } ); if ( undefined !== embedBlock && onReplace ) { onReplace( embedBlock ); return; } setAttributes( { blob: undefined, src: url, id: undefined, poster: undefined, } ); setTemporaryURL(); } } const { createErrorNotice } = useDispatch( noticesStore ); function onUploadError( message ) { createErrorNotice( message, { type: 'snackbar' } ); } // Much of this description is duplicated from MediaPlaceholder. const placeholder = ( content ) => { return ( <Placeholder className="block-editor-media-placeholder" withIllustration={ ! isSingleSelected } icon={ icon } label={ __( 'Video' ) } instructions={ __( 'Drag and drop a video, upload, or choose from your library.' ) } > { content } </Placeholder> ); }; const classes = clsx( className, { 'is-transient': !! temporaryURL, } ); const blockProps = useBlockProps( { className: classes, } ); if ( ! src && ! temporaryURL ) { return ( <div { ...blockProps }> <MediaPlaceholder icon={ <BlockIcon icon={ icon } /> } onSelect={ onSelectVideo } onSelectURL={ onSelectURL } accept="video/*" allowedTypes={ ALLOWED_MEDIA_TYPES } value={ attributes } onError={ onUploadError } placeholder={ placeholder } /> </div> ); } return ( <> { isSingleSelected && ( <> <BlockControls> <TracksEditor tracks={ tracks } onChange={ ( newTracks ) => { setAttributes( { tracks: newTracks } ); } } /> </BlockControls> <BlockControls group="other"> <MediaReplaceFlow mediaId={ id } mediaURL={ src } allowedTypes={ ALLOWED_MEDIA_TYPES } accept="video/*" onSelect={ onSelectVideo } onSelectURL={ onSelectURL } onError={ onUploadError } onReset={ () => onSelectVideo( undefined ) } variant="toolbar" /> </BlockControls> </> ) } <InspectorControls> <ToolsPanel label={ __( 'Settings' ) } resetAll={ () => { setAttributes( { autoplay: false, controls: true, loop: false, muted: false, playsInline: false, preload: 'metadata', poster: undefined, } ); } } dropdownMenuProps={ dropdownMenuProps } > <VideoCommonSettings setAttributes={ setAttributes } attributes={ attributes } /> <PosterImage poster={ poster } onChange={ ( posterImage ) => setAttributes( { poster: posterImage?.url, } ) } /> </ToolsPanel> </InspectorControls> <figure { ...blockProps }> <video controls={ controls } inert={ inertValue( ! isSingleSelected ) } poster={ poster } src={ src || temporaryURL } ref={ videoPlayer } autoPlay={ autoplay && ! prefersReducedMotion } loop={ loop } muted={ muted } playsInline={ playsInline } width={ width } height={ height } style={ Object.keys( videoStyle ).length ? videoStyle : undefined } > <Tracks tracks={ tracks } /> </video> { !! temporaryURL && <Spinner /> } <Caption attributes={ attributes } setAttributes={ setAttributes } isSelected={ isSingleSelected } insertBlocksAfter={ insertBlocksAfter } label={ __( 'Video caption text' ) } showToolbarButton={ isSingleSelected && hasNonContentControls } /> </figure> </> ); } export default VideoEdit;