Source: app/util/imageSizeUtils.js

import webp from 'check-for-webp';
import convertToWebP from './imageTypeUtils';
/**
 * Utilities for calculating the optimal image sizes to request from the backend.
 * @module
 * @category templating
 */

/**
 * Array of popular browser sizes. This array will be used as baseline to calculate
 * the image sizes to request. This array should include mobile browser sizes corrected
 * for device pixel ratio. So for this array, a phone with a css width of 320px and a
 * device-pixel-ratio of 2 will be considered size 640.
 */
export const SCREEN_WIDTHS = [480, 640, 800, 1080, 1280, 1440, 1920];

/**
 * The minimum width difference between image variants. If variants are closer together
 * than this amount, the filterSimilarVariants() util will remove them
 * @type {number}
 */
export const VARIANT_MIN_DIFF = 20;

/**
 * Get the width variants based on a scale of the full screen size.
 * @param {number} [scale=1] The scale of the image relative to the full viewport width
 * @returns {Array<number>} An array of width variants
 * @example // returns image variants for an image that shows at 50% of screen size
 * const variants = getViewportRelativeVariants(0.5);
 */
export const getViewportRelativeVariants = (scale = 1) => SCREEN_WIDTHS.map(x => x * scale);

/**
 * Returns a new array of width with the widths that are too close together
 * filtered out. The amount that widths have to differ is defined in VARIANT_MIN_DIFF
 * @param {Array<number>} widths An array of image widths
 * @returns {Array<number>} The filtered image widths
 */
export const filterSimilarVariants = widths =>
  [...widths]
    .sort((a, b) => a - b)
    .reduce((newVariants, variant) => {
      if (!newVariants.length || variant - newVariants[newVariants.length - 1] > VARIANT_MIN_DIFF) {
        newVariants.push(variant);
      }
      return newVariants;
    }, []);

/**
 * Picks the variant from an array of widths that should be used as fallback when
 * a browser does not support srcset attributes.
 * @param {Array<number>} widths An array of image widths
 * @returns {number} The fallback variant
 */
export const getFallbackVariant = widths => {
  const average = widths.reduce((sum, variant) => sum + variant) / widths.length;

  return [...widths].sort((a, b) => Math.abs(average - a) - Math.abs(average - b))[0];
};

/**
 * Gets the cropping url for a given image width
 * @param {string} url The url of the image resource
 * @param {number} width The width of the image to request
 * @param {number} [ratio] If set, will also include height in the image request.
 * This number should be equal to width divided by height.
 * @returns {string} An url to the cropped image resource
 * @example const src = getVariantUrl('http://some/image.jpg', 800, 16/9);
 */
export const getVariantUrl = ({ url, width, ratio, useWebp = true }) => {
  if (!url) {
    return '';
  }
  return `${webp && useWebp ? convertToWebP(url) : url}${
    url.indexOf('?') === -1 ? '?' : '&'
  }width=${width}${ratio ? `&height=${parseInt(width / ratio, 10)}` : ''}`;
};

/**
 * Generate a image src url based off a cloudinary id
 * @param {string} url The url of the cloudinary image
 * @param {number} width The width of the image to request
 * @param {number} ratio If set, will also include height in the image
 * request.
 * This number should be equal to width divided by height.
 * @param {string} cropType Will crop the image depending on how passed in
 * @param {bool} useWebp If true will return a webp url
 * @returns {string} An url to the cropped image resource
 * @example const src = getCloudinaryVariants({
 *    url: cloudinarySrc,
      useWebp: hasWebpSupport,
      width: px,
      ratio,
      cropTye: cloudinaryCropType,
    });
 */
export const getCloudinaryVariants = ({ url, width, cropType = 'fill', ratio, useWebp = true }) => {
  const [cloudName, imageUrl] = url && url.split('/image/upload/');
  const widthSrc = width ? `w_${width}` : '';
  const heightSrc = width && ratio ? `h_${parseInt(width / ratio, 10)}` : '';

  const imageRequestCrop = `c_${cropType},${heightSrc},${widthSrc},f_auto/`;
  // Needs to pull res.cloudinary.com/ from config
  const cropSrc = `https://res.cloudinary.com${cloudName}/image/upload/${imageRequestCrop}${imageUrl}`;

  return webp && useWebp ? convertToWebP(cropSrc) : cropSrc;
};

/** 1920/
 * Get the srcset to use for the given image widths
 * @param {string} url The url of the image resource
 * @param {Array<number>} widths The widths of the images to request
 * @param {number} [ratio] If set, will also include height in the image requests.
 * This number should be equal to width divided by height.
 * @returns {object} An object containing the following properties:
 *  - fallback A string for a fallback url to request when there is no srcset support
 *  - srcSet A string that can be used as srcset argument
 */
export const getSrcSetFromVariants = (url, widths, ratio, cloudinarySrc = false) => {
  const imageVariants = params =>
    cloudinarySrc ? getCloudinaryVariants(params) : getVariantUrl(params);
  return {
    fallback: getVariantUrl({ url, widths: getFallbackVariant(widths), ratio }),
    srcSet: widths
      .map(variant => `${imageVariants({ url, width: variant, ratio })} ${variant}w`)
      .join(','),
    srcSetWebp: widths
      .map(variant => `${imageVariants({ url, width: variant, ratio, useWebp: true })} ${variant}w`)
      .join(','),
  };
};