From 3c1fe582be7a8a933eff784c51386a51ac89c161 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 25 Mar 2019 16:35:38 +0000 Subject: [PATCH] Gallery Block: Add Media Library button to the upload new image area (#12367) Fixes: https://github.com/WordPress/gutenberg/issues/8309 Supersedes: https://github.com/WordPress/gutenberg/pull/9682 This PR adds a media library button in the upload new image are of the gallery block. Trying to follow the design 2 proposed in https://github.com/WordPress/gutenberg/pull/9682#issuecomment-425435709 by @kjellr and approved by @karmatosed. A new functionality that allows the gallery to open in library/add frame instead of the edit frame was added to MediaUpload, so when the user pressed the media library button in the add zone the user goes directly to the add to library section when using the edit gallery button on the toolbar the user goes to the edit gallery section. ## Description I added a gallery; I added some images; I selected the gallery; I checked that a new media library button exists in the add to gallery zone. If I click on it I go directly to a frame that allows the addition of images from the library to the gallery. ## How has this been tested? ## Screenshot screenshot 2018-11-27 at 15 35 14 --- packages/block-editor/CHANGELOG.md | 7 + .../src/components/media-placeholder/index.js | 288 +++++++++++++----- .../components/media-placeholder/style.scss | 27 ++ .../src/components/media-upload/README.md | 19 ++ packages/block-library/src/gallery/edit.js | 88 ++---- .../block-library/src/gallery/editor.scss | 36 --- packages/block-library/src/gallery/style.scss | 8 - packages/components/CHANGELOG.md | 4 + .../components/src/form-file-upload/index.js | 10 +- packages/edit-post/CHANGELOG.md | 9 +- .../hooks/components/media-upload/index.js | 12 +- 11 files changed, 320 insertions(+), 188 deletions(-) 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(); }