@wordpress/block-editor
Version:
357 lines (321 loc) • 8.74 kB
JavaScript
/**
* External dependencies
*/
import { Platform } from 'react-native';
/**
* WordPress dependencies
*/
import { Component, React } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import {
BottomSheet,
PanelBody,
Picker,
TextControl,
} from '@wordpress/components';
import {
getOtherMediaOptions,
requestMediaPicker,
mediaSources,
} from '@wordpress/react-native-bridge';
import {
capturePhoto,
captureVideo,
image,
wordpress,
mobile,
globe,
} from '@wordpress/icons';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { compose } from '@wordpress/compose';
import { withSelect } from '@wordpress/data';
/**
* Internal dependencies
*/
import {
MEDIA_TYPE_IMAGE,
MEDIA_TYPE_VIDEO,
MEDIA_TYPE_AUDIO,
MEDIA_TYPE_ANY,
OPTION_TAKE_VIDEO,
OPTION_TAKE_PHOTO,
OPTION_INSERT_FROM_URL,
OPTION_WORDPRESS_MEDIA_LIBRARY,
} from './constants';
import styles from './style.scss';
const URL_MEDIA_SOURCE = 'URL';
const PICKER_OPENING_DELAY = 200;
export class MediaUpload extends Component {
pickerTimeout;
constructor( props ) {
super( props );
this.onPickerPresent = this.onPickerPresent.bind( this );
this.onPickerSelect = this.onPickerSelect.bind( this );
this.getAllSources = this.getAllSources.bind( this );
this.state = {
url: '',
showURLInput: false,
otherMediaOptions: [],
};
}
componentDidMount() {
const { allowedTypes = [], autoOpen } = this.props;
getOtherMediaOptions( allowedTypes, ( otherMediaOptions ) => {
const otherMediaOptionsWithIcons = otherMediaOptions.map(
( option ) => {
return {
...option,
requiresModal: true,
types: allowedTypes,
id: option.value,
};
}
);
this.setState( { otherMediaOptions: otherMediaOptionsWithIcons } );
} );
if ( autoOpen ) {
this.onPickerPresent();
}
}
componentWillUnmount() {
clearTimeout( this.pickerTimeout );
}
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.
label: OPTION_TAKE_PHOTO,
requiresModal: true,
types: [ MEDIA_TYPE_IMAGE ],
icon: capturePhoto,
};
const cameraVideoSource = {
id: mediaSources.deviceCamera,
value: mediaSources.deviceCamera,
label: OPTION_TAKE_VIDEO,
requiresModal: true,
types: [ MEDIA_TYPE_VIDEO ],
icon: captureVideo,
};
const deviceLibrarySource = {
id: mediaSources.deviceLibrary,
value: mediaSources.deviceLibrary,
label: __( 'Choose from device' ),
requiresModal: true,
types: [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ],
icon: image,
};
const siteLibrarySource = {
id: mediaSources.siteMediaLibrary,
value: mediaSources.siteMediaLibrary,
label: OPTION_WORDPRESS_MEDIA_LIBRARY,
requiresModal: true,
types: [
MEDIA_TYPE_IMAGE,
MEDIA_TYPE_VIDEO,
MEDIA_TYPE_AUDIO,
MEDIA_TYPE_ANY,
],
icon: wordpress,
mediaLibrary: true,
};
const urlSource = {
id: URL_MEDIA_SOURCE,
value: URL_MEDIA_SOURCE,
label: OPTION_INSERT_FROM_URL,
types: [ MEDIA_TYPE_AUDIO, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ],
icon: globe,
};
// Only include `urlSource` option if `onSelectURL` prop is present, in order to match the web behavior.
const internalSources = [
deviceLibrarySource,
cameraImageSource,
cameraVideoSource,
siteLibrarySource,
...( onSelectURL ? [ urlSource ] : [] ),
];
return internalSources.concat( this.state.otherMediaOptions );
}
getMediaOptionsItems() {
const {
allowedTypes = [],
__experimentalOnlyMediaLibrary,
isAudioBlockMediaUploadEnabled,
} = this.props;
return this.getAllSources()
.filter( ( source ) => {
if ( __experimentalOnlyMediaLibrary ) {
return source.mediaLibrary;
} else if (
allowedTypes.every(
( allowedType ) =>
allowedType === MEDIA_TYPE_AUDIO &&
source.types.includes( allowedType )
) &&
source.id !== URL_MEDIA_SOURCE
) {
return isAudioBlockMediaUploadEnabled === true;
}
return allowedTypes.some( ( allowedType ) =>
source.types.includes( allowedType )
);
} )
.map( ( source ) => {
return {
...source,
icon: source.icon || this.getChooseFromDeviceIcon(),
};
} );
}
getChooseFromDeviceIcon() {
return mobile;
}
onPickerPresent() {
const { autoOpen } = this.props;
const isIOS = Platform.OS === 'ios';
if ( this.picker ) {
// the delay below is required because on iOS this action sheet gets dismissed by the close event of the Inserter
// so this delay allows the Inserter to be closed fully before presenting action sheet.
if ( autoOpen && isIOS ) {
this.pickerTimeout = setTimeout(
() => this.picker.presentPicker(),
PICKER_OPENING_DELAY
);
} else {
this.picker.presentPicker();
}
}
}
onPickerSelect( value ) {
const { allowedTypes = [], onSelect, multiple = false } = this.props;
if ( value === URL_MEDIA_SOURCE ) {
this.setState( { showURLInput: true } );
return;
}
const mediaSource = this.getAllSources()
.filter( ( source ) => source.value === value )
.shift();
const types = allowedTypes.filter( ( type ) =>
mediaSource.types.includes( type )
);
requestMediaPicker( mediaSource.id, types, multiple, ( media ) => {
if ( ( multiple && media ) || ( media && media.id ) ) {
onSelect( media );
}
} );
}
render() {
const { allowedTypes = [], isReplacingMedia, multiple } = this.props;
const isOneType = allowedTypes.length === 1;
const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE );
const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO );
const isAudio = isOneType && allowedTypes.includes( MEDIA_TYPE_AUDIO );
const isAnyType = isOneType && allowedTypes.includes( MEDIA_TYPE_ANY );
const isImageOrVideo =
allowedTypes.length === 2 &&
allowedTypes.includes( MEDIA_TYPE_IMAGE ) &&
allowedTypes.includes( MEDIA_TYPE_VIDEO );
let pickerTitle;
if ( isImage ) {
if ( isReplacingMedia ) {
pickerTitle = __( 'Replace image' );
} else {
pickerTitle = multiple
? __( 'Choose images' )
: __( 'Choose image' );
}
} else if ( isVideo ) {
if ( isReplacingMedia ) {
pickerTitle = __( 'Replace video' );
} else {
pickerTitle = __( 'Choose video' );
}
} else if ( isImageOrVideo ) {
if ( isReplacingMedia ) {
pickerTitle = __( 'Replace image or video' );
} else {
pickerTitle = __( 'Choose image or video' );
}
} else if ( isAudio ) {
if ( isReplacingMedia ) {
pickerTitle = __( 'Replace audio' );
} else {
pickerTitle = __( 'Choose audio' );
}
} else if ( isAnyType ) {
pickerTitle = __( 'Choose file' );
if ( isReplacingMedia ) {
pickerTitle = __( 'Replace file' );
} else {
pickerTitle = __( 'Choose file' );
}
}
const getMediaOptions = () => (
<Picker
title={ pickerTitle }
hideCancelButton
ref={ ( instance ) => ( this.picker = instance ) }
options={ this.getMediaOptionsItems() }
onChange={ this.onPickerSelect }
testID="media-options-picker"
/>
);
return (
<>
<URLInput
isVisible={ this.state.showURLInput }
onClose={ () => {
if ( this.state.url !== '' ) {
this.props.onSelectURL( this.state.url );
}
this.setState( { showURLInput: false, url: '' } );
} }
onChange={ ( url ) => {
this.setState( { url } );
} }
value={ this.state.url }
/>
{ this.props.render( {
open: this.onPickerPresent,
getMediaOptions,
} ) }
</>
);
}
}
function URLInput( props ) {
return (
<BottomSheet
hideHeader
isVisible={ props.isVisible }
onClose={ props.onClose }
>
<PanelBody style={ styles[ 'media-upload__link-input' ] }>
<TextControl
// TODO: Switch to `true` (40px size) if possible
__next40pxDefaultSize={ false }
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
autoCapitalize="none"
autoCorrect={ false }
autoComplete={ Platform.isIOS ? 'url' : 'off' }
keyboardType="url"
label={ OPTION_INSERT_FROM_URL }
onChange={ props.onChange }
placeholder={ __( 'Type a URL' ) }
value={ props.value }
/>
</PanelBody>
</BottomSheet>
);
}
export default compose( [
withSelect( ( select ) => {
const { capabilities } = select( blockEditorStore ).getSettings();
return {
isAudioBlockMediaUploadEnabled:
capabilities?.isAudioBlockMediaUploadEnabled === true,
};
} ),
] )( MediaUpload );