From 1550ac326a4f3bf20b22c64c106b8b760e8e912f Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Tue, 16 Apr 2019 14:19:58 +0200 Subject: [PATCH 1/2] Add srcset to images in the editor --- .../tiled-gallery/gallery-image/edit.js | 2 + .../blocks/tiled-gallery/layout/index.js | 85 +--------- .../blocks/tiled-gallery/utils/index.js | 151 ++++++++++++++++++ 3 files changed, 159 insertions(+), 79 deletions(-) create mode 100644 extensions/blocks/tiled-gallery/utils/index.js diff --git a/extensions/blocks/tiled-gallery/gallery-image/edit.js b/extensions/blocks/tiled-gallery/gallery-image/edit.js index e4d77349531ab..1658873a5101e 100644 --- a/extensions/blocks/tiled-gallery/gallery-image/edit.js +++ b/extensions/blocks/tiled-gallery/gallery-image/edit.js @@ -68,6 +68,7 @@ class GalleryImageEdit extends Component { linkTo, onRemove, origUrl, + srcSet, url, width, } = this.props; @@ -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 } /> diff --git a/extensions/blocks/tiled-gallery/layout/index.js b/extensions/blocks/tiled-gallery/layout/index.js index abcb564109d9e..58feff8fc304a 100644 --- a/extensions/blocks/tiled-gallery/layout/index.js +++ b/extensions/blocks/tiled-gallery/layout/index.js @@ -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 @@ -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 @@ -53,6 +25,7 @@ export default class Layout extends Component { images, isSave, linkTo, + layoutStyle, onRemoveImage, onSelectImage, selectedImage, @@ -67,6 +40,8 @@ export default class Layout extends Component { ); const Image = isSave ? GalleryImageSave : GalleryImageEdit; + const { src, srcSet } = photonizedImgProps( img, { layoutStyle } ); + return ( { ); @@ -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 ); -} diff --git a/extensions/blocks/tiled-gallery/utils/index.js b/extensions/blocks/tiled-gallery/utils/index.js new file mode 100644 index 0000000000000..e4a9bdf955a8a --- /dev/null +++ b/extensions/blocks/tiled-gallery/utils/index.js @@ -0,0 +1,151 @@ +/** + * 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 + * + * @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; + let srcSet; + if ( isSquareishLayout( layoutStyle ) ) { + const minWidth = Math.min( 600, 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( 600, 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 ); +} From 4bafbe26ed8230f69daaefdcdc08ce62b440980f Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 25 Apr 2019 17:48:51 +0200 Subject: [PATCH 2/2] Pull srcsetMinWidth into const --- extensions/blocks/tiled-gallery/utils/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extensions/blocks/tiled-gallery/utils/index.js b/extensions/blocks/tiled-gallery/utils/index.js index e4a9bdf955a8a..1fbb4bb44352f 100644 --- a/extensions/blocks/tiled-gallery/utils/index.js +++ b/extensions/blocks/tiled-gallery/utils/index.js @@ -67,13 +67,15 @@ export function photonizedImgProps( img, galleryAtts = {} ) { /** * Build a sensible `srcSet` that will let the browser get an optimized image based on - * viewport width + * viewport width. */ const step = 300; + const srcsetMinWith = 600; + let srcSet; if ( isSquareishLayout( layoutStyle ) ) { - const minWidth = Math.min( 600, width, height ); + const minWidth = Math.min( srcsetMinWith, width, height ); const maxWidth = Math.min( PHOTON_MAX_RESIZE, width, height ); srcSet = range( minWidth, maxWidth, step ) @@ -87,7 +89,7 @@ export function photonizedImgProps( img, galleryAtts = {} ) { .filter( Boolean ) .join( ',' ); } else { - const minWidth = Math.min( 600, width ); + const minWidth = Math.min( srcsetMinWith, width ); const maxWidth = Math.min( PHOTON_MAX_RESIZE, width ); srcSet = range( minWidth, maxWidth, step )