@wordpress/block-library
Version:
Block library for the WordPress editor.
403 lines (351 loc) • 11.1 kB
JavaScript
/**
* External dependencies
*/
import React from 'react';
import { View, ImageBackground, TextInput, Text, TouchableWithoutFeedback } from 'react-native';
import {
subscribeMediaUpload,
requestMediaPickFromMediaLibrary,
requestMediaPickFromDeviceLibrary,
requestMediaPickFromDeviceCamera,
mediaUploadSync,
requestImageFailedRetryDialog,
requestImageUploadCancelDialog,
} from 'react-native-gutenberg-bridge';
/**
* WordPress dependencies
*/
import {
Toolbar,
ToolbarButton,
Spinner,
Dashicon,
} from '@wordpress/components';
import {
MediaPlaceholder,
RichText,
BlockControls,
InspectorControls,
} from '@wordpress/block-editor';
import {
BottomSheet,
Picker,
} from '@wordpress/editor';
import { __ } from '@wordpress/i18n';
import { isURL } from '@wordpress/url';
/**
* Internal dependencies
*/
import ImageSize from './image-size';
import styles from './styles.scss';
const MEDIA_UPLOAD_STATE_UPLOADING = 1;
const MEDIA_UPLOAD_STATE_SUCCEEDED = 2;
const MEDIA_UPLOAD_STATE_FAILED = 3;
const MEDIA_UPLOAD_STATE_RESET = 4;
const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device';
const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO = 'take_photo';
const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library';
const LINK_DESTINATION_CUSTOM = 'custom';
const LINK_DESTINATION_NONE = 'none';
class ImageEdit extends React.Component {
constructor( props ) {
super( props );
this.state = {
showSettings: false,
progress: 0,
isUploadInProgress: false,
isUploadFailed: false,
};
this.mediaUpload = this.mediaUpload.bind( this );
this.addMediaUploadListener = this.addMediaUploadListener.bind( this );
this.removeMediaUploadListener = this.removeMediaUploadListener.bind( this );
this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this );
this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this );
this.updateMediaProgress = this.updateMediaProgress.bind( this );
this.updateAlt = this.updateAlt.bind( this );
this.updateImageURL = this.updateImageURL.bind( this );
this.onSetLinkDestination = this.onSetLinkDestination.bind( this );
this.onImagePressed = this.onImagePressed.bind( this );
this.onClearSettings = this.onClearSettings.bind( this );
}
componentDidMount() {
const { attributes } = this.props;
if ( attributes.id && ! isURL( attributes.url ) ) {
this.addMediaUploadListener();
mediaUploadSync();
}
}
componentWillUnmount() {
this.removeMediaUploadListener();
}
onImagePressed() {
const { attributes } = this.props;
if ( this.state.isUploadInProgress ) {
requestImageUploadCancelDialog( attributes.id );
} else if ( attributes.id && ! isURL( attributes.url ) ) {
requestImageFailedRetryDialog( attributes.id );
}
}
mediaUpload( payload ) {
const { attributes } = this.props;
if ( payload.mediaId !== attributes.id ) {
return;
}
switch ( payload.state ) {
case MEDIA_UPLOAD_STATE_UPLOADING:
this.updateMediaProgress( payload );
break;
case MEDIA_UPLOAD_STATE_SUCCEEDED:
this.finishMediaUploadWithSuccess( payload );
break;
case MEDIA_UPLOAD_STATE_FAILED:
this.finishMediaUploadWithFailure( payload );
break;
case MEDIA_UPLOAD_STATE_RESET:
this.mediaUploadStateReset( payload );
break;
}
}
updateMediaProgress( payload ) {
const { setAttributes } = this.props;
this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } );
if ( payload.mediaUrl ) {
setAttributes( { url: payload.mediaUrl } );
}
}
finishMediaUploadWithSuccess( payload ) {
const { setAttributes } = this.props;
setAttributes( { url: payload.mediaUrl, id: payload.mediaServerId } );
this.setState( { isUploadInProgress: false } );
this.removeMediaUploadListener();
}
finishMediaUploadWithFailure( payload ) {
const { setAttributes } = this.props;
setAttributes( { id: payload.mediaId } );
this.setState( { isUploadInProgress: false, isUploadFailed: true } );
}
mediaUploadStateReset( payload ) {
const { setAttributes } = this.props;
setAttributes( { id: payload.mediaId, url: null } );
this.setState( { isUploadInProgress: false, isUploadFailed: false } );
}
addMediaUploadListener() {
//if we already have a subscription not worth doing it again
if ( this.subscriptionParentMediaUpload ) {
return;
}
this.subscriptionParentMediaUpload = subscribeMediaUpload( ( payload ) => {
this.mediaUpload( payload );
} );
}
removeMediaUploadListener() {
if ( this.subscriptionParentMediaUpload ) {
this.subscriptionParentMediaUpload.remove();
}
}
updateAlt( newAlt ) {
this.props.setAttributes( { alt: newAlt } );
}
updateImageURL( url ) {
this.props.setAttributes( { url, width: undefined, height: undefined } );
}
onSetLinkDestination( href ) {
this.props.setAttributes( {
linkDestination: LINK_DESTINATION_CUSTOM,
href,
} );
}
onClearSettings() {
this.props.setAttributes( {
alt: '',
linkDestination: LINK_DESTINATION_NONE,
href: undefined,
} );
}
getMediaOptionsItems() {
return [
{ icon: 'format-image', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) },
{ icon: 'camera', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO, label: __( 'Take a Photo' ) },
{ icon: 'wordpress-alt', value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) },
];
}
render() {
const { attributes, isSelected, setAttributes } = this.props;
const { url, caption, height, width, alt, href } = attributes;
const onMediaLibraryButtonPressed = () => {
requestMediaPickFromMediaLibrary( ( mediaId, mediaUrl ) => {
if ( mediaUrl ) {
setAttributes( { id: mediaId, url: mediaUrl } );
}
} );
};
const onMediaUploadButtonPressed = () => {
requestMediaPickFromDeviceLibrary( ( mediaId, mediaUri ) => {
if ( mediaUri ) {
this.addMediaUploadListener( );
setAttributes( { url: mediaUri, id: mediaId } );
}
} );
};
const onMediaCaptureButtonPressed = () => {
requestMediaPickFromDeviceCamera( ( mediaId, mediaUri ) => {
if ( mediaUri ) {
this.addMediaUploadListener( );
setAttributes( { url: mediaUri, id: mediaId } );
}
} );
};
const onImageSettingsButtonPressed = () => {
this.setState( { showSettings: true } );
};
const onImageSettingsClose = () => {
this.setState( { showSettings: false } );
};
let picker;
const onMediaOptionsButtonPressed = () => {
picker.presentPicker();
};
const toolbarEditButton = (
<Toolbar>
<ToolbarButton
label={ __( 'Edit image' ) }
icon="edit"
onClick={ onMediaOptionsButtonPressed }
/>
</Toolbar>
);
const getInspectorControls = () => (
<BottomSheet
isVisible={ this.state.showSettings }
onClose={ onImageSettingsClose }
hideHeader
>
<BottomSheet.Cell
icon={ 'admin-links' }
label={ __( 'Link To' ) }
value={ href || '' }
valuePlaceholder={ __( 'Add URL' ) }
onChangeValue={ this.onSetLinkDestination }
autoCapitalize="none"
autoCorrect={ false }
/>
<BottomSheet.Cell
icon={ 'editor-textcolor' }
label={ __( 'Alt Text' ) }
value={ alt || '' }
valuePlaceholder={ __( 'None' ) }
separatorType={ 'fullWidth' }
onChangeValue={ this.updateAlt }
/>
<BottomSheet.Cell
label={ __( 'Clear All Settings' ) }
labelStyle={ styles.clearSettingsButton }
separatorType={ 'none' }
onPress={ this.onClearSettings }
/>
</BottomSheet>
);
const mediaOptions = this.getMediaOptionsItems();
const getMediaOptions = () => (
<Picker
hideCancelButton={ true }
ref={ ( instance ) => picker = instance }
options={ mediaOptions }
onChange={ ( value ) => {
if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) {
onMediaUploadButtonPressed();
} else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_PHOTO ) {
onMediaCaptureButtonPressed();
} else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) {
onMediaLibraryButtonPressed();
}
} }
/>
);
if ( ! url ) {
return (
<View style={ { flex: 1 } } >
{ getMediaOptions() }
<MediaPlaceholder
onMediaOptionsPressed={ onMediaOptionsButtonPressed }
/>
</View>
);
}
const showSpinner = this.state.isUploadInProgress;
const opacity = this.state.isUploadInProgress ? 0.3 : 1;
const progress = this.state.progress * 100;
return (
<TouchableWithoutFeedback onPress={ this.onImagePressed } disabled={ ! isSelected }>
<View style={ { flex: 1 } }>
{ showSpinner && <Spinner progress={ progress } /> }
<BlockControls>
{ toolbarEditButton }
</BlockControls>
<InspectorControls>
<ToolbarButton
label={ __( 'Image Settings' ) }
icon="admin-generic"
onClick={ onImageSettingsButtonPressed }
/>
</InspectorControls>
<ImageSize src={ url } >
{ ( sizes ) => {
const {
imageWidthWithinContainer,
imageHeightWithinContainer,
} = sizes;
if ( imageWidthWithinContainer === undefined ) {
return (
<View style={ styles.imageContainer } >
<Dashicon icon={ 'format-image' } size={ 300 } />
</View>
);
}
let finalHeight = imageHeightWithinContainer;
if ( height > 0 && height < imageHeightWithinContainer ) {
finalHeight = height;
}
let finalWidth = imageWidthWithinContainer;
if ( width > 0 && width < imageWidthWithinContainer ) {
finalWidth = width;
}
return (
<View style={ { flex: 1 } } >
{ getInspectorControls() }
{ getMediaOptions() }
<ImageBackground
style={ { width: finalWidth, height: finalHeight, opacity } }
resizeMethod="scale"
source={ { uri: url } }
key={ url }
>
{ this.state.isUploadFailed &&
<View style={ styles.imageContainer } >
<Dashicon icon={ 'image-rotate' } ariaPressed={ 'dashicon-active' } />
<Text style={ styles.uploadFailedText }>{ __( 'Failed to insert media.\nPlease tap for options.' ) }</Text>
</View>
}
</ImageBackground>
</View>
);
} }
</ImageSize>
{ ( ! RichText.isEmpty( caption ) > 0 || isSelected ) && (
<View style={ { padding: 12, flex: 1 } }>
<TextInput
style={ { textAlign: 'center' } }
fontFamily={ this.props.fontFamily || ( styles[ 'caption-text' ].fontFamily ) }
underlineColorAndroid="transparent"
value={ caption }
placeholder={ __( 'Write caption…' ) }
onChangeText={ ( newCaption ) => setAttributes( { caption: newCaption } ) }
/>
</View>
) }
</View>
</TouchableWithoutFeedback>
);
}
}
export default ImageEdit;