Skip to content

Commit 20f6ca5

Browse files
derekblankDerek Blank
and
Derek Blank
authored
[RNMobile] Add 'Insert from URL' option to Image block (#40334)
* 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 <derekblank@Dereks-MacBook-Pro.local>
1 parent 1a1396e commit 20f6ca5

File tree

4 files changed

+113
-11
lines changed

4 files changed

+113
-11
lines changed

packages/block-editor/src/components/media-upload/index.native.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const OPTION_TAKE_VIDEO = __( 'Take a Video' );
3939
export const OPTION_TAKE_PHOTO = __( 'Take a Photo' );
4040
export const OPTION_TAKE_PHOTO_OR_VIDEO = __( 'Take a Photo or Video' );
4141
export const OPTION_INSERT_FROM_URL = __( 'Insert from URL' );
42+
export const OPTION_WORDPRESS_MEDIA_LIBRARY = __( 'WordPress Media Library' );
4243

4344
const URL_MEDIA_SOURCE = 'URL';
4445

@@ -78,6 +79,8 @@ export class MediaUpload extends Component {
7879
}
7980

8081
getAllSources() {
82+
const { onSelectURL } = this.props;
83+
8184
const cameraImageSource = {
8285
id: mediaSources.deviceCamera, // ID is the value sent to native.
8386
value: mediaSources.deviceCamera + '-IMAGE', // This is needed to diferenciate image-camera from video-camera sources.
@@ -124,16 +127,17 @@ export class MediaUpload extends Component {
124127
id: URL_MEDIA_SOURCE,
125128
value: URL_MEDIA_SOURCE,
126129
label: __( 'Insert from URL' ),
127-
types: [ MEDIA_TYPE_AUDIO ],
130+
types: [ MEDIA_TYPE_AUDIO, MEDIA_TYPE_IMAGE ],
128131
icon: globe,
129132
};
130133

134+
// Only include `urlSource` option if `onSelectURL` prop is present, in order to match the web behavior.
131135
const internalSources = [
132136
deviceLibrarySource,
133137
cameraImageSource,
134138
cameraVideoSource,
135139
siteLibrarySource,
136-
urlSource,
140+
...( onSelectURL ? [ urlSource ] : [] ),
137141
];
138142

139143
return internalSources.concat( this.state.otherMediaOptions );

packages/block-editor/src/components/media-upload/test/index.native.js

+31-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
OPTION_TAKE_VIDEO,
2121
OPTION_TAKE_PHOTO,
2222
OPTION_INSERT_FROM_URL,
23+
OPTION_WORDPRESS_MEDIA_LIBRARY,
2324
} from '../index';
2425

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

36-
it( 'shows right media capture option for media type', () => {
37-
const expectOptionForMediaType = ( mediaType, expectedOption ) => {
37+
describe( 'Media capture options for different media block types', () => {
38+
const expectOptionForMediaType = async (
39+
mediaType,
40+
expectedOptions
41+
) => {
3842
const wrapper = render(
3943
<MediaUpload
4044
allowedTypes={ [ mediaType ] }
@@ -52,11 +56,32 @@ describe( 'MediaUpload component', () => {
5256
);
5357
fireEvent.press( wrapper.getByText( 'Open Picker' ) );
5458

55-
wrapper.getByText( expectedOption );
59+
expectedOptions.forEach( ( item ) => {
60+
const option = wrapper.getByText( item );
61+
expect( option ).toBeVisible();
62+
} );
5663
};
57-
expectOptionForMediaType( MEDIA_TYPE_IMAGE, OPTION_TAKE_PHOTO );
58-
expectOptionForMediaType( MEDIA_TYPE_VIDEO, OPTION_TAKE_VIDEO );
59-
expectOptionForMediaType( MEDIA_TYPE_AUDIO, OPTION_INSERT_FROM_URL );
64+
65+
it( 'shows the correct media capture options for the Image block', () => {
66+
expectOptionForMediaType( MEDIA_TYPE_IMAGE, [
67+
OPTION_TAKE_PHOTO,
68+
OPTION_WORDPRESS_MEDIA_LIBRARY,
69+
OPTION_INSERT_FROM_URL,
70+
] );
71+
} );
72+
73+
it( 'shows the correct media capture options for the Video block', () => {
74+
expectOptionForMediaType( MEDIA_TYPE_VIDEO, [
75+
OPTION_TAKE_VIDEO,
76+
OPTION_WORDPRESS_MEDIA_LIBRARY,
77+
] );
78+
} );
79+
80+
it( 'shows the correct media capture options for the Audio block', () => {
81+
expectOptionForMediaType( MEDIA_TYPE_AUDIO, [
82+
OPTION_INSERT_FROM_URL,
83+
] );
84+
} );
6085
} );
6186

6287
const expectMediaPickerForOption = (

packages/block-library/src/image/edit.native.js

+65-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
/**
22
* External dependencies
33
*/
4-
import { View, TouchableWithoutFeedback } from 'react-native';
4+
import {
5+
ActivityIndicator,
6+
Image as RNImage,
7+
TouchableWithoutFeedback,
8+
View,
9+
} from 'react-native';
510
import { useRoute } from '@react-navigation/native';
611

712
/**
@@ -45,7 +50,7 @@ import {
4550
blockSettingsScreens,
4651
} from '@wordpress/block-editor';
4752
import { __, _x, sprintf } from '@wordpress/i18n';
48-
import { getProtocol, hasQueryArg } from '@wordpress/url';
53+
import { getProtocol, hasQueryArg, isURL } from '@wordpress/url';
4954
import { doAction, hasAction } from '@wordpress/hooks';
5055
import { compose, withPreferredColorScheme } from '@wordpress/compose';
5156
import { withSelect, withDispatch } from '@wordpress/data';
@@ -57,6 +62,7 @@ import {
5762
} from '@wordpress/icons';
5863
import { store as coreStore } from '@wordpress/core-data';
5964
import { store as editPostStore } from '@wordpress/edit-post';
65+
import { store as noticesStore } from '@wordpress/notices';
6066

6167
/**
6268
* Internal dependencies
@@ -207,6 +213,7 @@ export class ImageEdit extends Component {
207213
this.onImagePressed = this.onImagePressed.bind( this );
208214
this.onSetFeatured = this.onSetFeatured.bind( this );
209215
this.onFocusCaption = this.onFocusCaption.bind( this );
216+
this.onSelectURL = this.onSelectURL.bind( this );
210217
this.updateAlignment = this.updateAlignment.bind( this );
211218
this.accessibilityLabelCreator = this.accessibilityLabelCreator.bind(
212219
this
@@ -461,6 +468,45 @@ export class ImageEdit extends Component {
461468
} );
462469
}
463470

471+
onSelectURL( newURL ) {
472+
const {
473+
createErrorNotice,
474+
imageDefaultSize,
475+
setAttributes,
476+
} = this.props;
477+
478+
if ( isURL( newURL ) ) {
479+
this.setState( {
480+
isFetchingImage: true,
481+
} );
482+
483+
// Use RN's Image.getSize to determine if URL is a valid image
484+
RNImage.getSize(
485+
newURL,
486+
() => {
487+
setAttributes( {
488+
url: newURL,
489+
id: undefined,
490+
width: undefined,
491+
height: undefined,
492+
sizeSlug: imageDefaultSize,
493+
} );
494+
this.setState( {
495+
isFetchingImage: false,
496+
} );
497+
},
498+
() => {
499+
createErrorNotice( __( 'Image file not found.' ) );
500+
this.setState( {
501+
isFetchingImage: false,
502+
} );
503+
}
504+
);
505+
} else {
506+
createErrorNotice( __( 'Invalid URL.' ) );
507+
}
508+
}
509+
464510
onFocusCaption() {
465511
if ( this.props.onFocus ) {
466512
this.props.onFocus();
@@ -484,6 +530,14 @@ export class ImageEdit extends Component {
484530
);
485531
}
486532

533+
showLoadingIndicator() {
534+
return (
535+
<View style={ styles.image__loading }>
536+
<ActivityIndicator animating />
537+
</View>
538+
);
539+
}
540+
487541
getWidth() {
488542
const { attributes } = this.props;
489543
const { align, width } = attributes;
@@ -611,7 +665,7 @@ export class ImageEdit extends Component {
611665
}
612666

613667
render() {
614-
const { isCaptionSelected } = this.state;
668+
const { isCaptionSelected, isFetchingImage } = this.state;
615669
const {
616670
attributes,
617671
isSelected,
@@ -713,9 +767,11 @@ export class ImageEdit extends Component {
713767
if ( ! url ) {
714768
return (
715769
<View style={ styles.content }>
770+
{ isFetchingImage && this.showLoadingIndicator() }
716771
<MediaPlaceholder
717772
allowedTypes={ [ MEDIA_TYPE_IMAGE ] }
718773
onSelect={ this.onSelectMediaUploadOption }
774+
onSelectURL={ this.onSelectURL }
719775
icon={ this.getPlaceholderIcon() }
720776
onFocus={ this.props.onFocus }
721777
autoOpenMediaUpload={
@@ -784,6 +840,8 @@ export class ImageEdit extends Component {
784840
} ) => {
785841
return (
786842
<View style={ imageContainerStyles }>
843+
{ isFetchingImage &&
844+
this.showLoadingIndicator() }
787845
<Image
788846
align={
789847
align && alignToFlex[ align ]
@@ -836,6 +894,7 @@ export class ImageEdit extends Component {
836894
allowedTypes={ [ MEDIA_TYPE_IMAGE ] }
837895
isReplacingMedia={ true }
838896
onSelect={ this.onSelectMediaUploadOption }
897+
onSelectURL={ this.onSelectURL }
839898
render={ ( { open, getMediaOptions } ) => {
840899
return getImageComponent( open, getMediaOptions );
841900
} }
@@ -881,7 +940,10 @@ export default compose( [
881940
};
882941
} ),
883942
withDispatch( ( dispatch ) => {
943+
const { createErrorNotice } = dispatch( noticesStore );
944+
884945
return {
946+
createErrorNotice,
885947
closeSettingsBottomSheet() {
886948
dispatch( editPostStore ).closeGeneralSidebar();
887949
},

packages/block-library/src/image/styles.native.scss

+11
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,14 @@
5757
.removeFeaturedButton {
5858
color: $alert-red;
5959
}
60+
61+
.image__loading {
62+
align-items: center;
63+
background-color: rgba(10, 10, 10, 0.5);
64+
flex: 1;
65+
height: 100%;
66+
justify-content: center;
67+
position: absolute;
68+
width: 100%;
69+
z-index: 1;
70+
}

0 commit comments

Comments
 (0)