Skip to content

Commit

Permalink
[RNMobile] Add 'Insert from URL' option to Image block (#40334)
Browse files Browse the repository at this point in the history
* Add 'Insert from URL' option to Video and Image blocks

* Update code style from linting

* Improve test cases for Media Upload capture options

* Fix whitespace issue

* Update Media Upload option tests to be asynchronous

* Update native image block to use correct image URL

* Add error handling for invalid URLs to native Image block

* Clear invalid URL error on Image URL success

* Fix synchronicity of Media Upload option tests

* Add check for URL handler to native Image block picker options

* Update code style

* Remove Video block from urlSource options

Why: to be introduced in a later PR

* Remove URL option from Video block for Media Upload test

* Use Notice snackbar for native Image block error handling

* Update Image/Media Upload code style and helpers

* Use getImage to determine if URL is a valid image within Image block

* Add loading indicator and isURL check to native Image block URL behavior

* Add loading indicator to native Image block media placeholder

* Fix whitespace issue in native Image block code style

* Reuse native Image block loading indicator

* Use undefined dimension attributes for the native Image block URL behavior

Co-authored-by: Derek Blank <[email protected]>
  • Loading branch information
2 people authored and Siobhan committed May 24, 2022
1 parent 639598f commit 3fe5683
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const OPTION_TAKE_VIDEO = __( 'Take a Video' );
export const OPTION_TAKE_PHOTO = __( 'Take a Photo' );
export const OPTION_TAKE_PHOTO_OR_VIDEO = __( 'Take a Photo or Video' );
export const OPTION_INSERT_FROM_URL = __( 'Insert from URL' );
export const OPTION_WORDPRESS_MEDIA_LIBRARY = __( 'WordPress Media Library' );

const URL_MEDIA_SOURCE = 'URL';

Expand Down Expand Up @@ -78,6 +79,8 @@ export class MediaUpload extends Component {
}

getAllSources() {
const { onSelectURL } = this.props;

const cameraImageSource = {
id: mediaSources.deviceCamera, // ID is the value sent to native.
value: mediaSources.deviceCamera + '-IMAGE', // This is needed to diferenciate image-camera from video-camera sources.
Expand Down Expand Up @@ -124,16 +127,17 @@ export class MediaUpload extends Component {
id: URL_MEDIA_SOURCE,
value: URL_MEDIA_SOURCE,
label: __( 'Insert from URL' ),
types: [ MEDIA_TYPE_AUDIO ],
types: [ MEDIA_TYPE_AUDIO, MEDIA_TYPE_IMAGE ],
icon: globe,
};

// Only include `urlSource` option if `onSelectURL` prop is present, in order to match the web behavior.
const internalSources = [
deviceLibrarySource,
cameraImageSource,
cameraVideoSource,
siteLibrarySource,
urlSource,
...( onSelectURL ? [ urlSource ] : [] ),
];

return internalSources.concat( this.state.otherMediaOptions );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
OPTION_TAKE_VIDEO,
OPTION_TAKE_PHOTO,
OPTION_INSERT_FROM_URL,
OPTION_WORDPRESS_MEDIA_LIBRARY,
} from '../index';

const MEDIA_URL = 'http://host.media.type';
Expand All @@ -33,8 +34,11 @@ describe( 'MediaUpload component', () => {
expect( wrapper ).toBeTruthy();
} );

it( 'shows right media capture option for media type', () => {
const expectOptionForMediaType = ( mediaType, expectedOption ) => {
describe( 'Media capture options for different media block types', () => {
const expectOptionForMediaType = async (
mediaType,
expectedOptions
) => {
const wrapper = render(
<MediaUpload
allowedTypes={ [ mediaType ] }
Expand All @@ -52,11 +56,32 @@ describe( 'MediaUpload component', () => {
);
fireEvent.press( wrapper.getByText( 'Open Picker' ) );

wrapper.getByText( expectedOption );
expectedOptions.forEach( ( item ) => {
const option = wrapper.getByText( item );
expect( option ).toBeVisible();
} );
};
expectOptionForMediaType( MEDIA_TYPE_IMAGE, OPTION_TAKE_PHOTO );
expectOptionForMediaType( MEDIA_TYPE_VIDEO, OPTION_TAKE_VIDEO );
expectOptionForMediaType( MEDIA_TYPE_AUDIO, OPTION_INSERT_FROM_URL );

it( 'shows the correct media capture options for the Image block', () => {
expectOptionForMediaType( MEDIA_TYPE_IMAGE, [
OPTION_TAKE_PHOTO,
OPTION_WORDPRESS_MEDIA_LIBRARY,
OPTION_INSERT_FROM_URL,
] );
} );

it( 'shows the correct media capture options for the Video block', () => {
expectOptionForMediaType( MEDIA_TYPE_VIDEO, [
OPTION_TAKE_VIDEO,
OPTION_WORDPRESS_MEDIA_LIBRARY,
] );
} );

it( 'shows the correct media capture options for the Audio block', () => {
expectOptionForMediaType( MEDIA_TYPE_AUDIO, [
OPTION_INSERT_FROM_URL,
] );
} );
} );

const expectMediaPickerForOption = (
Expand Down
68 changes: 65 additions & 3 deletions packages/block-library/src/image/edit.native.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/**
* External dependencies
*/
import { View, TouchableWithoutFeedback } from 'react-native';
import {
ActivityIndicator,
Image as RNImage,
TouchableWithoutFeedback,
View,
} from 'react-native';
import { useRoute } from '@react-navigation/native';

/**
Expand Down Expand Up @@ -45,7 +50,7 @@ import {
blockSettingsScreens,
} from '@wordpress/block-editor';
import { __, _x, sprintf } from '@wordpress/i18n';
import { getProtocol, hasQueryArg } from '@wordpress/url';
import { getProtocol, hasQueryArg, isURL } from '@wordpress/url';
import { doAction, hasAction } from '@wordpress/hooks';
import { compose, withPreferredColorScheme } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
Expand All @@ -57,6 +62,7 @@ import {
} from '@wordpress/icons';
import { store as coreStore } from '@wordpress/core-data';
import { store as editPostStore } from '@wordpress/edit-post';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
Expand Down Expand Up @@ -207,6 +213,7 @@ export class ImageEdit extends Component {
this.onImagePressed = this.onImagePressed.bind( this );
this.onSetFeatured = this.onSetFeatured.bind( this );
this.onFocusCaption = this.onFocusCaption.bind( this );
this.onSelectURL = this.onSelectURL.bind( this );
this.updateAlignment = this.updateAlignment.bind( this );
this.accessibilityLabelCreator = this.accessibilityLabelCreator.bind(
this
Expand Down Expand Up @@ -461,6 +468,45 @@ export class ImageEdit extends Component {
} );
}

onSelectURL( newURL ) {
const {
createErrorNotice,
imageDefaultSize,
setAttributes,
} = this.props;

if ( isURL( newURL ) ) {
this.setState( {
isFetchingImage: true,
} );

// Use RN's Image.getSize to determine if URL is a valid image
RNImage.getSize(
newURL,
() => {
setAttributes( {
url: newURL,
id: undefined,
width: undefined,
height: undefined,
sizeSlug: imageDefaultSize,
} );
this.setState( {
isFetchingImage: false,
} );
},
() => {
createErrorNotice( __( 'Image file not found.' ) );
this.setState( {
isFetchingImage: false,
} );
}
);
} else {
createErrorNotice( __( 'Invalid URL.' ) );
}
}

onFocusCaption() {
if ( this.props.onFocus ) {
this.props.onFocus();
Expand All @@ -484,6 +530,14 @@ export class ImageEdit extends Component {
);
}

showLoadingIndicator() {
return (
<View style={ styles.image__loading }>
<ActivityIndicator animating />
</View>
);
}

getWidth() {
const { attributes } = this.props;
const { align, width } = attributes;
Expand Down Expand Up @@ -611,7 +665,7 @@ export class ImageEdit extends Component {
}

render() {
const { isCaptionSelected } = this.state;
const { isCaptionSelected, isFetchingImage } = this.state;
const {
attributes,
isSelected,
Expand Down Expand Up @@ -713,9 +767,11 @@ export class ImageEdit extends Component {
if ( ! url ) {
return (
<View style={ styles.content }>
{ isFetchingImage && this.showLoadingIndicator() }
<MediaPlaceholder
allowedTypes={ [ MEDIA_TYPE_IMAGE ] }
onSelect={ this.onSelectMediaUploadOption }
onSelectURL={ this.onSelectURL }
icon={ this.getPlaceholderIcon() }
onFocus={ this.props.onFocus }
autoOpenMediaUpload={
Expand Down Expand Up @@ -784,6 +840,8 @@ export class ImageEdit extends Component {
} ) => {
return (
<View style={ imageContainerStyles }>
{ isFetchingImage &&
this.showLoadingIndicator() }
<Image
align={
align && alignToFlex[ align ]
Expand Down Expand Up @@ -836,6 +894,7 @@ export class ImageEdit extends Component {
allowedTypes={ [ MEDIA_TYPE_IMAGE ] }
isReplacingMedia={ true }
onSelect={ this.onSelectMediaUploadOption }
onSelectURL={ this.onSelectURL }
render={ ( { open, getMediaOptions } ) => {
return getImageComponent( open, getMediaOptions );
} }
Expand Down Expand Up @@ -881,7 +940,10 @@ export default compose( [
};
} ),
withDispatch( ( dispatch ) => {
const { createErrorNotice } = dispatch( noticesStore );

return {
createErrorNotice,
closeSettingsBottomSheet() {
dispatch( editPostStore ).closeGeneralSidebar();
},
Expand Down
11 changes: 11 additions & 0 deletions packages/block-library/src/image/styles.native.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,14 @@
.removeFeaturedButton {
color: $alert-red;
}

.image__loading {
align-items: center;
background-color: rgba(10, 10, 10, 0.5);
flex: 1;
height: 100%;
justify-content: center;
position: absolute;
width: 100%;
z-index: 1;
}

0 comments on commit 3fe5683

Please sign in to comment.