Source: utils/srcsetUtils.js

const ensurePresenceOf = require("./ensurePresenceOf");

const utils = require('./index');
const isEmpty = utils.isEmpty;
const generateBreakpoints = require('./generateBreakpoints');
const config = require('../../cloudinary').config;
const Cache = require('./../Cache');

/**
 * Options used to generate the srcset attribute.
 * @typedef {object} srcset
 * @property {(number[]|string[])}   [breakpoints] An array of breakpoints.
 * @property {number}                [min_width]   Minimal width of the srcset images.
 * @property {number}                [max_width]   Maximal width of the srcset images.
 * @property {number}                [max_images]  Number of srcset images to generate.
 * @property {object|string}         [transformation] The transformation to use in the srcset urls.
 * @property {boolean}               [sizes] Whether to calculate and add the sizes attribute.
 */

/**
 * Helper function. Generates a single srcset item url
 *
 * @private
 * @param {string} public_id  Public ID of the resource.
 * @param {number} width      Width in pixels of the srcset item.
 * @param {object|string} transformation
 * @param {object} options    Additional options.
 *
 * @return {string} Resulting URL of the item
 */
function srcsetUrl(public_id, width, transformation, options = {}) {
  let configParams = utils.extractUrlParams(options);
  transformation = transformation || options;
  configParams.raw_transformation = utils.generate_transformation_string([utils.extend({}, transformation), {crop: 'scale', width: width}]);

  return utils.url(public_id, configParams);
}

/**
 * If cache is enabled, get the breakpoints from the cache. If the values were not found in the cache,
 * or cache is not enabled, generate the values.
 * @param {srcset} srcset The srcset configuration parameters
 * @param {string} public_id
 * @param {object} options
 * @return {*|Array}
 */
function getOrGenerateBreakpoints(public_id, srcset, options) {
  let  breakpoints = [];
  if (srcset.useCache) {
    breakpoints = Cache.get(public_id, options);
    if (!breakpoints) {
      breakpoints = [];
    }
  } else {
    breakpoints = generateBreakpoints(srcset);
  }
  return breakpoints;
}

/**
 * Helper function. Generates srcset attribute value of the HTML img tag
 * @private
 *
 * @param {string} public_id  Public ID of the resource
 * @param {number[]} breakpoints An array of breakpoints (in pixels)
 * @param {object} transformation The transformation
 * @param {object} options Includes html tag options, transformation options
 * @return {string} Resulting srcset attribute value
 */
function generateSrcsetAttribute(public_id, breakpoints, transformation, options) {
  options = utils.clone(options);
  utils.patchFetchFormat(options);
  return breakpoints.map(width=>`${srcsetUrl(public_id, width, transformation, options)} ${width}w`).join(', ');
}

/**
 * Helper function. Generates sizes attribute value of the HTML img tag
 * @private
 * @param {object|string} srcsetData
 * @param {number[]} [srcsetData.breakpoints] An array of breakpoints.
 * @param {number}    [srcsetData.min_width]  Minimal width of the srcset images.
 * @param {number}    [srcsetData.max_width]  Maximal width of the srcset images.
 * @param {number}    [srcsetData.max_images] Number of srcset images to generate.
 *
 * @return {string} Resulting sizes attribute value
 */
function generateSizesAttribute(srcsetData){
  if (!srcsetData) {
    return '';
  } else if  (utils.isString(srcsetData)) {
    return srcsetData;
  }

  let breakpoints = getOrGenerateBreakpoints(srcsetData);
  return breakpoints.map(width=>`(max-width: ${width}px) ${width}px`).join(', ');
}

/**
 * Helper function. Generates srcset and sizes attributes of the image tag
 *
 * Generated attributes are added to attributes argument
 *
 * @private
 * @param {string}    publicId  The public ID of the resource
 * @param {object}    attributes Existing HTML attributes.
 * @param {srcset}    srcsetData
 * @param {object}    options    Additional options.
 *
 * @return array The responsive attributes
 */
function generateImageResponsiveAttributes(publicId, attributes={}, srcsetData={}, options={}){
  // Create both srcset and sizes here to avoid fetching breakpoints twice

  let responsiveAttributes = [];
  if (isEmpty(srcsetData)) {
    return responsiveAttributes;
  }

  let breakpoints = null;

  if (!attributes.srcset || (!attributes.sizes && srcsetData.sizes === true)) {
    breakpoints = getOrGenerateBreakpoints(publicId, srcsetData, options);
  }

  if (!attributes.srcset) {
    let transformation = srcsetData.transformation;
    let srcsetAttr = generateSrcsetAttribute(publicId, breakpoints, transformation, options);
    if (!isEmpty(srcsetAttr)) {
      responsiveAttributes["srcset"] = srcsetAttr;
    }
  }

  if (!attributes.sizes && srcsetData.sizes === true) {
    let sizesAttr = generateSizesAttribute(breakpoints);
    if (!isEmpty(sizesAttr)) {
      responsiveAttributes["sizes"] = sizesAttr;
    }
  }
  return responsiveAttributes;
}

/**
 * Generate a media query
 *
 * @private
 * @param {object} options configuration options
 * @param {number|string} options.min_width
 * @param {number|string} options.max_width
 * @return {string} a media query string
 */
function generateMediaAttr(options={}){
  let mediaQuery = [];
  if(options.min_width != null){
    mediaQuery.push(`(min-width: ${options.min_width}px)`);
  }
  if(options.max_width != null){
    mediaQuery.push(`(max-width: ${options.max_width}px)`);
  }
  return mediaQuery.join(' and ');
}

module.exports = {srcsetUrl, generateSrcsetAttribute, generateSizesAttribute, generateMediaAttr, generateImageResponsiveAttributes};