Skip to content

Commit

Permalink
Tiled Gallery: Fix block validation errors caused by inconsistent fle…
Browse files Browse the repository at this point in the history
…x-basis style (#18971)

Fix tiled gallery rounding of column width and flex-basis style

Inconsistent flex-basis style values are causing block validation errors in the tiled gallery. This fixes that issue and adds a deprecation to correct any blocks created between when the problem commit was merged and this one.
  • Loading branch information
aaronrobertshaw committed Mar 12, 2021
1 parent b659820 commit 488952f
Show file tree
Hide file tree
Showing 17 changed files with 896 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: bugfix

Tiled Gallery: Prevent block validation errors for mosaic and column layouts.
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
*/
import * as deprecatedV1 from './v1';
import * as deprecatedV2 from './v2';
import * as deprecatedV3 from './v3';

export default [ deprecatedV1, deprecatedV2 ];
export default [ deprecatedV3, deprecatedV2, deprecatedV1 ];
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const ALLOWED_MEDIA_TYPES = [ 'image' ];
export const DEFAULT_GALLERY_WIDTH = 580;
export const GUTTER_WIDTH = 4;
export const MAX_COLUMNS = 20;
export const MAX_ROUNDED_CORNERS = 20;
export const PHOTON_MAX_RESIZE = 2000;

/**
* Layouts
*/
export const LAYOUT_CIRCLE = 'circle';
export const LAYOUT_COLUMN = 'columns';
export const LAYOUT_DEFAULT = 'rectangular';
export const LAYOUT_SQUARE = 'square';
export const LAYOUT_STYLES = [
{
isDefault: true,
name: LAYOUT_DEFAULT,
},
{
name: LAYOUT_CIRCLE,
},
{
name: LAYOUT_SQUARE,
},
{
name: LAYOUT_COLUMN,
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* External Dependencies
*/
import classnames from 'classnames';
import { isBlobURL } from '@wordpress/blob';

export default function GalleryImageSave( props ) {
const { alt, imageFilter, height, id, link, linkTo, origUrl, url, width } = props;

if ( isBlobURL( origUrl ) ) {
return null;
}

let href;

switch ( linkTo ) {
case 'media':
href = url;
break;
case 'attachment':
href = link;
break;
}

const img = (
<img
alt={ alt }
data-height={ height }
data-id={ id }
data-link={ link }
data-url={ origUrl }
data-width={ width }
src={ url }
/>
);

return (
<figure
className={ classnames( 'tiled-gallery__item', {
[ `filter__${ imageFilter }` ]: !! imageFilter,
} ) }
>
{ href ? <a href={ href }>{ img }</a> : img }
</figure>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Internal dependencies
*/
import { LAYOUT_DEFAULT } from './constants';

export { default as save } from './save';

export const attributes = {
// Set default align
align: {
default: 'center',
type: 'string',
},
// Set default className (used with block styles)
className: {
default: `is-style-${ LAYOUT_DEFAULT }`,
type: 'string',
},
columns: {
type: 'number',
},
columnWidths: {
default: [],
type: 'array',
},
ids: {
default: [],
type: 'array',
},
imageFilter: {
type: 'string',
},
images: {
type: 'array',
default: [],
source: 'query',
selector: '.tiled-gallery__item',
query: {
alt: {
attribute: 'alt',
default: '',
selector: 'img',
source: 'attribute',
},
height: {
attribute: 'data-height',
selector: 'img',
source: 'attribute',
type: 'number',
},
id: {
attribute: 'data-id',
selector: 'img',
source: 'attribute',
},
link: {
attribute: 'data-link',
selector: 'img',
source: 'attribute',
},
url: {
attribute: 'data-url',
selector: 'img',
source: 'attribute',
},
width: {
attribute: 'data-width',
selector: 'img',
source: 'attribute',
type: 'number',
},
},
},
linkTo: {
default: 'none',
type: 'string',
},
roundedCorners: {
type: 'integer',
default: 0,
},
};

export const supports = {
align: [ 'center', 'wide', 'full' ],
customClassName: false,
html: false,
};

export const migrate = oldAttributes => {
// The column widths need to be updated to match the new precision
// implemented in the current version.
const precision = Math.pow( 10, 5 );

return {
...oldAttributes,
columnWidths: oldAttributes.columnWidths.map( column =>
column.map( width => Math.round( width * precision ) / precision )
),
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default function Column( { children, width } ) {
// This deprecation fixes inconsistent precision of flex-basis style decimal.
// It needs to be adjusted here so that the style value matches the post
// content to then trigger re-saving the block.
const precision = Math.pow( 10, 12 ); // 1000000000000.
const roundedWidth = Math.round( width * precision ) / precision;
const style = width ? { flexBasis: `${ roundedWidth }%` } : undefined;

return (
<div className="tiled-gallery__col" style={ style }>
{ children }
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Gallery( { children, galleryRef } ) {
return (
<div className="tiled-gallery__gallery" ref={ galleryRef }>
{ children }
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import classnames from 'classnames';

/**
* Internal dependencies
*/
import GalleryImageSave from '../gallery-image/save';
import Mosaic from './mosaic';
import Square from './square';
import { isSquareishLayout, photonizedImgProps } from '../utils';
import { LAYOUT_CIRCLE, MAX_ROUNDED_CORNERS } from '../constants';

export default class Layout extends Component {
// This is tricky:
// - We need to "photonize" to resize the images at appropriate dimensions
// - The resize will depend on the image size and the layout in some cases
// - Handlers need to be created by index so that the image changes can be applied correctly.
// This is because the images are stored in an array in the block attributes.
renderImage( img, i ) {
const {
columns,
imageFilter,
images,
isSave,
linkTo,
layoutStyle,
onMoveBackward,
onMoveForward,
onRemoveImage,
onSelectImage,
selectedImage,
setImageAttributes,
} = this.props;

const ariaLabel = sprintf(
/* translators: %1$d is the order number of the image, %2$d is the total number of images. */
__( 'image %1$d of %2$d in gallery', 'jetpack' ),
i + 1,
images.length
);

const { src, srcSet } = photonizedImgProps( img, { layoutStyle } );

return (
<GalleryImageSave
alt={ img.alt }
aria-label={ ariaLabel }
columns={ columns }
height={ img.height }
id={ img.id }
imageFilter={ imageFilter }
isFirstItem={ i === 0 }
isLastItem={ i + 1 === images.length }
isSelected={ selectedImage === i }
key={ i }
link={ img.link }
linkTo={ linkTo }
onMoveBackward={ isSave ? undefined : onMoveBackward( i ) }
onMoveForward={ isSave ? undefined : onMoveForward( i ) }
onRemove={ isSave ? undefined : onRemoveImage( i ) }
onSelect={ isSave ? undefined : onSelectImage( i ) }
origUrl={ img.url }
setAttributes={ isSave ? undefined : setImageAttributes( i ) }
showMovers={ images.length > 1 }
srcSet={ srcSet }
url={ src }
width={ img.width }
/>
);
}

render() {
const {
align,
children,
className,
columns,
images,
layoutStyle,
roundedCorners,
onResize,
isSave,
columnWidths,
} = this.props;
const LayoutRenderer = isSquareishLayout( layoutStyle ) ? Square : Mosaic;
const renderedImages = this.props.images.map( this.renderImage, this );
const roundedCornersValue =
layoutStyle !== LAYOUT_CIRCLE ? Math.min( roundedCorners, MAX_ROUNDED_CORNERS ) : 0;

return (
<div
className={ classnames( className, {
[ `has-rounded-corners-${ roundedCornersValue }` ]: roundedCornersValue > 0,
} ) }
>
<LayoutRenderer
align={ align }
columns={ columns }
columnWidths={ isSave ? columnWidths : undefined }
images={ images }
layoutStyle={ layoutStyle }
renderedImages={ renderedImages }
onResize={ isSave ? undefined : onResize }
/>
{ children }
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* External dependencies
*/
import { Component, createRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import Column from '../column';
import Gallery from '../gallery';
import Row from '../row';
import { imagesToRatios, ratiosToColumns, ratiosToMosaicRows } from './ratios';

export default class Mosaic extends Component {
gallery = createRef();
pendingRaf = null;

render() {
const { align, columns, images, layoutStyle, renderedImages, columnWidths } = this.props;

const ratios = imagesToRatios( images );
const rows =
'columns' === layoutStyle
? ratiosToColumns( ratios, columns )
: ratiosToMosaicRows( ratios, { isWide: [ 'full', 'wide' ].includes( align ) } );

let cursor = 0;
return (
<Gallery galleryRef={ this.gallery }>
{ rows.map( ( row, rowIndex ) => (
<Row key={ rowIndex }>
{ row.map( ( colSize, colIndex ) => {
const columnImages = renderedImages.slice( cursor, cursor + colSize );
cursor += colSize;
return (
<Column
key={ colIndex }
width={ columnWidths ? columnWidths[ rowIndex ][ colIndex ] : undefined }
>
{ columnImages }
</Column>
);
} ) }
</Row>
) ) }
</Gallery>
);
}
}
Loading

0 comments on commit 488952f

Please sign in to comment.