Skip to content

Commit

Permalink
[RNMobile] Embed block: Unavailable preview fallback (#33266)
Browse files Browse the repository at this point in the history
* Add /oembed path to supported endpoints

* Check embed preview status differently on native

* Use ActivityIndicator in embed loading

* Add loading and link styles

* Render different view in placholder if link cannot be embedded

* Show thumbnail or cannot embed text in preview

* Update embed loading style

* Fix spinner on Android

* Remove todo

* Fix loading dark background

* Fix thumbnail aspectRatio

* Add embed no preview component and refactor styles

* Add requestReview to RN bridge (iOS)

* Add requestReview to RN bridge (Android)

* Add request preview to Gutenberg demo

* [TEST] Enable embed block

* Update texts of embed no preview component

* Add help icon to embed no preview component

* Add dark mode styles to help icon

* Add native version of embed edit component

* Add embed preview image style

* Fix style import in embed preview

* Remove platform related code from embed block

* Disable embed block (enabled only for dev)

* Update a comment in native version of embed block

* Show toast text on preview request call in demo project

* Move onSubmit to inline callbacks

Co-authored-by: Ceyhun Ozugur <ceyhunozugur@gmail.com>
  • Loading branch information
fluiddot and ceyhun authored Jul 12, 2021
1 parent f3797c3 commit b29e3eb
Show file tree
Hide file tree
Showing 16 changed files with 582 additions and 122 deletions.
49 changes: 12 additions & 37 deletions packages/block-library/src/embed/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { __, _x, sprintf } from '@wordpress/i18n';
import { useState, useEffect, Platform } from '@wordpress/element';
import { useState, useEffect } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { useBlockProps } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
Expand Down Expand Up @@ -53,7 +53,6 @@ const EmbedEdit = ( props ) => {
setAttributes,
insertBlocksAfter,
onFocus,
clientId,
} = props;

const defaultEmbedInfo = {
Expand Down Expand Up @@ -94,10 +93,7 @@ const EmbedEdit = ( props ) => {
// Some WordPress URLs that can't be embedded will cause the API to return
// a valid JSON response with no HTML and `data.status` set to 404, rather
// than generating a fallback response as other embeds do.
const wordpressCantEmbed = Platform.select( {
web: embedPreview?.data?.status === 404,
native: embedPreview?.code === '404',
} );
const wordpressCantEmbed = embedPreview?.data?.status === 404;
const validPreview =
!! embedPreview && ! badEmbedProvider && ! wordpressCantEmbed;
return {
Expand Down Expand Up @@ -191,30 +187,8 @@ const EmbedEdit = ( props ) => {
);
}

const label = Platform.select( {
// translators: %s: type of embed e.g: "YouTube", "Twitter", etc. "Embed" is used when no specific type exists
web: sprintf( __( '%s URL' ), title ),
native: title,
} );

const onSubmit = ( event ) => {
if ( event ) {
event.preventDefault();
}

setIsEditingURL( false );
setAttributes( { url } );
};

const onSubmitNative = ( value ) => {
// On native, the URL change is only notified when submitting,
// and not via 'onChange', so we have to explicitly set the URL.
setURL( value );

// Replicate the same behavior as onSubmit
setIsEditingURL( false );
setAttributes( { url: value } );
};
// translators: %s: type of embed e.g: "YouTube", "Twitter", etc. "Embed" is used when no specific type exists
const label = sprintf( __( '%s URL' ), title );

// No preview, or we can't embed the current URL, or we've clicked the edit button.
const showEmbedPlaceholder = ! preview || cannotEmbed || isEditingURL;
Expand All @@ -226,19 +200,21 @@ const EmbedEdit = ( props ) => {
icon={ icon }
label={ label }
onFocus={ onFocus }
onSubmit={ Platform.select( {
web: onSubmit,
native: onSubmitNative,
} ) }
onSubmit={ ( event ) => {
if ( event ) {
event.preventDefault();
}

setIsEditingURL( false );
setAttributes( { url } );
} }
value={ url }
cannotEmbed={ cannotEmbed }
onChange={ ( event ) => setURL( event.target.value ) }
fallback={ () => fallback( url, onReplace ) }
tryAgain={ () => {
invalidateResolution( 'getEmbedPreview', [ url ] );
} }
isSelected={ isSelected }
isEditingURL={ isEditingURL }
/>
</View>
);
Expand Down Expand Up @@ -285,7 +261,6 @@ const EmbedEdit = ( props ) => {
icon={ icon }
label={ label }
insertBlocksAfter={ insertBlocksAfter }
clientId={ clientId }
/>
</View>
</>
Expand Down
218 changes: 218 additions & 0 deletions packages/block-library/src/embed/edit.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/**
* Internal dependencies
*/
import {
createUpgradedEmbedBlock,
getAttributesFromPreview,
getEmbedInfoByProvider,
} from './util';
import EmbedControls from './embed-controls';
import { embedContentIcon } from './icons';
import EmbedLoading from './embed-loading';
import EmbedPlaceholder from './embed-placeholder';
import EmbedPreview from './embed-preview';
import EmbedBottomSheet from './embed-bottom-sheet';

/**
* WordPress dependencies
*/
import { _x } from '@wordpress/i18n';
import { useState, useEffect } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import {
useBlockProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
import { View } from '@wordpress/primitives';

const EmbedEdit = ( props ) => {
const {
attributes: {
providerNameSlug,
previewable,
responsive,
url: attributesUrl,
},
attributes,
isSelected,
onReplace,
setAttributes,
insertBlocksAfter,
onFocus,
clientId,
} = props;

const defaultEmbedInfo = {
title: _x( 'Embed', 'block title' ),
icon: embedContentIcon,
};
const { icon, title } =
getEmbedInfoByProvider( providerNameSlug ) || defaultEmbedInfo;

const [ url, setURL ] = useState( attributesUrl );

const { wasBlockJustInserted } = useSelect(
( select ) => ( {
wasBlockJustInserted: select(
blockEditorStore
).wasBlockJustInserted( clientId, 'inserter_menu' ),
} ),
[ clientId ]
);
const [ isEditingURL, setIsEditingURL ] = useState(
isSelected && wasBlockJustInserted && ! url
);

const { preview, fetching, cannotEmbed } = useSelect(
( select ) => {
const {
getEmbedPreview,
isPreviewEmbedFallback,
isRequestingEmbedPreview,
} = select( coreStore );
if ( ! attributesUrl ) {
return { fetching: false, cannotEmbed: false };
}

const embedPreview = getEmbedPreview( attributesUrl );
const previewIsFallback = isPreviewEmbedFallback( attributesUrl );

// The external oEmbed provider does not exist. We got no type info and no html.
const badEmbedProvider =
embedPreview?.html === false &&
embedPreview?.type === undefined;
// Some WordPress URLs that can't be embedded will cause the API to return
// a valid JSON response with no HTML and `code` set to 404, rather
// than generating a fallback response as other embeds do.
const wordpressCantEmbed = embedPreview?.code === '404';
const validPreview =
!! embedPreview && ! badEmbedProvider && ! wordpressCantEmbed;
return {
preview: validPreview ? embedPreview : undefined,
fetching: isRequestingEmbedPreview( attributesUrl ),
cannotEmbed: ! validPreview || previewIsFallback,
};
},
[ attributesUrl ]
);

/**
* @return {Object} Attributes derived from the preview, merged with the current attributes.
*/
const getMergedAttributes = () => {
const { allowResponsive, className } = attributes;
return {
...attributes,
...getAttributesFromPreview(
preview,
title,
className,
responsive,
allowResponsive
),
};
};

useEffect( () => {
if ( ! preview?.html || ! cannotEmbed || fetching ) {
return;
}
// At this stage, we're not fetching the preview and know it can't be embedded,
// so try removing any trailing slash, and resubmit.
const newURL = attributesUrl.replace( /\/$/, '' );
setURL( newURL );
setIsEditingURL( false );
setAttributes( { url: newURL } );
}, [ preview?.html, attributesUrl ] );

// Handle incoming preview
useEffect( () => {
if ( preview && ! isEditingURL ) {
// Even though we set attributes that get derived from the preview,
// we don't access them directly because for the initial render,
// the `setAttributes` call will not have taken effect. If we're
// rendering responsive content, setting the responsive classes
// after the preview has been rendered can result in unwanted
// clipping or scrollbars. The `getAttributesFromPreview` function
// that `getMergedAttributes` uses is memoized so that we're not
// calculating them on every render.
setAttributes( getMergedAttributes() );
if ( onReplace ) {
const upgradedBlock = createUpgradedEmbedBlock(
props,
getMergedAttributes()
);

if ( upgradedBlock ) {
onReplace( upgradedBlock );
}
}
}
}, [ preview, isEditingURL ] );

const blockProps = useBlockProps();

if ( fetching ) {
return (
<View { ...blockProps }>
<EmbedLoading />
</View>
);
}

const showEmbedPlaceholder = ! preview || cannotEmbed;

return (
<>
{ showEmbedPlaceholder ? (
<View { ...blockProps }>
<EmbedPlaceholder
icon={ icon }
isSelected={ isSelected }
label={ title }
onPress={ ( event ) => {
onFocus( event );
setIsEditingURL( true );
} }
cannotEmbed={ cannotEmbed }
/>
</View>
) : (
<>
<EmbedControls
showEditButton={ preview && ! cannotEmbed }
switchBackToURLInput={ () => setIsEditingURL( true ) }
/>
<View { ...blockProps }>
<EmbedPreview
clientId={ clientId }
icon={ icon }
insertBlocksAfter={ insertBlocksAfter }
isSelected={ isSelected }
label={ title }
preview={ preview }
previewable={ previewable }
url={ url }
/>
</View>
</>
) }
<EmbedBottomSheet
value={ url }
isVisible={ isEditingURL }
onClose={ () => setIsEditingURL( false ) }
onSubmit={ ( value ) => {
// On native, the URL change is only notified when submitting,
// and not via 'onChange', so we have to explicitly set the URL.
setURL( value );

setIsEditingURL( false );
setAttributes( { url: value } );
} }
/>
</>
);
};

export default EmbedEdit;
23 changes: 23 additions & 0 deletions packages/block-library/src/embed/embed-controls.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
import { BlockControls } from '@wordpress/block-editor';
import { edit } from '@wordpress/icons';

const EmbedControls = ( { showEditButton, switchBackToURLInput } ) => (
<BlockControls>
<ToolbarGroup>
{ showEditButton && (
<ToolbarButton
label={ __( 'Edit URL' ) }
icon={ edit }
onClick={ switchBackToURLInput }
/>
) }
</ToolbarGroup>
</BlockControls>
);

export default EmbedControls;
Loading

0 comments on commit b29e3eb

Please sign in to comment.