diff --git a/projects/plugins/jetpack/extensions/blocks/videopress/edit.native.js b/projects/plugins/jetpack/extensions/blocks/videopress/edit.native.js index e781484cf7861..b639d17d12a0c 100644 --- a/projects/plugins/jetpack/extensions/blocks/videopress/edit.native.js +++ b/projects/plugins/jetpack/extensions/blocks/videopress/edit.native.js @@ -1,9 +1,7 @@ -/** - * External dependencies - */ /** * WordPress dependencies */ +import apiFetch from '@wordpress/api-fetch'; import { BlockCaption, MediaPlaceholder, @@ -17,7 +15,7 @@ import { store as blockEditorStore, } from '@wordpress/block-editor'; import { Icon, ToolbarButton, ToolbarGroup, PanelBody } from '@wordpress/components'; -import { withPreferredColorScheme, compose } from '@wordpress/compose'; +import { withPreferredColorScheme, compose, createHigherOrderComponent } from '@wordpress/compose'; import { withDispatch, withSelect } from '@wordpress/data'; import { Component } from '@wordpress/element'; import { doAction, hasAction } from '@wordpress/hooks'; @@ -30,14 +28,17 @@ import { requestImageUploadCancelDialog, } from '@wordpress/react-native-bridge'; import { isURL, getProtocol } from '@wordpress/url'; -import { View, TouchableWithoutFeedback, Text } from 'react-native'; +/** + * External dependencies + */ +import { ActivityIndicator, View, TouchableWithoutFeedback, Text } from 'react-native'; /** * Internal dependencies */ -import { createUpgradedEmbedBlock } from '../embed/util'; import VideoCommonSettings from './edit-common-settings'; import SvgIconRetry from './icon-retry'; import style from './style.scss'; +import { pickGUIDFromUrl } from './utils'; const ICON_TYPE = { PLACEHOLDER: 'placeholder', @@ -45,13 +46,17 @@ const ICON_TYPE = { UPLOAD: 'upload', }; -class VideoEdit extends Component { +class VideoPressEdit extends Component { constructor( props ) { super( props ); this.state = { isCaptionSelected: false, videoContainerHeight: 0, + isUploadInProgress: false, + isUploadFailed: false, + isLoadingMetadata: false, + metadata: {}, }; this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); @@ -65,11 +70,20 @@ class VideoEdit extends Component { this.onFocusCaption = this.onFocusCaption.bind( this ); } - componentDidMount() { + async componentDidMount() { const { attributes } = this.props; + const { guid } = attributes; if ( attributes.id && getProtocol( attributes.src ) === 'file:' ) { mediaUploadSync(); } + + // Try to infer the VideoPress ID from the source upon component mount. + // If the ID already exists, fetch the metadata to get the video URL. + if ( ! guid ) { + await this.setGuid(); + } else { + await this.fetchMetadata( guid ); + } } componentWillUnmount() { @@ -80,13 +94,40 @@ class VideoEdit extends Component { } static getDerivedStateFromProps( props, state ) { - // Avoid a UI flicker in the toolbar by insuring that isCaptionSelected - // is updated immediately any time the isSelected prop becomes false. return { + // Avoid a UI flicker in the toolbar by insuring that isCaptionSelected + // is updated immediately any time the isSelected prop becomes false. isCaptionSelected: props.isSelected && state.isCaptionSelected, + // Reset metadata when "guid" attribute is not defined. + metadata: props.attributes?.guid ? state.metadata : {}, }; } + async setGuid( value ) { + const { setAttributes, attributes } = this.props; + const { src } = attributes; + // If no value is passed, we try to extract the VideoPress ID from the source. + const guid = value ?? pickGUIDFromUrl( src ) ?? undefined; + setAttributes( { guid } ); + if ( guid ) { + await this.fetchMetadata( guid ); + } + } + + async fetchMetadata( guid ) { + this.setState( { isLoadingMetadata: true } ); + try { + const metadata = await apiFetch( { + path: `/rest/v1.1/videos/${ guid }`, + } ); + this.setState( { metadata, isLoadingMetadata: false } ); + } catch ( error ) { + // eslint-disable-next-line no-console + console.error( `Couldn't fetch metadata of VideoPress video with ID = ${ guid }`, error ); + this.setState( { isLoadingMetadata: false } ); + } + } + onVideoPressed() { const { attributes } = this.props; @@ -119,8 +160,11 @@ class VideoEdit extends Component { finishMediaUploadWithSuccess( payload ) { const { setAttributes } = this.props; - setAttributes( { src: payload.mediaUrl, id: payload.mediaServerId } ); + const { mediaUrl, mediaServerId, metadata = {} } = payload; + const { videopressGUID } = metadata; + setAttributes( { src: mediaUrl, id: mediaServerId } ); this.setState( { isUploadInProgress: false } ); + this.setGuid( videopressGUID ); } finishMediaUploadWithFailure( payload ) { @@ -131,29 +175,23 @@ class VideoEdit extends Component { mediaUploadStateReset() { const { setAttributes } = this.props; - setAttributes( { id: null, src: null } ); + setAttributes( { id: null, src: null, guid: null } ); this.setState( { isUploadInProgress: false } ); } - onSelectMediaUploadOption( { id, url } ) { + onSelectMediaUploadOption( payload ) { const { setAttributes } = this.props; + const { id, url, metadata = {} } = payload; + const { videopressGUID } = metadata; setAttributes( { id, src: url } ); + this.setGuid( videopressGUID ); } onSelectURL( url ) { - const { createErrorNotice, onReplace, setAttributes } = this.props; + const { createErrorNotice, setAttributes } = this.props; - if ( isURL( url ) && /^https?:/.test( getProtocol( url ) ) ) { - // Check if there's an embed block that handles this URL. - const embedBlock = createUpgradedEmbedBlock( { - attributes: { url }, - } ); - if ( undefined !== embedBlock ) { - onReplace( embedBlock ); - return; - } - - setAttributes( { src: url, id: undefined, poster: undefined } ); + if ( isURL( url ) ) { + setAttributes( { id: url, src: url } ); } else { createErrorNotice( __( 'Invalid URL.', 'jetpack' ) ); } @@ -186,10 +224,19 @@ class VideoEdit extends Component { return ; } + getVideoURL() { + const { attributes } = this.props; + const { src, guid } = attributes; + const { metadata = {} } = this.state; + + return metadata.original || `https://videopress.com/v/${ guid }` || src; + } + render() { const { setAttributes, attributes, isSelected, wasBlockJustInserted } = this.props; const { id, src, guid } = attributes; - const { videoContainerHeight } = this.state; + const { isLoadingMetadata, isUploadInProgress, isUploadFailed, videoContainerHeight } = + this.state; const toolbarEditButton = ( ); - // NOTE: `guid` is not part of the block's attribute definition. This case - // handled here is a temporary fix until a we find a better approach. const isSourcePresent = src || ( guid && id ); if ( ! isSourcePresent ) { return ( @@ -251,8 +296,13 @@ class VideoEdit extends Component { onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } onUpdateMediaProgress={ this.updateMediaProgress } onMediaUploadStateReset={ this.mediaUploadStateReset } - renderContent={ ( { isUploadInProgress, isUploadFailed, retryMessage } ) => { - const showVideo = isURL( src ) && ! isUploadInProgress && ! isUploadFailed; + renderContent={ ( { retryMessage } ) => { + const videoURL = this.getVideoURL(); + const showVideo = + isURL( videoURL ) && + ! isUploadInProgress && + ! isUploadFailed && + ! isLoadingMetadata; const icon = this.getIcon( isUploadFailed ? ICON_TYPE.RETRY : ICON_TYPE.UPLOAD ); const styleIconContainer = isUploadFailed ? style.modalIconRetry : style.modalIcon; @@ -273,7 +323,7 @@ class VideoEdit extends Component { @@ -289,7 +339,8 @@ class VideoEdit extends Component { ), } } > - { videoContainerHeight > 0 && iconContainer } + { videoContainerHeight > 0 && + ( isLoadingMetadata ? : iconContainer ) } { isUploadFailed && ( { retryMessage } ) } @@ -322,17 +373,21 @@ class VideoEdit extends Component { } } -export default compose( [ - withSelect( ( select, { clientId } ) => ( { - wasBlockJustInserted: select( blockEditorStore ).wasBlockJustInserted( - clientId, - 'inserter_menu' - ), - } ) ), - withDispatch( dispatch => { - const { createErrorNotice } = dispatch( noticesStore ); - - return { createErrorNotice }; - } ), - withPreferredColorScheme, -] )( VideoEdit ); +export default CoreVideoEdit => + compose( [ + withSelect( ( select, { clientId } ) => ( { + wasBlockJustInserted: select( blockEditorStore ).wasBlockJustInserted( + clientId, + 'inserter_menu' + ), + } ) ), + withDispatch( dispatch => { + const { createErrorNotice } = dispatch( noticesStore ); + + return { createErrorNotice }; + } ), + withPreferredColorScheme, + createHigherOrderComponent( WrappedComponent => props => { + return ; + } ), + ] )( VideoPressEdit );