Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update image formatter to use dynamic thumbnailer #998

Merged
merged 5 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 35 additions & 66 deletions static/js/formatters-internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export function image(simpleOrComplexImage = {}, size = '200x', atLeastAsLarge =
return img;
nmanu1 marked this conversation as resolved.
Show resolved Hide resolved
}

function imageBySizeEntity(image, desiredSize, atLeastAsLarge = true) {
function createDynamicUrl(image, desiredSize, atLeastAsLarge = true) {
nmanu1 marked this conversation as resolved.
Show resolved Hide resolved
if ((image == null) || !(Object.prototype.toString.call(image).indexOf('Object') > 0)) {
throw new Error("Expected parameter of type Map");
}
Expand All @@ -308,22 +308,6 @@ export function image(simpleOrComplexImage = {}, size = '200x', atLeastAsLarge =
throw new Error(`Object of type boolean expected. Got ${typeof atLeastAsLarge}.`);
}

if (!image.thumbnails) {
image.thumbnails = [];
}

if (!Array.isArray(image.thumbnails)) {
throw new Error(`Object of type array expected. Got ${typeof image.thumbnails}.`);
}

if (image.width != undefined && image.height != undefined && image.url != undefined) {
image.thumbnails.push({
'width': image.width,
'height': image.height,
'url': image.url
});
}

let desiredWidth, desiredHeight;
let desiredDims = desiredSize.split('x');

Expand All @@ -340,15 +324,28 @@ export function image(simpleOrComplexImage = {}, size = '200x', atLeastAsLarge =
throw new Error("Invalid height specified");
}
}
const thumbnails = image.thumbnails
.filter(thumb => thumb.width && thumb.height)
.sort((a, b) => b.width - a.width);
return atLeastAsLarge
? _getSmallestThumbnailOverThreshold(thumbnails, desiredWidth, desiredHeight)
: _getLargestThumbnailUnderThreshold(thumbnails, desiredWidth, desiredHeight);

const [urlWithoutExtension, extension] = _splitUrlOnIndex(image.url, image.url.lastIndexOf('.'));
const [urlBeforeDimensions, dimensions] = _splitUrlOnIndex(urlWithoutExtension, urlWithoutExtension.lastIndexOf('/') + 1);

if (desiredDims[0] === '' || desiredDims[1] === '') {
yen-tt marked this conversation as resolved.
Show resolved Hide resolved
if (atLeastAsLarge) {
desiredWidth = desiredWidth ?? 1;
desiredHeight = desiredHeight ?? 1;
} else {
const fullSizeDims = dimensions.split('x');
desiredWidth = desiredWidth ?? Number.parseInt(fullSizeDims[0]);
desiredHeight = desiredHeight ?? Number.parseInt(fullSizeDims[1]);
}
}

const urlWithDesiredDims = urlBeforeDimensions + desiredWidth + 'x' + desiredHeight + extension;

return atLeastAsLarge ? _replaceUrlHost(urlWithDesiredDims, 'dynl.mktgcdn.com')
yen-tt marked this conversation as resolved.
Show resolved Hide resolved
: _replaceUrlHost(urlWithDesiredDims, 'dynm.mktgcdn.com');
}

const result = imageBySizeEntity(img, size, atLeastAsLarge);
const result = createDynamicUrl(img, size, atLeastAsLarge);

return Object.assign(
{},
Expand All @@ -360,55 +357,27 @@ export function image(simpleOrComplexImage = {}, size = '200x', atLeastAsLarge =
}

/**
* Gets the smallest thumbnail that is over the min width and min height.
* If no thumbnails are over the given thresholds, will return the closest one.
*
* This method assumes all thumbnails have the same aspect ratio, and that
* thumbnails are sorted in descending size.
*
* @param {Array<{{url: string, width: number, height: number}}>} thumbnails
* @param {number|undefined} minWidth
* @param {number|undefined} minHeight
* @returns {string}
* Splits a url into two parts at the specified index.
*
* @param {string} url
* @param {number} index
* @returns {Array<string>}
*/
function _getSmallestThumbnailOverThreshold(thumbnails, minWidth, minHeight) {
let index = thumbnails.length - 1;
while (index > 0) {
const thumb = thumbnails[index];
const widthOverThreshold = minWidth ? thumb.width >= minWidth : true;
const heightOverThreshold = minHeight ? thumb.height >= minHeight : true;
if (widthOverThreshold && heightOverThreshold) {
return thumb.url
}
index--;
}
return thumbnails[0].url;
function _splitUrlOnIndex(url, index) {
nmanu1 marked this conversation as resolved.
Show resolved Hide resolved
return [url.slice(0, index), url.slice(index)];
}

/**
* Gets the largest thumbnail that is under the max width and max height.
* If no thumbnails are under the given thresholds, will return the closest one.
* Replaces the current host of a url with the specified host.
*
* This method assumes all thumbnails have the same aspect ratio, and that
* thumbnails are sorted in descending size.
*
* @param {Array<{{url: string, width: number, height: number}}>} thumbnails
* @param {number|undefined} maxWidth
* @param {number|undefined} maxHeight
* @param {string} url
* @param {string} host
* @returns {string}
*/
function _getLargestThumbnailUnderThreshold(thumbnails, maxWidth, maxHeight) {
let index = 0;
while (index < thumbnails.length) {
const thumb = thumbnails[index];
const widthOverThreshold = maxWidth ? thumb.width <= maxWidth : true;
const heightOverThreshold = maxHeight ? thumb.height <= maxHeight : true;
if (widthOverThreshold && heightOverThreshold) {
return thumb.url
}
index++;
}
return thumbnails[thumbnails.length - 1].url;
function _replaceUrlHost(url, host) {
const splitUrl = url.split('://');
const urlAfterHost = splitUrl[1].slice(splitUrl[1].indexOf('/'));
return splitUrl[0] + '://' + host + urlAfterHost;
yen-tt marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
72 changes: 14 additions & 58 deletions tests/static/js/formatters-internal/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,99 +2,55 @@ import Formatters from 'static/js/formatters.js';

describe('image formatter', () => {
const img = {
url: 'https://a.mktgcdn.com/p/1024x768.jpg',
width: 1024,
height: 768,
thumbnails: [
{
url: 'https://a.mktgcdn.com/p/619x348.jpg',
width: 619,
height: 348
},
{
url: 'https://a.mktgcdn.com/p/600x337.jpg',
width: 600,
height: 337
},
{
url: 'https://a.mktgcdn.com/p/196x110.jpg',
width: 196,
height: 110
}
]
url: 'https://a.mktgcdn.com/p/1024x768.jpg'
}

describe('when choosing the smallest image over threshold', () => {
it('By default chooses the smallest image with width >= 200', () => {
const imageUrl = Formatters.image(img).url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/600x337.jpg');
expect(imageUrl).toEqual('https://dynl.mktgcdn.com/p/200x1.jpg');
});

it('Can restrict the dimensions by width', () => {
const imageUrl = Formatters.image(img, '601x').url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/619x348.jpg');
});

it('Can restrict by width when both dimensions specified', () => {
const imageUrl = Formatters.image(img, '601x1').url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/619x348.jpg');
expect(imageUrl).toEqual('https://dynl.mktgcdn.com/p/601x1.jpg');
});

it('Can restrict the dimensions by height', () => {
const imageUrl = Formatters.image(img, 'x338').url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/619x348.jpg');
});

it('Can restrict by height when both dimensions specified', () => {
const imageUrl = Formatters.image(img, '1x338').url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/619x348.jpg');
});

it('Can restrict by width when both dimensions specified', () => {
const imageUrl = Formatters.image(img, '601x0').url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/619x348.jpg');
expect(imageUrl).toEqual('https://dynl.mktgcdn.com/p/1x338.jpg');
});

it('return the largest image when no image is over threshold', () => {
const imageUrl = Formatters.image(img, '99999x99999').url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/1024x768.jpg');
it('Can restrict by both dimensions', () => {
const imageUrl = Formatters.image(img, '601x338').url;
expect(imageUrl).toEqual('https://dynl.mktgcdn.com/p/601x338.jpg');
});

it('returns the smallest image when no dimensions given', () => {
const imageUrl = Formatters.image(img, 'x').url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/196x110.jpg');
expect(imageUrl).toEqual('https://dynl.mktgcdn.com/p/1x1.jpg');
});
});

describe('when choosing the biggest image under threshold', () => {
it('Can restrict the dimensions by width', () => {
const imageUrl = Formatters.image(img, '601x', false).url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/600x337.jpg');
});

it('Can restrict by width when both dimensions specified', () => {
const imageUrl = Formatters.image(img, '601x9999', false).url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/600x337.jpg');
expect(imageUrl).toEqual('https://dynm.mktgcdn.com/p/601x768.jpg');
});

it('Can restrict the dimensions by height', () => {
const imageUrl = Formatters.image(img, 'x338', false).url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/600x337.jpg');
});

it('Can restrict by height when both dimensions specified', () => {
const imageUrl = Formatters.image(img, '9999x338', false).url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/600x337.jpg');
expect(imageUrl).toEqual('https://dynm.mktgcdn.com/p/1024x338.jpg');
});

it('returns the smallest image when no image is under threshold', () => {
const imageUrl = Formatters.image(img, '-1x-1', false).url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/196x110.jpg');
it('Can restrict by both dimensions', () => {
const imageUrl = Formatters.image(img, '999x338', false).url;
expect(imageUrl).toEqual('https://dynm.mktgcdn.com/p/999x338.jpg');
});

it('return the largest image when no dimensions given', () => {
const imageUrl = Formatters.image(img, 'x', false).url;
expect(imageUrl).toEqual('https://a.mktgcdn.com/p/1024x768.jpg');
expect(imageUrl).toEqual('https://dynm.mktgcdn.com/p/1024x768.jpg');
});
});
});