Skip to content

Commit

Permalink
Image block: allow uploading external image if image host allows it (#…
Browse files Browse the repository at this point in the history
…23565)

* Image block: allow uploading external image

* Catch if host doesn't allow cross origin

* Adjust icon

* Fix e2e tests

* Only show button if image can bee uploaded
  • Loading branch information
ellatrix authored Jul 2, 2020
1 parent 2e80a87 commit 3feea68
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 6 deletions.
2 changes: 1 addition & 1 deletion packages/block-library/src/image/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const isTemporaryImage = ( id, url ) => ! id && isBlobURL( url );
*
* @return {boolean} Is the url an externally hosted url?
*/
const isExternalImage = ( id, url ) => url && ! id && ! isBlobURL( url );
export const isExternalImage = ( id, url ) => url && ! id && ! isBlobURL( url );

export function ImageEdit( {
attributes,
Expand Down
67 changes: 62 additions & 5 deletions packages/block-library/src/image/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ import { useEffect, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { getPath } from '@wordpress/url';
import { createBlock } from '@wordpress/blocks';
import { crop } from '@wordpress/icons';
import { crop, upload } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { createUpgradedEmbedBlock } from '../embed/util';
import useClientWidth from './use-client-width';
import ImageEditor from './image-editor';
import { isExternalImage } from './edit';

/**
* Module constants
Expand Down Expand Up @@ -86,16 +87,27 @@ export default function Image( {
},
[ id, isSelected ]
);
const { maxWidth, isRTL, imageSizes } = useSelect( ( select ) => {
const { getSettings } = select( 'core/block-editor' );
return pick( getSettings(), [ 'imageSizes', 'isRTL', 'maxWidth' ] );
} );
const { maxWidth, isRTL, imageSizes, mediaUpload } = useSelect(
( select ) => {
const { getSettings } = select( 'core/block-editor' );
return pick( getSettings(), [
'imageSizes',
'isRTL',
'maxWidth',
'mediaUpload',
] );
}
);
const { toggleSelection } = useDispatch( 'core/block-editor' );
const { createErrorNotice, createSuccessNotice } = useDispatch(
'core/notices'
);
const isLargeViewport = useViewportMatch( 'medium' );
const [ captionFocused, setCaptionFocused ] = useState( false );
const isWideAligned = includes( [ 'wide', 'full' ], align );
const [ { naturalWidth, naturalHeight }, setNaturalSize ] = useState( {} );
const [ isEditingImage, setIsEditingImage ] = useState( false );
const [ externalBlob, setExternalBlob ] = useState();
const clientWidth = useClientWidth( containerRef, [ align ] );
const isResizable = ! isWideAligned && isLargeViewport;
const imageSizeOptions = map(
Expand All @@ -111,6 +123,20 @@ export default function Image( {
}
}, [ isSelected ] );

// If an image is externally hosted, try to fetch the image data. This may
// fail if the image host doesn't allow CORS with the domain. If it works,
// we can enable a button in the toolbar to upload the image.
useEffect( () => {
if ( ! isExternalImage( id, url ) || ! isSelected || externalBlob ) {
return;
}

window
.fetch( url )
.then( ( response ) => response.blob() )
.then( ( blob ) => setExternalBlob( blob ) );
}, [ id, url, isSelected, externalBlob ] );

function onResizeStart() {
toggleSelection( false );
}
Expand Down Expand Up @@ -172,6 +198,28 @@ export default function Image( {
} );
}

function uploadExternal() {
mediaUpload( {
filesList: [ externalBlob ],
onFileChange( [ img ] ) {
onSelectImage( img );

if ( isBlobURL( img.url ) ) {
return;
}

setExternalBlob();
createSuccessNotice( __( 'Image uploaded.' ), {
type: 'snackbar',
} );
},
allowedTypes: ALLOWED_MEDIA_TYPES,
onError( message ) {
createErrorNotice( message, { type: 'snackbar' } );
},
} );
}

useEffect( () => {
if ( ! isSelected ) {
setIsEditingImage( false );
Expand Down Expand Up @@ -206,6 +254,15 @@ export default function Image( {
/>
</ToolbarGroup>
) }
{ externalBlob && (
<ToolbarGroup>
<ToolbarButton
onClick={ uploadExternal }
icon={ upload }
label={ __( 'Upload external image' ) }
/>
</ToolbarGroup>
) }
{ ! isEditingImage && (
<MediaReplaceFlow
mediaId={ id }
Expand Down

0 comments on commit 3feea68

Please sign in to comment.