diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 7ab5c7ebbc8c3..d1f6fdd785089 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -74,7 +74,8 @@ export default { // In the experiment we want to also show column control in Auto mode, and // the minimum width control in Manual mode. const showColumnsControl = - window.__experimentalEnableGridInteractivity || layout?.columnCount; + window.__experimentalEnableGridInteractivity || + !! layout?.columnCount; const showMinWidthControl = window.__experimentalEnableGridInteractivity || ! layout?.columnCount; @@ -317,7 +318,7 @@ function GridLayoutColumnsAndRowsControl( { const defaultNewColumnCount = isManualPlacement ? 1 : undefined; const newColumnCount = - value === '' + value === '' || value === '0' ? defaultNewColumnCount : parseInt( value, 10 ); onChange( { @@ -327,7 +328,7 @@ function GridLayoutColumnsAndRowsControl( { } else { // Don't allow unsetting the column count. const newColumnCount = - value === '' + value === '' || value === '0' ? 1 : parseInt( value, 10 ); onChange( { @@ -337,7 +338,7 @@ function GridLayoutColumnsAndRowsControl( { } } } value={ columnCount } - min={ 0 } + min={ 1 } label={ __( 'Columns' ) } hideLabelFromVision={ ! window.__experimentalEnableGridInteractivity || @@ -355,7 +356,7 @@ function GridLayoutColumnsAndRowsControl( { onChange={ ( value ) => { // Don't allow unsetting the row count. const newRowCount = - value === '' + value === '' || value === '0' ? 1 : parseInt( value, 10 ); onChange( { @@ -364,21 +365,24 @@ function GridLayoutColumnsAndRowsControl( { } ); } } value={ rowCount } - min={ 0 } + min={ 1 } label={ __( 'Rows' ) } /> ) : ( onChange( { ...layout, - columnCount: value, + columnCount: + value === '' || value === '0' + ? 1 + : value, } ) } - min={ 0 } + min={ 1 } max={ 16 } withInputField={ false } label={ __( 'Columns' ) } diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js index b925e80e59503..c381a62f78386 100644 --- a/packages/block-library/src/file/transforms.js +++ b/packages/block-library/src/file/transforms.js @@ -24,12 +24,32 @@ const transforms = { const blobURL = createBlobURL( file ); // File will be uploaded in componentDidMount() - blocks.push( - createBlock( 'core/file', { - blob: blobURL, - fileName: file.name, - } ) - ); + if ( file.type.startsWith( 'video/' ) ) { + blocks.push( + createBlock( 'core/video', { + blob: createBlobURL( file ), + } ) + ); + } else if ( file.type.startsWith( 'image/' ) ) { + blocks.push( + createBlock( 'core/image', { + blob: createBlobURL( file ), + } ) + ); + } else if ( file.type.startsWith( 'audio/' ) ) { + blocks.push( + createBlock( 'core/audio', { + blob: createBlobURL( file ), + } ) + ); + } else { + blocks.push( + createBlock( 'core/file', { + blob: blobURL, + fileName: file.name, + } ) + ); + } } ); return blocks; diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index f9aed8d93f95a..d44dc73abfd85 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -12,8 +12,8 @@ import { Placeholder } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { BlockIcon, - MediaPlaceholder, useBlockProps, + MediaPlaceholder, store as blockEditorStore, __experimentalUseBorderProps as useBorderProps, __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, @@ -32,6 +32,7 @@ import { unlock } from '../lock-unlock'; import { useUploadMediaFromBlobURL } from '../utils/hooks'; import Image from './image'; import { isValidFileType } from './utils'; +import { useMaxWidthObserver } from './use-max-width-observer'; /** * Module constants @@ -109,12 +110,23 @@ export function ImageEdit( { align, metadata, } = attributes; + const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob ); - const figureRef = useRef(); - const [ contentResizeListener, { width: containerWidth } ] = + const containerRef = useRef(); + // Only observe the max width from the parent container when the parent layout is not flex nor grid. + // This won't work for them because the container width changes with the image. + // TODO: Find a way to observe the container width for flex and grid layouts. + const isMaxWidthContainerWidth = + ! parentLayout || + ( parentLayout.type !== 'flex' && parentLayout.type !== 'grid' ); + const [ maxWidthObserver, maxContentWidth ] = useMaxWidthObserver(); + + const [ placeholderResizeListener, { width: placeholderWidth } ] = useResizeObserver(); + const isSmallContainer = placeholderWidth && placeholderWidth < 160; + const altRef = useRef(); useEffect( () => { altRef.current = alt; @@ -160,7 +172,7 @@ export function ImageEdit( { } function onSelectImagesList( images ) { - const win = figureRef.current?.ownerDocument.defaultView; + const win = containerRef.current?.ownerDocument.defaultView; if ( images.every( ( file ) => file instanceof win.File ) ) { /** @type {File[]} */ @@ -348,7 +360,10 @@ export function ImageEdit( { Object.keys( borderProps.style ).length > 0 ), } ); - const blockProps = useBlockProps( { ref: figureRef, className: classes } ); + const blockProps = useBlockProps( { + ref: containerRef, + className: classes, + } ); // Much of this description is duplicated from MediaPlaceholder. const { lockUrlControls = false, lockUrlControlsMessage } = useSelect( @@ -387,11 +402,15 @@ export function ImageEdit( { [ borderProps.className ]: !! borderProps.className && ! isSingleSelected, } ) } - withIllustration - icon={ lockUrlControls ? pluginsIcon : icon } - label={ __( 'Image' ) } + icon={ + ! isSmallContainer && + ( lockUrlControls ? pluginsIcon : icon ) + } + withIllustration={ ! isSingleSelected || isSmallContainer } + label={ ! isSmallContainer && __( 'Image' ) } instructions={ ! lockUrlControls && + ! isSmallContainer && __( 'Upload or drag an image file here, or pick one from your library.' ) @@ -408,13 +427,12 @@ export function ImageEdit( { ...shadowProps.style, } } > - { lockUrlControls ? ( - - { lockUrlControlsMessage } - - ) : ( - content - ) } + { lockUrlControls && + ! isSmallContainer && + lockUrlControlsMessage } + + { ! lockUrlControls && ! isSmallContainer && content } + { placeholderResizeListener } ); }; @@ -436,7 +454,7 @@ export function ImageEdit( { clientId={ clientId } blockEditingMode={ blockEditingMode } parentLayoutType={ parentLayout?.type } - containerWidth={ containerWidth } + maxContentWidth={ maxContentWidth } /> } @@ -455,7 +473,7 @@ export function ImageEdit( { { // The listener cannot be placed as the first element as it will break the in-between inserter. // See https://github.com/WordPress/gutenberg/blob/71134165868298fc15e22896d0c28b41b3755ff7/packages/block-editor/src/components/block-list/use-in-between-inserter.js#L120 - contentResizeListener + isSingleSelected && isMaxWidthContainerWidth && maxWidthObserver } ); diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index 636741c7d9ddb..34f65d690d3d7 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -1,44 +1,8 @@ // Provide special styling for the placeholder. // @todo this particular minimal style of placeholder could be componentized further. .wp-block-image.wp-block-image { - - // Show Placeholder style on-select. - &.is-selected .block-editor-media-placeholder { - // Block UI appearance. - color: $gray-900; - background-color: $white; - box-shadow: inset 0 0 0 $border-width $gray-900; - border: none; - - // Disable any duotone filter applied in the selected state. - filter: none !important; - - > svg { - opacity: 0; - } - - .components-placeholder__illustration { - display: none; - } - - &::before { - opacity: 0; - } - } - .block-bindings-media-placeholder-message { - opacity: 0; - } - &.is-selected .block-bindings-media-placeholder-message { - opacity: 1; - } - - // Remove the transition while we still have a legacy placeholder style. - // Otherwise the content jumps between the 1px placeholder border, and any inherited custom - // parent border that may get applied when you deselect. - .components-placeholder__label, - .components-placeholder__instructions, - .components-button { - transition: none; + .block-editor-media-placeholder.is-small { + min-height: 60px; } } @@ -149,6 +113,11 @@ figure.wp-block-image:not(.wp-block) { text-align: center; } +// Relatively position the alignment container to support the content resizer. +.wp-block[data-align]:has(> .wp-block-image) { + position: relative; +} + .wp-block-image__crop-area { position: relative; max-width: 100%; diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 2de316f78ba5d..60d83f8912907 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -108,7 +108,7 @@ export default function Image( { clientId, blockEditingMode, parentLayoutType, - containerWidth, + maxContentWidth, } ) { const { url = '', @@ -556,6 +556,24 @@ export default function Image( { const showBlockControls = showUrlInput || allowCrop || showCoverControls; + const mediaReplaceFlow = isSingleSelected && + ! isEditingImage && + ! lockUrlControls && ( + + onSelectImage( undefined ) } + /> + + ); + const controls = ( <> { showBlockControls && ( @@ -592,20 +610,6 @@ export default function Image( { ) } ) } - { isSingleSelected && ! isEditingImage && ! lockUrlControls && ( - - onSelectImage( undefined ) } - /> - - ) } { isSingleSelected && externalBlob && ( @@ -934,7 +938,7 @@ export default function Image( { // @todo It would be good to revisit this once a content-width variable // becomes available. const maxWidthBuffer = maxWidth * 2.5; - const maxContentWidth = containerWidth || maxWidthBuffer; + const maxResizeWidth = maxContentWidth || maxWidthBuffer; let showRightHandle = false; let showLeftHandle = false; @@ -980,9 +984,9 @@ export default function Image( { } } showHandle={ isSingleSelected } minWidth={ minWidth } - maxWidth={ maxContentWidth } + maxWidth={ maxResizeWidth } minHeight={ minHeight } - maxHeight={ maxContentWidth / ratio } + maxHeight={ maxResizeWidth / ratio } lockAspectRatio={ ratio } enable={ { top: false, @@ -996,6 +1000,7 @@ export default function Image( { // Clear hardcoded width if the resized width is close to the max-content width. if ( + maxContentWidth && // Only do this if the image is bigger than the container to prevent it from being squished. // TODO: Remove this check if the image support setting 100% width. naturalWidth >= maxContentWidth && @@ -1029,12 +1034,18 @@ export default function Image( { } if ( ! url && ! temporaryURL ) { - // Add all controls if the image attributes are connected. - return metadata?.bindings ? controls : sizeControls; + return ( + <> + { mediaReplaceFlow } + { /* Add all controls if the image attributes are connected. */ } + { metadata?.bindings ? controls : sizeControls } + + ); } return ( <> + { mediaReplaceFlow } { controls } { img } diff --git a/packages/block-library/src/image/transforms.js b/packages/block-library/src/image/transforms.js index 0e1dfb6ee9da4..347d240828017 100644 --- a/packages/block-library/src/image/transforms.js +++ b/packages/block-library/src/image/transforms.js @@ -3,9 +3,6 @@ */ import { createBlobURL, isBlobURL } from '@wordpress/blob'; import { createBlock, getBlockAttributes } from '@wordpress/blocks'; -import { dispatch } from '@wordpress/data'; -import { store as noticesStore } from '@wordpress/notices'; -import { __ } from '@wordpress/i18n'; export function stripFirstImage( attributes, { shortcode } ) { const { body } = document.implementation.createHTMLDocument( '' ); @@ -138,26 +135,6 @@ const transforms = { // creating a new gallery. type: 'files', isMatch( files ) { - // The following check is intended to catch non-image files when dropped together with images. - if ( - files.some( - ( file ) => file.type.indexOf( 'image/' ) === 0 - ) && - files.some( - ( file ) => file.type.indexOf( 'image/' ) !== 0 - ) - ) { - const { createErrorNotice } = dispatch( noticesStore ); - createErrorNotice( - __( - 'If uploading to a gallery all files need to be image formats' - ), - { - id: 'gallery-transform-invalid-file', - type: 'snackbar', - } - ); - } return files.every( ( file ) => file.type.indexOf( 'image/' ) === 0 ); diff --git a/packages/block-library/src/image/use-max-width-observer.js b/packages/block-library/src/image/use-max-width-observer.js new file mode 100644 index 0000000000000..684392537fac7 --- /dev/null +++ b/packages/block-library/src/image/use-max-width-observer.js @@ -0,0 +1,32 @@ +/** + * WordPress dependencies + */ +import { useRef } from '@wordpress/element'; +import { useResizeObserver } from '@wordpress/compose'; + +function useMaxWidthObserver() { + const [ contentResizeListener, { width } ] = useResizeObserver(); + const observerRef = useRef(); + + const maxWidthObserver = ( + + ); + + return [ maxWidthObserver, width ]; +} + +export { useMaxWidthObserver };