Skip to content

Commit

Permalink
Enable playing the Core Video block's video in the editor (#2799)
Browse files Browse the repository at this point in the history
* Fork the Core Video block's edit component, to enable playing the video
* Rename components
* Add loop setting, remove controls setting
* Do not allow removing the video poster
* Add video poster logic to custom block edit component
  • Loading branch information
kienstra authored and swissspidy committed Jul 15, 2019
1 parent e4ed3d3 commit e63d2c3
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 95 deletions.
8 changes: 0 additions & 8 deletions assets/css/amp-stories.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,6 @@ table.wp-block-table {
background-image: url("../images/quote-white.svg");
}

/**
* See https://github.com/ampproject/amp-wp/issues/2283
*/
.edit-post-layout[data-block-name="core/video"] .edit-post-settings-sidebar__panel-block .block-editor-block-inspector__card + div > .components-panel__body:first-child .components-toggle-control:nth-child(2),
.edit-post-layout[data-block-name="core/video"] .edit-post-settings-sidebar__panel-block .block-editor-block-inspector__card + div > .components-panel__body:first-child .components-toggle-control:nth-child(6) {
display: none;
}

/* Block Warnings */

.is-selected .block-editor-block-list__block .block-editor-warning {
Expand Down
317 changes: 317 additions & 0 deletions assets/src/stories-editor/components/custom-video-block-edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import { get } from 'lodash';

/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { getBlobByURL, isBlobURL } from '@wordpress/blob';
import { Component, createRef } from '@wordpress/element';
import {
BaseControl,
Button,
IconButton,
Notice,
PanelBody,
Path,
SVG,
ToggleControl,
Toolbar,
withNotices,
} from '@wordpress/components';
import {
BlockControls,
BlockIcon,
InspectorControls,
MediaPlaceholder,
MediaUpload,
MediaUploadCheck,
RichText,
} from '@wordpress/block-editor';
import { compose, withInstanceId } from '@wordpress/compose';
import { withSelect } from '@wordpress/data';

const ALLOWED_MEDIA_TYPES = [ 'video' ];
const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ];
const icon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z" /></SVG>;

/**
* Mainly forked from the Core Video block edit component, but allows the <video> to play instead of being disabled.
*
* There are very few changes from the Core Video block's component.
* The main change is that in render(), the <video> is not wrapped in <Disabled>, so it can play.
*
* Also removes video settings that are not applicable / allowed in an AMP Stories context.
*
* @class
*/
class CustomVideoBlockEdit extends Component {
constructor() {
super( ...arguments );

this.state = {
editing: ! this.props.attributes.src,
};

this.videoPlayer = createRef();
this.posterImageButton = createRef();
this.toggleAttribute = this.toggleAttribute.bind( this );
this.onSelectURL = this.onSelectURL.bind( this );
this.onSelectPoster = this.onSelectPoster.bind( this );
this.onUploadError = this.onUploadError.bind( this );
}

componentDidMount() {
const {
attributes,
mediaUpload,
noticeOperations,
setAttributes,
} = this.props;
const { id, src = '' } = attributes;
if ( ! id && isBlobURL( src ) ) {
const file = getBlobByURL( src );
if ( file ) {
mediaUpload( {
filesList: [ file ],
onFileChange: ( [ { url } ] ) => {
setAttributes( { src: url } );
},
onError: ( message ) => {
this.setState( { editing: true } );
noticeOperations.createErrorNotice( message );
},
allowedTypes: ALLOWED_MEDIA_TYPES,
} );
}
}
}

componentDidUpdate( prevProps ) {
if ( this.props.attributes.poster !== prevProps.attributes.poster ) {
this.videoPlayer.current.load();
}

if ( ! this.props.attributes.poster && this.props.videoFeaturedImage ) {
this.props.setAttributes( { poster: this.props.videoFeaturedImage.source_url } );
}
}
toggleAttribute( attribute ) {
return ( newValue ) => {
this.props.setAttributes( { [ attribute ]: newValue } );
};
}

onSelectURL( newSrc ) {
const { attributes, setAttributes } = this.props;
const { src } = attributes;

// Set the block's src from the edit component's state, and switch off
// the editing UI.
if ( newSrc !== src ) {
// Omit the embed block logic, as that didn't seem to work.
setAttributes( { src: newSrc, id: undefined } );
}

this.setState( { editing: false } );
}

onSelectPoster( image ) {
const { setAttributes } = this.props;
setAttributes( { poster: image.url } );
}

onUploadError( message ) {
const { noticeOperations } = this.props;
noticeOperations.removeAllNotices();
noticeOperations.createErrorNotice( message );
}

render() {
const {
caption,
loop,
poster,
src,
} = this.props.attributes;
const {
className,
instanceId,
isSelected,
noticeUI,
setAttributes,
} = this.props;
const { editing } = this.state;
const switchToEditing = () => {
this.setState( { editing: true } );
};
const onSelectVideo = ( media ) => {
if ( ! media || ! media.url ) {
// in this case there was an error and we should continue in the editing state
// previous attributes should be removed because they may be temporary blob urls
setAttributes( { src: undefined, id: undefined } );
switchToEditing();
return;
}

// sets the block's attribute and updates the edit component from the
// selected media, then switches off the editing UI
setAttributes( { src: media.url, id: media.id } );
this.setState( { src: media.url, editing: false } );
};

if ( editing ) {
return (
<MediaPlaceholder
icon={ <BlockIcon icon={ icon } /> }
className={ className }
onSelect={ onSelectVideo }
onSelectURL={ this.onSelectURL }
accept="video/mp4"
allowedTypes={ ALLOWED_MEDIA_TYPES }
value={ this.props.attributes }
notices={ noticeUI }
onError={ this.onUploadError }
/>
);
}

const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`;

return (
<>
<BlockControls>
<Toolbar>
<IconButton
className="components-icon-button components-toolbar__control"
label={ __( 'Edit video', 'amp' ) }
onClick={ switchToEditing }
icon="edit"
/>
</Toolbar>
</BlockControls>
<InspectorControls>
<PanelBody title={ __( 'Video Settings', 'amp' ) }>
<ToggleControl
label={ __( 'Loop', 'amp' ) }
onChange={ this.toggleAttribute( 'loop' ) }
checked={ loop }
/>
<MediaUploadCheck>
<BaseControl
className="editor-video-poster-control"
>
<BaseControl.VisualLabel>
{ __( 'Poster Image', 'amp' ) }
</BaseControl.VisualLabel>
<MediaUpload
title={ __( 'Select Poster Image', 'amp' ) }
onSelect={ this.onSelectPoster }
allowedTypes={ VIDEO_POSTER_ALLOWED_MEDIA_TYPES }
render={ ( { open } ) => (
<Button
isDefault
onClick={ open }
ref={ this.posterImageButton }
aria-describedby={ videoPosterDescription }
>
{ ! poster ? __( 'Select Poster Image', 'amp' ) : __( 'Replace image', 'amp' ) }
</Button>
) }
/>
{ poster && (
<p
id={ videoPosterDescription }
hidden
>
{
/* translators: %s: the poster image URL. */
sprintf( __( 'The current poster image url is %s', 'amp' ), this.props.attributes.poster )
}
</p>
) }
{ ! poster && (
<Notice
status="error"
isDismissible={ false }
>
{ __( 'A poster is required for videos in stories.', 'amp' ) }
</Notice>
) }
</BaseControl>
</MediaUploadCheck>
</PanelBody>
</InspectorControls>
<figure className="wp-block-video">
<video
autoPlay
muted
loop={ loop }
controls={ ! loop }
poster={ poster }
ref={ this.videoPlayer }
src={ src }
/>
{ ( ! RichText.isEmpty( caption ) || isSelected ) && (
<RichText
tagName="figcaption"
placeholder={ __( 'Write caption…', 'amp' ) }
value={ caption }
onChange={ ( value ) => setAttributes( { caption: value } ) }
inlineToolbar
/>
) }
</figure>
</>
);
}
}

CustomVideoBlockEdit.propTypes = {
attributes: PropTypes.shape( {
caption: PropTypes.string,
controls: PropTypes.bool,
loop: PropTypes.bool,
id: PropTypes.number,
poster: PropTypes.string,
src: PropTypes.string,
} ),
className: PropTypes.string,
instanceId: PropTypes.number,
isSelected: PropTypes.bool,
mediaUpload: PropTypes.func,
getMedia: PropTypes.func,
noticeUI: PropTypes.func,
noticeOperations: PropTypes.object,
setAttributes: PropTypes.func,
videoFeaturedImage: PropTypes.shape( {
source_url: PropTypes.string,
} ),
};

export default compose( [
withSelect( ( select, props ) => {
const { getMedia } = select( 'core' );
const { getSettings } = select( 'core/block-editor' );
const { __experimentalMediaUpload } = getSettings();

let videoFeaturedImage;

if ( props.attributes.id && ! props.attributes.poster ) {
const media = getMedia( props.attributes.id );
const featuredImage = media && get( media, [ '_links', 'wp:featuredmedia', 0, 'href' ], null );
videoFeaturedImage = featuredImage && getMedia( Number( featuredImage.split( '/' ).pop() ) );
}

return {
mediaUpload: __experimentalMediaUpload,
getMedia,
videoFeaturedImage,
};
} ),
withNotices,
withInstanceId,
] )( CustomVideoBlockEdit );
Loading

0 comments on commit e63d2c3

Please sign in to comment.