Skip to content

Commit

Permalink
Update Tiled Gallery block
Browse files Browse the repository at this point in the history
  • Loading branch information
simison committed Oct 12, 2018
1 parent 0b8a8ae commit 348851c
Show file tree
Hide file tree
Showing 12 changed files with 988 additions and 459 deletions.
29 changes: 29 additions & 0 deletions client/gutenberg/extensions/tiled-gallery/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/** @format */

/**
* External Dependencies
*/
import { _x } from '@wordpress/i18n';

export const DEFAULT_COLUMNS = 3;
export const MAX_COLUMNS = 20;
export const TILE_MARGIN = 2;
export const LAYOUTS = [
{
label: _x( 'Tiled mosaic', 'Tiled gallery layout', 'jetpack' ),
name: 'rectangular',
isDefault: true,
},
{
label: _x( 'Tiled columns', 'Tiled gallery layout', 'jetpack' ),
name: 'columns',
},
{
label: _x( 'Square tiles', 'Tiled gallery layout', 'jetpack' ),
name: 'square',
},
{
label: _x( 'Circles', 'Tiled gallery layout', 'jetpack' ),
name: 'circle',
},
];
263 changes: 195 additions & 68 deletions client/gutenberg/extensions/tiled-gallery/edit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,96 @@
/**
* External Dependencies
*/
import classnames from 'classnames';
import filter from 'lodash/filter';
import pick from 'lodash/pick';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import find from 'lodash/find';
import { Component, Fragment } from '@wordpress/element';
import {
BlockControls,
InspectorControls,
MediaPlaceholder,
MediaUpload,
mediaUpload,
} from '@wordpress/editor';
import { __ } from '@wordpress/i18n';
import {
DropZone,
FormFileUpload,
IconButton,
PanelBody,
RangeControl,
SelectControl,
ToggleControl,
Toolbar,
withNotices,
} from '@wordpress/components';
import {
BlockControls,
InspectorControls,
MediaPlaceholder,
mediaUpload,
MediaUpload,
} from '@wordpress/editor';
// @TODO:
// Adding `@wordpress/token-list` to dependencies conflicts with current 3.0.0 version of `@wordpress/editor`
// This will still work for Jetpack, but will fail when importing the block in Calypso
// eslint-disable-next-line import/no-extraneous-dependencies
import TokenList from '@wordpress/token-list';

/**
* Internal dependencies
*/
import TiledGallerySave from './save.jsx';
import { LAYOUTS, MAX_COLUMNS, DEFAULT_COLUMNS } from './constants';
import GalleryImage from './gallery-image';
import LayoutStyles from './layout-styles';

export function defaultColumnsNumber( attributes ) {
return Math.min( DEFAULT_COLUMNS, attributes.images.length );
}

/**
* Module variables
* Returns the active style from the given className.
*
* @param {Array} styles Block style variations.
* @param {string} className Class name
*
* @return {Object?} The active style.
*
* From https://github.com/WordPress/gutenberg/blob/077f6c4eb9ba061bc00d5f3ae956d4789a291fb5/packages/editor/src/components/block-styles/index.js#L21-L43
*/
const MAX_COLUMNS = 8;
const linkOptions = [
{ value: 'attachment', label: __( 'Attachment Page', 'jetpack' ) },
{ value: 'media', label: __( 'Media File', 'jetpack' ) },
{ value: 'none', label: __( 'None', 'jetpack' ) },
];
function getActiveStyle( styles, className ) {
for ( const style of new TokenList( className ).values() ) {
if ( style.indexOf( 'is-style-' ) === -1 ) {
continue;
}

const potentialStyleName = style.substring( 9 );
const activeStyle = find( styles, { name: potentialStyleName } );
if ( activeStyle ) {
return activeStyle;
}
}

return find( styles, 'isDefault' );
}

const getActiveStyleName = className => {
const activeStyle = getActiveStyle( LAYOUTS, className );
return activeStyle.name;
};

class TiledGalleryEdit extends Component {
constructor() {
super( ...arguments );

this.onSelectImage = this.onSelectImage.bind( this );
this.onSelectImages = this.onSelectImages.bind( this );
this.setLayout = this.setLayout.bind( this );
this.setLinkTo = this.setLinkTo.bind( this );
this.setColumnsNumber = this.setColumnsNumber.bind( this );
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.state = {
selectedImage: null,
layout: getActiveStyleName( arguments[ 0 ].className ),
};
}

Expand All @@ -68,9 +106,27 @@ class TiledGalleryEdit extends Component {
};
}

onRemoveImage( index ) {
return () => {
const images = filter( this.props.attributes.images, ( img, i ) => index !== i );
const { columns } = this.props.attributes;
this.setState( { selectedImage: null } );
this.props.setAttributes( {
images,
columns: columns ? Math.min( images.length, columns ) : columns,
} );
};
}

onSelectImages( images ) {
this.props.setAttributes( {
images: images.map( image => pick( image, [ 'alt', 'caption', 'id', 'url', 'link' ] ) ),
images: images.map( image => pick( image, [ 'alt', 'caption', 'id', 'link', 'url' ] ) ),
} );
}

setLayout( value ) {
this.setState( {
layout: value,
} );
}

Expand All @@ -82,6 +138,16 @@ class TiledGalleryEdit extends Component {
this.props.setAttributes( { columns: value } );
}

toggleImageCrop() {
this.props.setAttributes( { imageCrop: ! this.props.attributes.imageCrop } );
}

getImageCropHelp( checked ) {
return checked
? __( 'Thumbnails are cropped to align.', 'jetpack' )
: __( 'Thumbnails are not cropped.', 'jetpack' );
}

setImageAttributes( index, attributes ) {
const {
attributes: { images },
Expand All @@ -102,6 +168,10 @@ class TiledGalleryEdit extends Component {
} );
}

uploadFromFiles( event ) {
this.addFiles( event.target.files );
}

addFiles( files ) {
const currentImages = this.props.attributes.images || [];
const { noticeOperations, setAttributes } = this.props;
Expand All @@ -120,23 +190,38 @@ class TiledGalleryEdit extends Component {
componentDidUpdate( prevProps ) {
// Deselect images when deselecting the block
if ( ! this.props.isSelected && prevProps.isSelected ) {
// @TODO refactor
// eslint-disable-next-line react/no-did-update-set-state
//eslint-disable-next-line
this.setState( {
selectedImage: null,
captionSelected: false,
} );
}

if ( this.props.className !== prevProps.className ) {
const activeStyleName = getActiveStyleName( this.props.className );

if ( activeStyleName !== this.state.layout ) {
this.setLayout( activeStyleName );
}
}
}

render() {
const { attributes, className, isSelected, noticeOperations, noticeUI } = this.props;
const { images, columns, linkTo } = attributes;
const { attributes, isSelected, className, noticeOperations, noticeUI } = this.props;
const {
images,
columns = defaultColumnsNumber( attributes ),
align,
imageCrop,
linkTo,
} = attributes;

const layoutsSupportingColumns = [ 'square', 'circle' ];

const dropZone = <DropZone key="item-dropzone" onFilesDrop={ this.addFiles } />;
const dropZone = <DropZone onFilesDrop={ this.addFiles } />;

const controls = isSelected && (
<BlockControls key="controls">
const controls = (
<BlockControls>
{ !! images.length && (
<Toolbar>
<MediaUpload
Expand All @@ -163,65 +248,107 @@ class TiledGalleryEdit extends Component {
return (
<Fragment>
{ controls }
{ noticeUI }
<MediaPlaceholder
key="gallery-placeholder"
className={ className }
icon="format-gallery"
className={ className }
labels={ {
title: __( 'Tiled Gallery', 'jetpack' ),
title: __( 'Tiled gallery', 'jetpack' ),
name: __( 'images', 'jetpack' ),
} }
onSelect={ this.onSelectImages }
notices={ noticeUI }
onError={ noticeOperations.createErrorNotice }
accept="image/*"
type="image"
multiple
notices={ noticeUI }
onError={ noticeOperations.createErrorNotice }
/>
</Fragment>
);
}

// To avoid users accidentally navigating out of Gutenberg by clicking an image, we disable linkTo in the editor view here by forcing 'none'.
const imageTiles = (
<TiledGallerySave
attributes={ {
className,
images,
columns,
linkTo: 'none',
} }
/>
);

return (
<Fragment>
{ controls }
{ isSelected && (
<InspectorControls key="inspector">
<PanelBody title={ __( 'Tiled Gallery Settings', 'jetpack' ) }>
{ images.length > 1 && (
<RangeControl
label={ __( 'Columns', 'jetpack' ) }
value={ columns }
onChange={ this.setColumnsNumber }
min={ 1 }
max={ Math.min( MAX_COLUMNS, images.length ) }
/>
) }
<SelectControl
label={ __( 'Link to', 'jetpack' ) }
value={ linkTo }
onChange={ this.setLinkTo }
options={ linkOptions }
<InspectorControls>
<PanelBody title={ __( 'Tiled gallery Settings', 'jetpack' ) }>
{ images.length > 1 && (
<RangeControl
label={ __( 'Columns', 'jetpack' ) }
value={ columns }
onChange={ this.setColumnsNumber }
min={ 1 }
disabled={ layoutsSupportingColumns.indexOf( this.state.layout ) === -1 }
max={ Math.min( MAX_COLUMNS, images.length ) }
/>
</PanelBody>
</InspectorControls>
) }
{ dropZone }
) }
<ToggleControl
label={ __( 'Crop Images', 'jetpack' ) }
checked={ !! imageCrop }
onChange={ this.toggleImageCrop }
help={ this.getImageCropHelp }
/>
<SelectControl
label={ __( 'Link To', 'jetpack' ) }
value={ linkTo }
onChange={ this.setLinkTo }
options={ [
{ value: 'attachment', label: __( 'Attachment Page', 'jetpack' ) },
{ value: 'media', label: __( 'Media File', 'jetpack' ) },
{ value: 'none', label: __( 'None', 'jetpack' ) },
] }
/>
</PanelBody>
</InspectorControls>
{ noticeUI }
{ imageTiles }
<LayoutStyles
layout={ this.state.layout }
columns={ columns }
images={ images }
className={ className }
/>
{ dropZone }
<ul
className={ classnames( className, {
'is-cropped': imageCrop,
[ `align${ align }` ]: align,
[ `columns-${ columns }` ]: columns,
} ) }
>
{ images.map( ( img, index ) => {
return (
<li
className={ `tiled-gallery__item tiled-gallery__item-${ index }` }
key={ img.id || img.url }
>
<GalleryImage
url={ img.url }
alt={ img.alt }
id={ img.id }
isSelected={ isSelected && this.state.selectedImage === index }
onRemove={ this.onRemoveImage( index ) }
onSelect={ this.onSelectImage( index ) }
setAttributes={ attrs => this.setImageAttributes( index, attrs ) }
caption={ this.state.layout !== 'circle' ? img.caption : '' }
captionEnabled={ this.state.layout !== 'circle' }
/>
</li>
);
} ) }
{ isSelected && (
<li className={ `tiled-gallery__item has-add-item-button` }>
<FormFileUpload
multiple
isLarge
className="block-library-gallery-add-item-button"
onChange={ this.uploadFromFiles }
accept="image/*"
icon="insert"
>
{ __( 'Upload an image', 'jetpack' ) }
</FormFileUpload>
</li>
) }
</ul>
</Fragment>
);
}
Expand Down
Loading

0 comments on commit 348851c

Please sign in to comment.