Skip to content

Commit

Permalink
Tiled gallery block: Add srcset to images in the editor (#12061)
Browse files Browse the repository at this point in the history
* Add srcset to images in the editor

* Pull srcsetMinWidth into const
  • Loading branch information
sirreal authored and kraftbj committed May 14, 2019
1 parent d21b307 commit 4f6c119
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 79 deletions.
2 changes: 2 additions & 0 deletions extensions/blocks/tiled-gallery/gallery-image/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class GalleryImageEdit extends Component {
linkTo,
onRemove,
origUrl,
srcSet,
url,
width,
} = this.props;
Expand Down Expand Up @@ -102,6 +103,7 @@ class GalleryImageEdit extends Component {
onKeyDown={ this.onImageKeyDown }
ref={ this.img }
src={ isTransient ? undefined : url }
srcSet={ isTransient ? undefined : srcSet }
tabIndex="0"
style={ isTransient ? { backgroundImage: `url(${ url })` } : undefined }
/>
Expand Down
85 changes: 6 additions & 79 deletions extensions/blocks/tiled-gallery/layout/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
/**
* External dependencies
*/
import photon from 'photon';
import { __, sprintf } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
import { format as formatUrl, parse as parseUrl } from 'url';
import { isBlobURL } from '@wordpress/blob';

/**
* Internal dependencies
Expand All @@ -14,34 +11,9 @@ import GalleryImageEdit from '../gallery-image/edit';
import GalleryImageSave from '../gallery-image/save';
import Mosaic from './mosaic';
import Square from './square';
import { PHOTON_MAX_RESIZE } from '../constants';
import { isSquareishLayout, photonizedImgProps } from '../utils';

export default class Layout extends Component {
photonize( { height, width, url } ) {
if ( ! url ) {
return;
}

// Do not Photonize images that are still uploading or from localhost
if ( isBlobURL( url ) || /^https?:\/\/localhost/.test( url ) ) {
return url;
}

// Drop query args, photon URLs can't handle them
// This should be the "raw" url, we'll add dimensions later
const cleanUrl = url.split( '?', 1 )[ 0 ];

const photonImplementation = isWpcomFilesUrl( url ) ? photonWpcomImage : photon;

const { layoutStyle } = this.props;

if ( isSquareishLayout( layoutStyle ) && width && height ) {
const size = Math.min( PHOTON_MAX_RESIZE, width, height );
return photonImplementation( cleanUrl, { resize: `${ size },${ size }` } );
}
return photonImplementation( cleanUrl );
}

// 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
Expand All @@ -53,6 +25,7 @@ export default class Layout extends Component {
images,
isSave,
linkTo,
layoutStyle,
onRemoveImage,
onSelectImage,
selectedImage,
Expand All @@ -67,6 +40,8 @@ export default class Layout extends Component {
);
const Image = isSave ? GalleryImageSave : GalleryImageEdit;

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

return (
<Image
alt={ img.alt }
Expand All @@ -82,7 +57,8 @@ export default class Layout extends Component {
onSelect={ isSave ? undefined : onSelectImage( i ) }
origUrl={ img.url }
setAttributes={ isSave ? undefined : setImageAttributes( i ) }
url={ this.photonize( img ) }
srcSet={ srcSet }
url={ src }
width={ img.width }
/>
);
Expand All @@ -109,52 +85,3 @@ export default class Layout extends Component {
);
}
}

function isSquareishLayout( layout ) {
return [ 'circle', 'square' ].includes( layout );
}

function isWpcomFilesUrl( url ) {
const { host } = parseUrl( url );
return /\.files\.wordpress\.com$/.test( host );
}

/**
* Apply photon arguments to *.files.wordpress.com images
*
* This function largely duplicates the functionlity of the photon.js lib.
* This is necessary because we want to serve images from *.files.wordpress.com so that private
* WordPress.com sites can use this block which depends on a Photon-like image service.
*
* If we pass all images through Photon servers, some images are unreachable. *.files.wordpress.com
* is already photon-like so we can pass it the same parameters for image resizing.
*
* @param {string} url Image url
* @param {Object} opts Options to pass to photon
*
* @return {string} Url string with options applied
*/
function photonWpcomImage( url, opts = {} ) {
// Adhere to the same options API as the photon.js lib
const photonLibMappings = {
width: 'w',
height: 'h',
letterboxing: 'lb',
removeLetterboxing: 'ulb',
};

// Discard some param parts
const { auth, hash, port, query, search, ...urlParts } = parseUrl( url );

// Build query
// This reduction intentionally mutates the query as it is built internally.
urlParts.query = Object.keys( opts ).reduce(
( q, key ) =>
Object.assign( q, {
[ photonLibMappings.hasOwnProperty( key ) ? photonLibMappings[ key ] : key ]: opts[ key ],
} ),
{}
);

return formatUrl( urlParts );
}
153 changes: 153 additions & 0 deletions extensions/blocks/tiled-gallery/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* External dependencies
*/
import photon from 'photon';
import { format as formatUrl, parse as parseUrl } from 'url';
import { isBlobURL } from '@wordpress/blob';
import { range } from 'lodash';

/**
* Internal dependencies
*/
import { PHOTON_MAX_RESIZE } from '../constants';

export function isSquareishLayout( layout ) {
return [ 'circle', 'square' ].includes( layout );
}

/**
* Build src and srcSet properties which can be used on an <img />
*
* @param {Object} img Image
* @param {number} img.height Image height
* @param {string} img.url Image URL
* @param {number} img.width Image width
*
* @param {Object} galleryAtts Gallery attributes relevant for image optimization.
* @param {string} galleryAtts.layoutStyle Gallery layout. 'rectangular', 'circle', etc.
* @param {number} galleryAtts.columns Gallery columns. Not applicable for all layouts.
*
* @return {Object} Returns an object. If possible, the object will include `src` and `srcSet`
* properties {string} for use on an image.
*/
export function photonizedImgProps( img, galleryAtts = {} ) {
if ( ! img.height || ! img.url || ! img.width ) {
return {};
}

// Do not Photonize images that are still uploading or from localhost
if ( isBlobURL( img.url ) || /^https?:\/\/localhost/.test( img.url ) ) {
return { src: img.url };
}

// Drop query args, photon URLs can't handle them
// This should be the "raw" url, we'll add dimensions later
const url = img.url.split( '?', 1 )[ 0 ];
const { height, width } = img;
const { layoutStyle } = galleryAtts;

const photonImplementation = isWpcomFilesUrl( url ) ? photonWpcomImage : photon;

/**
* Build the `src`
* We don't know what the viewport size will be like. Use full size src.
*/

let src;
if ( isSquareishLayout( layoutStyle ) && width && height ) {
// Layouts with 1:1 width/height ratio should be made square
const size = Math.min( PHOTON_MAX_RESIZE, width, height );
src = photonImplementation( url, {
resize: `${ size },${ size }`,
strip: 'all',
} );
} else {
src = photonImplementation( url, { strip: 'all' } );
}

/**
* Build a sensible `srcSet` that will let the browser get an optimized image based on
* viewport width.
*/

const step = 300;
const srcsetMinWith = 600;

let srcSet;
if ( isSquareishLayout( layoutStyle ) ) {
const minWidth = Math.min( srcsetMinWith, width, height );
const maxWidth = Math.min( PHOTON_MAX_RESIZE, width, height );

srcSet = range( minWidth, maxWidth, step )
.map( srcsetWidth => {
const srcsetSrc = photonImplementation( url, {
resize: `${ srcsetWidth },${ srcsetWidth }`,
strip: 'all',
} );
return srcsetSrc ? `${ srcsetSrc } ${ srcsetWidth }w` : null;
} )
.filter( Boolean )
.join( ',' );
} else {
const minWidth = Math.min( srcsetMinWith, width );
const maxWidth = Math.min( PHOTON_MAX_RESIZE, width );

srcSet = range( minWidth, maxWidth, step )
.map( srcsetWidth => {
const srcsetSrc = photonImplementation( url, {
strip: 'all',
width: srcsetWidth,
} );
return srcsetSrc ? `${ srcsetSrc } ${ srcsetWidth }w` : null;
} )
.filter( Boolean )
.join( ',' );
}

return Object.assign( { src }, srcSet && { srcSet } );
}

function isWpcomFilesUrl( url ) {
const { host } = parseUrl( url );
return /\.files\.wordpress\.com$/.test( host );
}

/**
* Apply photon arguments to *.files.wordpress.com images
*
* This function largely duplicates the functionlity of the photon.js lib.
* This is necessary because we want to serve images from *.files.wordpress.com so that private
* WordPress.com sites can use this block which depends on a Photon-like image service.
*
* If we pass all images through Photon servers, some images are unreachable. *.files.wordpress.com
* is already photon-like so we can pass it the same parameters for image resizing.
*
* @param {string} url Image url
* @param {Object} opts Options to pass to photon
*
* @return {string} Url string with options applied
*/
function photonWpcomImage( url, opts = {} ) {
// Adhere to the same options API as the photon.js lib
const photonLibMappings = {
width: 'w',
height: 'h',
letterboxing: 'lb',
removeLetterboxing: 'ulb',
};

// Discard some param parts
const { auth, hash, port, query, search, ...urlParts } = parseUrl( url );

// Build query
// This reduction intentionally mutates the query as it is built internally.
urlParts.query = Object.keys( opts ).reduce(
( q, key ) =>
Object.assign( q, {
[ photonLibMappings.hasOwnProperty( key ) ? photonLibMappings[ key ] : key ]: opts[ key ],
} ),
{}
);

return formatUrl( urlParts );
}

0 comments on commit 4f6c119

Please sign in to comment.