From 90a7c7e49bb79204b8745610bc691dfce51aeeae Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Thu, 5 Sep 2024 15:51:36 +0800 Subject: [PATCH] Allow dropping multiple images to the image block (#65030) co-authored-by: kevin940726 Co-authored-by: ramonjd Co-authored-by: Mamaduka Co-authored-by: andrewserong Co-authored-by: swissspidy Co-authored-by: richtabor * Allow dropping multiple images to the image block * Check for canInsert and file type * Separate the list callback --- .../components/media-placeholder/README.md | 4 +- .../src/components/media-placeholder/index.js | 5 +- packages/block-library/src/image/edit.js | 63 +++++++++++++++++-- packages/block-library/src/image/utils.js | 14 ++++- 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/media-placeholder/README.md b/packages/block-editor/src/components/media-placeholder/README.md index 6cb00206d93d72..68fcf150c4edc4 100644 --- a/packages/block-editor/src/components/media-placeholder/README.md +++ b/packages/block-editor/src/components/media-placeholder/README.md @@ -198,9 +198,9 @@ Callback called when urls can be configured. No media insertion from url will be ### handleUpload -When set to false the handling of the upload is left to the calling component. +When the value is set to `false` or returned as `false`, the handling of the upload is left to the consumer component. The function signature accepts an array containing the files to be uploaded. -- Type: `Boolean` +- Type: `Boolean|Function` - Required: No - Default: `true` - Platform: Web diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index a5b4eb0bf19b3f..bbf546a3953f32 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -172,7 +172,10 @@ export function MediaPlaceholder( { }; const onFilesUpload = ( files ) => { - if ( ! handleUpload ) { + if ( + ! handleUpload || + ( typeof handleUpload === 'function' && ! handleUpload( files ) ) + ) { return onSelect( files ); } onFilesPreUpload( files ); diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index b7c25595dcfccf..ae8461a6b1cf4c 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -6,8 +6,8 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { isBlobURL } from '@wordpress/blob'; -import { store as blocksStore } from '@wordpress/blocks'; +import { isBlobURL, createBlobURL } from '@wordpress/blob'; +import { store as blocksStore, createBlock } from '@wordpress/blocks'; import { Placeholder } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { @@ -31,6 +31,7 @@ import { useResizeObserver } from '@wordpress/compose'; import { unlock } from '../lock-unlock'; import { useUploadMediaFromBlobURL } from '../utils/hooks'; import Image from './image'; +import { isValidFileType } from './utils'; /** * Module constants @@ -109,6 +110,7 @@ export function ImageEdit( { metadata, } = attributes; const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob ); + const figureRef = useRef(); const [ contentResizeListener, { width: containerWidth } ] = useResizeObserver(); @@ -123,7 +125,7 @@ export function ImageEdit( { captionRef.current = caption; }, [ caption ] ); - const { __unstableMarkNextChangeAsNotPersistent } = + const { __unstableMarkNextChangeAsNotPersistent, replaceBlock } = useDispatch( blockEditorStore ); useEffect( () => { @@ -138,7 +140,12 @@ export function ImageEdit( { } }, [ __unstableMarkNextChangeAsNotPersistent, align, setAttributes ] ); - const { getSettings } = useSelect( blockEditorStore ); + const { + getSettings, + getBlockRootClientId, + getBlockName, + canInsertBlockType, + } = useSelect( blockEditorStore ); const blockEditingMode = useBlockEditingMode(); const { createErrorNotice } = useDispatch( noticesStore ); @@ -152,7 +159,52 @@ export function ImageEdit( { } ); } + function onSelectImagesList( images ) { + const win = figureRef.current?.ownerDocument.defaultView; + + if ( images.every( ( file ) => file instanceof win.File ) ) { + /** @type {File[]} */ + const files = images; + const rootClientId = getBlockRootClientId( clientId ); + + if ( files.some( ( file ) => ! isValidFileType( file ) ) ) { + // Copied from the same notice in the gallery block. + createErrorNotice( + __( + 'If uploading to a gallery all files need to be image formats' + ), + { id: 'gallery-upload-invalid-file', type: 'snackbar' } + ); + } + + const imageBlocks = files + .filter( ( file ) => isValidFileType( file ) ) + .map( ( file ) => + createBlock( 'core/image', { + blob: createBlobURL( file ), + } ) + ); + + if ( getBlockName( rootClientId ) === 'core/gallery' ) { + replaceBlock( clientId, imageBlocks ); + } else if ( canInsertBlockType( 'core/gallery', rootClientId ) ) { + const galleryBlock = createBlock( + 'core/gallery', + {}, + imageBlocks + ); + + replaceBlock( clientId, galleryBlock ); + } + } + } + function onSelectImage( media ) { + if ( Array.isArray( media ) ) { + onSelectImagesList( media ); + return; + } + if ( ! media || ! media.url ) { setAttributes( { url: undefined, @@ -296,7 +348,7 @@ export function ImageEdit( { Object.keys( borderProps.style ).length > 0 ), } ); - const blockProps = useBlockProps( { className: classes } ); + const blockProps = useBlockProps( { ref: figureRef, className: classes } ); // Much of this description is duplicated from MediaPlaceholder. const { lockUrlControls = false, lockUrlControlsMessage } = useSelect( @@ -394,6 +446,7 @@ export function ImageEdit( { placeholder={ placeholder } accept="image/*" allowedTypes={ ALLOWED_MEDIA_TYPES } + handleUpload={ ( files ) => files.length === 1 } value={ { id, src } } mediaPreview={ mediaPreview } disableMediaButtons={ temporaryURL || url } diff --git a/packages/block-library/src/image/utils.js b/packages/block-library/src/image/utils.js index 1ef7973b4e57a3..1541c3daac3aab 100644 --- a/packages/block-library/src/image/utils.js +++ b/packages/block-library/src/image/utils.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { NEW_TAB_REL } from './constants'; +import { NEW_TAB_REL, ALLOWED_MEDIA_TYPES } from './constants'; /** * Evaluates a CSS aspect-ratio property value as a number. @@ -81,3 +81,15 @@ export function getImageSizeAttributes( image, size ) { return {}; } + +/** + * Checks if the file has a valid file type. + * + * @param {File} file - The file to check. + * @return {boolean} - Returns true if the file has a valid file type, otherwise false. + */ +export function isValidFileType( file ) { + return ALLOWED_MEDIA_TYPES.some( + ( mediaType ) => file.type.indexOf( mediaType ) === 0 + ); +}