diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md
index 4306b855955f52..e8e873c3719d2b 100644
--- a/packages/block-editor/CHANGELOG.md
+++ b/packages/block-editor/CHANGELOG.md
@@ -1,5 +1,12 @@
## 2.0.0 (Unreleased)
+### New Features
+
+- Added the `addToGallery` property to the `MediaUpload` interface. The property allows users to open the media modal in the `gallery-library`instead of `gallery-edit` state.
+- Added the `addToGallery` property to the `MediaPlaceholder` component. The component passes the property to the `MediaUpload` component used inside the placeholder.
+- Added the `isAppender` property to the `MediaPlaceholder` component. The property changes the look of the placeholder to be adequate to scenarios where new files are added to an already existing set of files, e.g., adding files to a gallery.
+- Added the `dropZoneUIOnly` property to the `MediaPlaceholder` component. The property makes the `MediaPlaceholder` only render a dropzone without any other additional UI.
+
### Breaking Changes
- `CopyHandler` will now only catch cut/copy events coming from its `props.children`, instead of from anywhere in the `document`.
diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js
index d972aeca06bd7e..42fa7e7d8fbe71 100644
--- a/packages/block-editor/src/components/media-placeholder/index.js
+++ b/packages/block-editor/src/components/media-placeholder/index.js
@@ -1,7 +1,14 @@
/**
* External dependencies
*/
-import { every, get, noop, startsWith, defaultTo } from 'lodash';
+import {
+ defaultTo,
+ every,
+ get,
+ isArray,
+ noop,
+ startsWith,
+} from 'lodash';
import classnames from 'classnames';
/**
@@ -103,8 +110,28 @@ export class MediaPlaceholder extends Component {
}
onFilesUpload( files ) {
- const { onSelect, multiple, onError, allowedTypes, mediaUpload } = this.props;
- const setMedia = multiple ? onSelect : ( [ media ] ) => onSelect( media );
+ const {
+ addToGallery,
+ allowedTypes,
+ mediaUpload,
+ multiple,
+ onError,
+ onSelect,
+ value = [],
+ } = this.props;
+ let setMedia;
+ if ( multiple ) {
+ if ( addToGallery ) {
+ const currentValue = value;
+ setMedia = ( newMedia ) => {
+ onSelect( currentValue.concat( newMedia ) );
+ };
+ } else {
+ setMedia = onSelect;
+ }
+ } else {
+ setMedia = ( [ media ] ) => onSelect( media );
+ }
mediaUpload( {
allowedTypes,
filesList: files,
@@ -121,42 +148,32 @@ export class MediaPlaceholder extends Component {
this.setState( { isURLInputVisible: false } );
}
- render() {
+ renderPlaceholder( content, onClick ) {
const {
- accept,
- icon,
+ allowedTypes = [],
className,
+ hasUploadPermissions,
+ icon,
+ isAppender,
labels = {},
- onSelect,
- value = {},
- onSelectURL,
- onHTMLDrop = noop,
- multiple = false,
notices,
- allowedTypes = [],
- hasUploadPermissions,
- mediaUpload,
+ onSelectURL,
} = this.props;
- const {
- isURLInputVisible,
- src,
- } = this.state;
-
- let instructions = labels.instructions || '';
- let title = labels.title || '';
+ let instructions = labels.instructions;
+ let title = labels.title;
if ( ! hasUploadPermissions && ! onSelectURL ) {
instructions = __( 'To edit this block, you need permission to upload media.' );
}
- if ( ! instructions || ! title ) {
+ if ( instructions === undefined || title === undefined ) {
const isOneType = 1 === allowedTypes.length;
const isAudio = isOneType && 'audio' === allowedTypes[ 0 ];
const isImage = isOneType && 'image' === allowedTypes[ 0 ];
const isVideo = isOneType && 'video' === allowedTypes[ 0 ];
- if ( ! instructions ) {
+ if ( instructions === undefined ) {
if ( hasUploadPermissions ) {
instructions = __( 'Drag a media file, upload a new one or select a file from your library.' );
@@ -180,7 +197,7 @@ export class MediaPlaceholder extends Component {
}
}
- if ( ! title ) {
+ if ( title === undefined ) {
title = __( 'Media' );
if ( isAudio ) {
@@ -193,70 +210,191 @@ export class MediaPlaceholder extends Component {
}
}
+ const placeholderClassName = classnames(
+ 'block-editor-media-placeholder',
+ 'editor-media-placeholder',
+ className,
+ { 'is-appender': isAppender }
+ );
+
return (
-
- { !! mediaUpload && (
-
-
-
- { __( 'Upload' ) }
-
-
- ) }
- (
-
- ) }
+ { content }
+
+ );
+ }
+
+ renderDropZone() {
+ const { onHTMLDrop = noop } = this.props;
+ return (
+
+ );
+ }
+
+ renderUrlSelectionUI() {
+ const {
+ onSelectURL,
+ } = this.props;
+ if ( ! onSelectURL ) {
+ return null;
+ }
+ const {
+ isURLInputVisible,
+ src,
+ } = this.state;
+ return (
+
+
+ { isURLInputVisible && (
+
-
- { onSelectURL && (
-
+ ) }
+
+ );
+ }
+
+ renderMediaUploadChecked() {
+ const {
+ accept,
+ addToGallery,
+ allowedTypes = [],
+ isAppender,
+ mediaUpload,
+ multiple = false,
+ onSelect,
+ value = {},
+ } = this.props;
+
+ const mediaLibraryButton = (
+
id ) :
+ value.id
+ }
+ render={ ( { open } ) => {
+ return (
- { isURLInputVisible && (
-
+ );
+ } }
+ />
+ );
+
+ if ( mediaUpload && isAppender ) {
+ return (
+
+ { this.renderDropZone() }
+ {
+ const content = (
+
+
+ { __( 'Upload' ) }
+
+ { mediaLibraryButton }
+ { this.renderUrlSelectionUI() }
+
+ );
+ return this.renderPlaceholder( content, openFileDialog );
+ } }
+ />
+
+ );
+ }
+ if ( mediaUpload ) {
+ const content = (
+
+ { this.renderDropZone() }
+
- ) }
-
+ onChange={ this.onUpload }
+ accept={ accept }
+ multiple={ multiple }
+ >
+ { __( 'Upload' ) }
+
+ { mediaLibraryButton }
+ { this.renderUrlSelectionUI() }
+
+ );
+ return this.renderPlaceholder( content );
+ }
+ return this.renderPlaceholder( mediaLibraryButton );
+ }
+
+ render() {
+ const {
+ dropZoneUIOnly,
+ } = this.props;
+
+ if ( dropZoneUIOnly ) {
+ return (
+
+ { this.renderDropZone() }
+
+ );
+ }
+
+ return (
+
+ { this.renderMediaUploadChecked() }
+
);
}
}
diff --git a/packages/block-editor/src/components/media-placeholder/style.scss b/packages/block-editor/src/components/media-placeholder/style.scss
index 7cc50c523885af..d18b42ce5e57a9 100644
--- a/packages/block-editor/src/components/media-placeholder/style.scss
+++ b/packages/block-editor/src/components/media-placeholder/style.scss
@@ -45,3 +45,30 @@
.components-form-file-upload .block-editor-media-placeholder__button {
margin-right: $grid-size-small;
}
+
+.block-editor-media-placeholder.is-appender {
+ min-height: 100px;
+ outline: $border-width dashed $dark-gray-150;
+
+ &:hover {
+ outline: $border-width dashed $dark-gray-500;
+ cursor: pointer;
+ }
+
+ .is-dark-theme & {
+
+ &:hover {
+ outline: $border-width dashed $white;
+ }
+ }
+
+ .block-editor-media-placeholder__upload-button {
+ margin-right: $grid-size-small;
+ &.components-button:hover,
+ &.components-button:focus {
+ box-shadow: none;
+ border: $border-width solid $dark-gray-500;
+ }
+ }
+
+}
diff --git a/packages/block-editor/src/components/media-upload/README.md b/packages/block-editor/src/components/media-upload/README.md
index eb2f75cbdd8af2..6e0a67d9f187c6 100644
--- a/packages/block-editor/src/components/media-upload/README.md
+++ b/packages/block-editor/src/components/media-upload/README.md
@@ -101,6 +101,25 @@ CSS class added to the media modal frame.
- Type: `String`
- Required: No
+
+### addToGallery
+
+If true, the gallery media modal opens directly in the media library where the user can add additional images.
+If false the gallery media modal opens in the edit mode where the user can edit existing images, by reordering them, remove them, or change their attributes.
+Only applies if `gallery === true`.
+
+- Type: `Boolean`
+- Required: No
+- Default: `false`
+
+### gallery
+
+If true, the component will initiate all the states required to represent a gallery. By default, the media modal opens in the gallery edit frame, but that can be changed using the `addToGallery`flag.
+
+- Type: `Boolean`
+- Required: No
+- Default: `false`
+
## render
A callback invoked to render the Button opening the media library.
diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js
index ade7ba978264ba..a42e86fa94037c 100644
--- a/packages/block-library/src/gallery/edit.js
+++ b/packages/block-library/src/gallery/edit.js
@@ -8,8 +8,6 @@ import { filter, pick, map, get } from 'lodash';
* WordPress dependencies
*/
import {
- DropZone,
- FormFileUpload,
IconButton,
PanelBody,
RangeControl,
@@ -25,7 +23,6 @@ import {
MediaUpload,
InspectorControls,
} from '@wordpress/block-editor';
-import { mediaUpload } from '@wordpress/editor';
import { Component, Fragment } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
@@ -64,8 +61,6 @@ class GalleryEdit extends Component {
this.toggleImageCrop = this.toggleImageCrop.bind( this );
this.onRemoveImage = this.onRemoveImage.bind( this );
this.setImageAttributes = this.setImageAttributes.bind( this );
- this.addFiles = this.addFiles.bind( this );
- this.uploadFromFiles = this.uploadFromFiles.bind( this );
this.setAttributes = this.setAttributes.bind( this );
this.state = {
@@ -152,27 +147,6 @@ class GalleryEdit extends Component {
} );
}
- uploadFromFiles( event ) {
- this.addFiles( event.target.files );
- }
-
- addFiles( files ) {
- const currentImages = this.props.attributes.images || [];
- const { noticeOperations } = this.props;
- const { setAttributes } = this;
- mediaUpload( {
- allowedTypes: ALLOWED_MEDIA_TYPES,
- filesList: files,
- onFileChange: ( images ) => {
- const imagesNormalized = images.map( ( image ) => pickRelevantMediaFiles( image ) );
- setAttributes( {
- images: currentImages.concat( imagesNormalized ),
- } );
- },
- onError: noticeOperations.createErrorNotice,
- } );
- }
-
componentDidUpdate( prevProps ) {
// Deselect images when deselecting the block
if ( ! this.props.isSelected && prevProps.isSelected ) {
@@ -187,15 +161,11 @@ class GalleryEdit extends Component {
const { attributes, isSelected, className, noticeOperations, noticeUI } = this.props;
const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes;
- const dropZone = (
-
- );
+ const hasImages = !! images.length;
const controls = (
- { !! images.length && (
+ { hasImages && (
);
- if ( images.length === 0 ) {
+ const mediaPlaceholder = (
+ }
+ labels={ {
+ title: ! hasImages && __( 'Gallery' ),
+ instructions: ! hasImages && __( 'Drag images, upload new ones or select files from your library.' ),
+ } }
+ onSelect={ this.onSelectImages }
+ accept="image/*"
+ allowedTypes={ ALLOWED_MEDIA_TYPES }
+ multiple
+ value={ hasImages ? images : undefined }
+ onError={ noticeOperations.createErrorNotice }
+ notices={ hasImages ? undefined : noticeUI }
+ />
+ );
+
+ if ( ! hasImages ) {
return (
{ controls }
- }
- className={ className }
- labels={ {
- title: __( 'Gallery' ),
- instructions: __( 'Drag images, upload new ones or select files from your library.' ),
- } }
- onSelect={ this.onSelectImages }
- accept="image/*"
- allowedTypes={ ALLOWED_MEDIA_TYPES }
- multiple
- notices={ noticeUI }
- onError={ noticeOperations.createErrorNotice }
- />
+ { mediaPlaceholder }
);
}
@@ -277,7 +255,6 @@ class GalleryEdit extends Component {
}
) }
>
- { dropZone }
{ images.map( ( img, index ) => {
/* translators: %1$d is the order number of the image, %2$d is the total number of images. */
const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length );
@@ -298,21 +275,8 @@ class GalleryEdit extends Component {
);
} ) }
- { isSelected &&
-
-
- { __( 'Upload an image' ) }
-
-
- }
+ { mediaPlaceholder }
);
}
diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss
index 34b08797e2e558..4136018059e499 100644
--- a/packages/block-library/src/gallery/editor.scss
+++ b/packages/block-library/src/gallery/editor.scss
@@ -53,31 +53,6 @@ ul.wp-block-gallery li {
}
}
- .components-form-file-upload,
- .components-button.block-library-gallery-add-item-button {
- width: 100%;
- height: 100%;
- }
-
- .components-button.block-library-gallery-add-item-button {
- display: flex;
- flex-direction: column;
- justify-content: center;
- box-shadow: none;
- border: none;
- border-radius: 0;
- min-height: 100px;
-
- & .dashicon {
- margin-top: 10px;
- }
-
- &:hover,
- &:focus {
- border: $border-width solid $dark-gray-500;
- }
- }
-
.block-editor-rich-text figcaption {
a {
color: $white;
@@ -118,14 +93,3 @@ ul.wp-block-gallery li {
margin-top: -9px;
margin-left: -9px;
}
-
-// Last item always needs margins reset.
-// When block is selected, only reset the right margin of the 2nd to last item.
-.wp-block-gallery {
- .is-selected & .blocks-gallery-image:nth-last-child(2),
- .is-selected & .blocks-gallery-item:nth-last-child(2),
- .is-typing & .blocks-gallery-image:nth-last-child(2),
- .is-typing & .blocks-gallery-item:nth-last-child(2) {
- margin-right: 0;
- }
-}
diff --git a/packages/block-library/src/gallery/style.scss b/packages/block-library/src/gallery/style.scss
index 2557f727c5c0e9..df60d6fd1fc9b6 100644
--- a/packages/block-library/src/gallery/style.scss
+++ b/packages/block-library/src/gallery/style.scss
@@ -126,14 +126,6 @@
margin-right: 0;
}
- // Make the "Add new Gallery item" button full-width (so it always appears
- // below other items).
- .blocks-gallery-item {
- &.has-add-item-button {
- width: 100%;
- }
- }
-
// Apply max-width to floated items that have no intrinsic width.
&.alignleft,
&.alignright {
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 2035d92dd9be94..c76f5a75ac8d7d 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -1,5 +1,9 @@
## 7.2.1 (Unreleased)
+### New Features
+
+- Added a new `render` property to `FormFileUpload` component. Allowing users of the component to custom the UI for their needs.
+
### Bug fixes
- Fix `instanceId` prop passed through to `Button` component via `MenuItems` producing React console error. Fixed by removing the unnecessary use of `withInstanceId` on the `MenuItems` component [#14599](https://github.com/WordPress/gutenberg/pull/14599)
diff --git a/packages/components/src/form-file-upload/index.js b/packages/components/src/form-file-upload/index.js
index 461f83c263888c..7a45a075ddfd8c 100644
--- a/packages/components/src/form-file-upload/index.js
+++ b/packages/components/src/form-file-upload/index.js
@@ -24,10 +24,10 @@ class FormFileUpload extends Component {
}
render() {
- const { children, multiple = false, accept, onChange, icon = 'upload', ...props } = this.props;
+ const { children, multiple = false, accept, onChange, icon = 'upload', render, ...props } = this.props;
- return (
-
+ const ui = render ?
+ render( { openFileDialog: this.openFileDialog } ) : (
{ children }
+ );
+ return (
+
+ { ui }
{
class MediaUpload extends Component {
constructor( {
allowedTypes,
- multiple = false,
gallery = false,
- title = __( 'Select or Upload Media' ),
modalClass,
+ multiple = false,
+ title = __( 'Select or Upload Media' ),
} ) {
super( ...arguments );
this.openModal = this.openModal.bind( this );
@@ -124,6 +124,7 @@ class MediaUpload extends Component {
buildAndSetGalleryFrame() {
const {
+ addToGallery = false,
allowedTypes,
multiple = false,
value = null,
@@ -140,7 +141,12 @@ class MediaUpload extends Component {
if ( this.frame ) {
this.frame.remove();
}
- const currentState = value ? 'gallery-edit' : 'gallery';
+ let currentState;
+ if ( addToGallery ) {
+ currentState = 'gallery-library';
+ } else {
+ currentState = value ? 'gallery-edit' : 'gallery';
+ }
if ( ! this.GalleryDetailsMediaFrame ) {
this.GalleryDetailsMediaFrame = getGalleryDetailsMediaFrame();
}