Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Gallery component for semi-cross-platform Gallery block #18265

Merged
merged 36 commits into from
Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
711f64e
Extract gallery.js
pinarol Oct 10, 2019
22f558f
Refactor gallery to accept props more directly
mkevins Oct 16, 2019
c1de094
Use viewport HOC in gallery edit
mkevins Oct 25, 2019
1bded51
Use cross-platform Platform module in gallery edit
mkevins Oct 25, 2019
f8d06dd
WIP - gallery edit - use icon prop with RangeControl
mkevins Oct 29, 2019
58945da
Fix lint errors
mkevins Oct 29, 2019
76d8da6
Refactor gallery to accept props more directly
mkevins Nov 6, 2019
ae22614
Add empty upload function for mobile gallery
pinarol Oct 10, 2019
abd295d
Use all caps for gallery placeholder text on mobile
mkevins Nov 12, 2019
a351eaa
Rename gallery instructions to placeholder text
mkevins Nov 12, 2019
2b8c4ab
Extract BlockIcon to ./icon in gallery block
mkevins Nov 12, 2019
e89d33d
Use withPreferredColorScheme HOC for mobile gallery icon
mkevins Nov 12, 2019
55170b9
Fix js lint
mkevins Nov 12, 2019
4c6d65c
Add isNarrow flag below large breakpoint in gallery
mkevins Nov 12, 2019
6702308
Fix shared icon extraction for web
mkevins Nov 13, 2019
a8100e1
Remove RangeControl icon from gallery edit
mkevins Nov 15, 2019
e0cdbcc
WIP - use fullWidth separatorType for gallery controls
mkevins Nov 15, 2019
d43e8ca
Add experimental force blur on unmount prop to RichText wrapper
mkevins Nov 19, 2019
c142269
Revert "Add experimental force blur on unmount prop to RichText wrapper"
mkevins Nov 21, 2019
b7c5dbb
Put upload media options behind __DEV__ flag for multiple=true
mkevins Nov 21, 2019
c7864e7
Fix lint
mkevins Nov 21, 2019
971f725
Limit mediaUpload in blobURL check to web
mkevins Nov 25, 2019
a9f3651
Fix scss imports for jest
mkevins Nov 28, 2019
25cf16d
Temporarily enable travis for this branch
mkevins Nov 28, 2019
39af5ad
Extract placeholderText to static constant at top of gallery edit
mkevins Nov 29, 2019
00bd40c
Extract separatorType to static constant at top of gallery edit
mkevins Nov 29, 2019
33a7df3
Remove separatorType prop from web gallery controls
mkevins Nov 29, 2019
27e110b
Use isNarrow flag in withViewportMatch HOC for gallery edit
mkevins Dec 3, 2019
c6d5026
Enable mobile gallery behind __DEV__ flag
mkevins Dec 5, 2019
6f7d35f
Skip blob url handling in gallery when images array is empty
mkevins Dec 5, 2019
870dac3
Remove top-level mobile gallery PR from Travis branches
mkevins Dec 5, 2019
6ac5b18
Fix accessibility on gallery
pinarol Dec 4, 2019
70e9e64
Revert "Skip blob url handling in gallery when images array is empty"
mkevins Dec 6, 2019
9024c49
Enable mobile gallery block in production
mkevins Dec 6, 2019
b48d813
Merge branch 'master' into try/gallery-draft-extract-gallery
mkevins Dec 6, 2019
853f4dd
Avoid adding duplicate images
pinarol Dec 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,15 @@ export class MediaUpload extends React.Component {
}

getMediaOptionsItems() {
const { allowedTypes = [] } = this.props;
const { allowedTypes = [], multiple = false } = this.props;

// disable upload sources for now when multiple flag is set
// eslint-disable-next-line no-undef
if ( ! __DEV__ ) {
if ( allowedTypes.includes( MEDIA_TYPE_IMAGE ) && multiple ) {
return [ siteLibrarySource ];
}
}

return this.getAllSources().filter( ( source ) => {
return allowedTypes.filter( ( allowedType ) => source.types.includes( allowedType ) ).length > 0;
Expand Down
102 changes: 36 additions & 66 deletions packages/block-library/src/gallery/edit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* External dependencies
*/
import classnames from 'classnames';
import {
every,
filter,
Expand All @@ -23,22 +22,21 @@ import {
withNotices,
} from '@wordpress/components';
import {
BlockIcon,
MediaPlaceholder,
InspectorControls,
RichText,
} from '@wordpress/block-editor';
import { Component } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { Component, Platform } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob';
import { withSelect } from '@wordpress/data';
import { withViewportMatch } from '@wordpress/viewport';

/**
* Internal dependencies
*/
import GalleryImage from './gallery-image';
import { icon } from './icons';
import { sharedIcon } from './shared-icon';
import { defaultColumnsNumber, pickRelevantMediaFiles } from './shared';
import Gallery from './gallery';

const MAX_COLUMNS = 8;
const linkOptions = [
Expand All @@ -48,6 +46,18 @@ const linkOptions = [
];
const ALLOWED_MEDIA_TYPES = [ 'image' ];

const PLACEHOLDER_TEXT = Platform.select( {
web: __( 'Drag images, upload new ones or select files from your library.' ),
native: __( 'ADD MEDIA' ),
} );

// currently this is needed for consistent controls UI on mobile
// this can be removed after control components settle on consistent defaults
const MOBILE_CONTROL_PROPS = Platform.select( {
web: {},
native: { separatorType: 'fullWidth' },
} );

class GalleryEdit extends Component {
constructor() {
super( ...arguments );
Expand Down Expand Up @@ -227,7 +237,7 @@ class GalleryEdit extends Component {
componentDidMount() {
const { attributes, mediaUpload } = this.props;
const { images } = attributes;
if ( every( images, ( { url } ) => isBlobURL( url ) ) ) {
if ( images.length && every( images, ( { url } ) => isBlobURL( url ) ) ) {
mkevins marked this conversation as resolved.
Show resolved Hide resolved
const filesList = map( images, ( { url } ) => getBlobByURL( url ) );
forEach( images, ( { url } ) => revokeBlobURL( url ) );
mediaUpload( {
Expand All @@ -254,12 +264,9 @@ class GalleryEdit extends Component {
className,
isSelected,
noticeUI,
setAttributes,
} = this.props;
const {
align,
columns = defaultColumnsNumber( attributes ),
caption,
imageCrop,
images,
linkTo,
Expand All @@ -274,10 +281,10 @@ class GalleryEdit extends Component {
isAppender={ hasImages }
className={ className }
disableMediaButtons={ hasImages && ! isSelected }
icon={ ! hasImages && <BlockIcon icon={ icon } /> }
icon={ ! hasImages && sharedIcon }
labels={ {
title: ! hasImages && __( 'Gallery' ),
instructions: ! hasImages && __( 'Drag images, upload new ones or select files from your library.' ),
instructions: ! hasImages && PLACEHOLDER_TEXT,
} }
onSelect={ this.onSelectImages }
accept="image/*"
Expand All @@ -286,25 +293,20 @@ class GalleryEdit extends Component {
value={ hasImagesWithId ? images : undefined }
onError={ this.onUploadError }
notices={ hasImages ? undefined : noticeUI }
onFocus={ this.props.onFocus }
/>
);

if ( ! hasImages ) {
return mediaPlaceholder;
}

const captionClassNames = classnames(
'blocks-gallery-caption',
{
'screen-reader-text': ! isSelected && RichText.isEmpty( caption ),
}
);
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Gallery Settings' ) }>
{ images.length > 1 && <RangeControl
label={ __( 'Columns' ) }
{ ...MOBILE_CONTROL_PROPS }
value={ columns }
onChange={ this.setColumnsNumber }
min={ 1 }
Expand All @@ -313,65 +315,32 @@ class GalleryEdit extends Component {
/> }
<ToggleControl
label={ __( 'Crop Images' ) }
{ ...MOBILE_CONTROL_PROPS }
checked={ !! imageCrop }
onChange={ this.toggleImageCrop }
help={ this.getImageCropHelp }
/>
<SelectControl
label={ __( 'Link To' ) }
{ ...MOBILE_CONTROL_PROPS }
value={ linkTo }
onChange={ this.setLinkTo }
options={ linkOptions }
/>
</PanelBody>
</InspectorControls>
{ noticeUI }
<figure className={ classnames(
className,
{
[ `align${ align }` ]: align,
[ `columns-${ columns }` ]: columns,
'is-cropped': imageCrop,
}
) }
>
<ul className="blocks-gallery-grid">
{ 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 );

return (
<li className="blocks-gallery-item" key={ img.id || img.url }>
<GalleryImage
url={ img.url }
alt={ img.alt }
id={ img.id }
isFirstItem={ index === 0 }
isLastItem={ ( index + 1 ) === images.length }
isSelected={ isSelected && this.state.selectedImage === index }
onMoveBackward={ this.onMoveBackward( index ) }
onMoveForward={ this.onMoveForward( index ) }
onRemove={ this.onRemoveImage( index ) }
onSelect={ this.onSelectImage( index ) }
setAttributes={ ( attrs ) => this.setImageAttributes( index, attrs ) }
caption={ img.caption }
aria-label={ ariaLabel }
/>
</li>
);
} ) }
</ul>
{ mediaPlaceholder }
<RichText
tagName="figcaption"
className={ captionClassNames }
placeholder={ __( 'Write gallery caption…' ) }
value={ caption }
unstableOnFocus={ this.onFocusGalleryCaption }
onChange={ ( value ) => setAttributes( { caption: value } ) }
inlineToolbar
/>
</figure>
<Gallery
{ ...this.props }
selectedImage={ this.state.selectedImage }
mediaPlaceholder={ mediaPlaceholder }
onMoveBackward={ this.onMoveBackward }
mkevins marked this conversation as resolved.
Show resolved Hide resolved
onMoveForward={ this.onMoveForward }
onRemoveImage={ this.onRemoveImage }
onSelectImage={ this.onSelectImage }
onSetImageAttributes={ this.setImageAttributes }
onFocusGalleryCaption={ this.onFocusGalleryCaption }
/>
</>
);
}
Expand All @@ -383,4 +352,5 @@ export default compose( [
return { mediaUpload };
} ),
withNotices,
withViewportMatch( { isNarrow: '< small' } ),
] )( GalleryEdit );
17 changes: 15 additions & 2 deletions packages/block-library/src/gallery/gallery-image.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import {
requestImageFailedRetryDialog,
requestImageUploadCancelDialog,
} from 'react-native-gutenberg-bridge';
import { isEmpty } from 'lodash';

/**
* WordPress dependencies
*/
import { Component } from '@wordpress/element';
import { Icon } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';
import { RichText, MediaUploadProgress } from '@wordpress/block-editor';
import { isURL } from '@wordpress/url';
import { withPreferredColorScheme } from '@wordpress/compose';
Expand Down Expand Up @@ -258,14 +259,17 @@ class GalleryImage extends Component {
}

render() {
const { id, onRemove, getStylesFromColorScheme } = this.props;
const { id, onRemove, getStylesFromColorScheme, isSelected } = this.props;

const containerStyle = getStylesFromColorScheme( style.galleryImageContainer,
style.galleryImageContainerDark );

return (
<TouchableWithoutFeedback
onPress={ this.onMediaPressed }
accessible={ ! isSelected } // We need only child views to be accessible after the selection
accessibilityLabel={ this.accessibilityLabelImageContainer() } // if we don't set this explicitly it reads system provided accessibilityLabels of all child components and those include pretty technical words which don't make sense
accessibilityRole={ 'imagebutton' } // this makes VoiceOver to read a description of image provided by system on iOS and lets user know this is a button which conveys the message of tappablity
>
<View style={ containerStyle }>
<MediaUploadProgress
Expand All @@ -280,6 +284,15 @@ class GalleryImage extends Component {
</TouchableWithoutFeedback>
);
}

accessibilityLabelImageContainer() {
const { caption, 'aria-label': ariaLabel } = this.props;

return isEmpty( caption ) ? ariaLabel : ( ariaLabel + '. ' + sprintf(
/* translators: accessibility text. %s: image caption. */
__( 'Image caption. %s' ), caption
) );
}
}

export default withPreferredColorScheme( GalleryImage );
101 changes: 101 additions & 0 deletions packages/block-library/src/gallery/gallery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import {
RichText,
} from '@wordpress/block-editor';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import GalleryImage from './gallery-image';
import { defaultColumnsNumber } from './shared';

export const Gallery = ( props ) => {
const {
attributes,
className,
isSelected,
setAttributes,
selectedImage,
mediaPlaceholder,
onMoveBackward,
onMoveForward,
onRemoveImage,
onSelectImage,
onSetImageAttributes,
onFocusGalleryCaption,
} = props;

const {
align,
columns = defaultColumnsNumber( attributes ),
caption,
imageCrop,
images,
} = attributes;

const captionClassNames = classnames(
'blocks-gallery-caption',
{
'screen-reader-text': ! isSelected && RichText.isEmpty( caption ),
}
);

return (
<figure className={ classnames(
className,
{
[ `align${ align }` ]: align,
[ `columns-${ columns }` ]: columns,
'is-cropped': imageCrop,
}
) }
>
<ul className="blocks-gallery-grid">
{ 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 );

return (
<li className="blocks-gallery-item" key={ img.id || img.url }>
<GalleryImage
url={ img.url }
alt={ img.alt }
id={ img.id }
isFirstItem={ index === 0 }
isLastItem={ ( index + 1 ) === images.length }
isSelected={ isSelected && selectedImage === index }
onMoveBackward={ onMoveBackward( index ) }
onMoveForward={ onMoveForward( index ) }
onRemove={ onRemoveImage( index ) }
onSelect={ onSelectImage( index ) }
setAttributes={ ( attrs ) => onSetImageAttributes( index, attrs ) }
caption={ img.caption }
aria-label={ ariaLabel }
/>
</li>
);
} ) }
</ul>
{ mediaPlaceholder }
<RichText
tagName="figcaption"
className={ captionClassNames }
placeholder={ __( 'Write gallery caption…' ) }
value={ caption }
unstableOnFocus={ onFocusGalleryCaption }
onChange={ ( value ) => setAttributes( { caption: value } ) }
inlineToolbar
/>
</figure>
);
};

export default Gallery;
11 changes: 11 additions & 0 deletions packages/block-library/src/gallery/shared-icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* WordPress dependencies
*/
import { BlockIcon } from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import { icon } from './icons.js';

export const sharedIcon = <BlockIcon icon={ icon } />;
Loading