| 1 |
/* eslint-env browser, node */ |
| 2 |
/** |
| 3 |
* Imagify pure helpers. |
| 4 |
* |
| 5 |
* Deliberately free of jQuery, the DOM, and every `imagify*` config global, so it |
| 6 |
* can be imported directly by a unit test with no loader, sandbox, or stubbing. |
| 7 |
* Everything here must stay a plain input-to-output function; anything needing the |
| 8 |
* page belongs in the file that uses it. |
| 9 |
* |
| 10 |
* Attached to `window.imagify` for the admin scripts, and exported through |
| 11 |
* `module.exports` for Jest. Both guards are defensive so the file is safe to load |
| 12 |
* in either place. |
| 13 |
*/ |
| 14 |
( function ( w ) { |
| 15 |
/** |
| 16 |
* Format a number of bytes as a human readable size. |
| 17 |
* |
| 18 |
* @param {number} bytes Number of bytes. |
| 19 |
* @return {string} e.g. "1.00 MB". Non-breaking space before the unit. |
| 20 |
*/ |
| 21 |
function humanSize( bytes ) { |
| 22 |
var sizes = [ 'B', 'kB', 'MB' ], |
| 23 |
i; |
| 24 |
|
| 25 |
if ( 0 === bytes ) { |
| 26 |
return '0\xA0kB'; |
| 27 |
} |
| 28 |
|
| 29 |
i = parseInt( Math.floor( Math.log( bytes ) / Math.log( 1024 ) ), 10 ); |
| 30 |
|
| 31 |
return ( bytes / Math.pow( 1024, i ) ).toFixed( 2 ) + '\xA0' + sizes[ i ]; |
| 32 |
} |
| 33 |
|
| 34 |
/** |
| 35 |
* Format a plan quota for display. |
| 36 |
* |
| 37 |
* @param {number} quota Quota in MB. -1 means unlimited. |
| 38 |
* @return {string} e.g. "Unlimited", "1 GB", "500 MB". |
| 39 |
*/ |
| 40 |
function formatQuota( quota ) { |
| 41 |
if ( -1 === quota ) { |
| 42 |
return 'Unlimited'; |
| 43 |
} |
| 44 |
|
| 45 |
return quota >= 1000 ? quota / 1000 + ' GB' : quota + ' MB'; |
| 46 |
} |
| 47 |
|
| 48 |
/** |
| 49 |
* Monthly equivalent of an annual cost, rounded to 2 decimals. |
| 50 |
* |
| 51 |
* @param {number} annual Annual cost. |
| 52 |
* @return {number} |
| 53 |
*/ |
| 54 |
function monthlyFromAnnual( annual ) { |
| 55 |
return Math.round( annual / 12 * 100 ) / 100; |
| 56 |
} |
| 57 |
|
| 58 |
/** |
| 59 |
* Apply a percentage discount to a plan's prices. |
| 60 |
* |
| 61 |
* @param {number} monthly Monthly cost. |
| 62 |
* @param {number} annual Annual cost. |
| 63 |
* @param {number} couponValue Discount in percent, e.g. 30 for -30%. |
| 64 |
* @return {{monthly: number, yearly: number}} |
| 65 |
*/ |
| 66 |
function applyDiscount( monthly, annual, couponValue ) { |
| 67 |
var percent = ( 100 - couponValue ) / 100; |
| 68 |
|
| 69 |
return { |
| 70 |
monthly: monthly * percent, |
| 71 |
yearly: monthlyFromAnnual( annual * percent ) |
| 72 |
}; |
| 73 |
} |
| 74 |
|
| 75 |
/** |
| 76 |
* Progress of a bulk run. |
| 77 |
* |
| 78 |
* The workload can grow mid-run (new uploads), so the largest total seen is used |
| 79 |
* as the denominator - otherwise `processed` goes negative and the percentage |
| 80 |
* exceeds 100. See #760 and #865. |
| 81 |
* |
| 82 |
* @param {number} total Number of media to process when the run started. |
| 83 |
* @param {number} remaining Number of media still waiting to be processed. |
| 84 |
* @return {{processed: number, total: number, percent: number}} |
| 85 |
*/ |
| 86 |
function getProgress( total, remaining ) { |
| 87 |
var effectiveTotal, processed; |
| 88 |
|
| 89 |
total = Math.max( parseInt( total, 10 ) || 0, 0 ); |
| 90 |
remaining = Math.max( parseInt( remaining, 10 ) || 0, 0 ); |
| 91 |
|
| 92 |
effectiveTotal = Math.max( total, remaining ); |
| 93 |
processed = effectiveTotal - remaining; |
| 94 |
|
| 95 |
return { |
| 96 |
processed: processed, |
| 97 |
total: effectiveTotal, |
| 98 |
percent: effectiveTotal > 0 ? Math.floor( processed / effectiveTotal * 100 ) : 0 |
| 99 |
}; |
| 100 |
} |
| 101 |
|
| 102 |
/** |
| 103 |
* Split a price into its integer part and a two-digit decimal part. |
| 104 |
* |
| 105 |
* The pricing modal built this inline in three places. It crashed on a whole |
| 106 |
* number: `'5'.split('.')[1]` is undefined, so reading `.length` threw a |
| 107 |
* TypeError and the modal failed to render. A price with no decimal separator |
| 108 |
* now yields '00'. |
| 109 |
* |
| 110 |
* @param {string|number} value A price, e.g. 4.99, '4.9', 5. |
| 111 |
* @return {string[]} [ integerPart, twoDigitDecimalPart ] |
| 112 |
*/ |
| 113 |
function splitPrice( value ) { |
| 114 |
var parts = ( '' + value ).split( '.' ), |
| 115 |
decimals = parts.length > 1 ? '' + parts[ 1 ] : ''; |
| 116 |
|
| 117 |
if ( '' === decimals ) { |
| 118 |
decimals = '00'; |
| 119 |
} else if ( 1 === decimals.length ) { |
| 120 |
decimals = decimals + '0'; |
| 121 |
} else { |
| 122 |
decimals = decimals.substring( 0, 2 ); |
| 123 |
} |
| 124 |
|
| 125 |
return [ parts[ 0 ], decimals ]; |
| 126 |
} |
| 127 |
|
| 128 |
/** |
| 129 |
* Plan names a promotion applies to, deduplicated. |
| 130 |
* |
| 131 |
* @param {object} promo A promotion, whose `applies_to` is either an array of |
| 132 |
* objects carrying `plan_name`, or a bare value. |
| 133 |
* @return {string[]} |
| 134 |
*/ |
| 135 |
function getPromoAppliesTo( promo ) { |
| 136 |
var appliesTo = []; |
| 137 |
|
| 138 |
if ( ! ( promo.applies_to instanceof Array ) ) { |
| 139 |
return [ promo.applies_to ]; |
| 140 |
} |
| 141 |
|
| 142 |
promo.applies_to.forEach( function ( plan ) { |
| 143 |
if ( ! appliesTo.includes( plan.plan_name ) ) { |
| 144 |
appliesTo.push( plan.plan_name ); |
| 145 |
} |
| 146 |
} ); |
| 147 |
|
| 148 |
return appliesTo; |
| 149 |
} |
| 150 |
|
| 151 |
/** |
| 152 |
* Coerce a media ID to an integer. |
| 153 |
* |
| 154 |
* @param {number|string} id A media ID. |
| 155 |
* @return {number} |
| 156 |
*/ |
| 157 |
function sanitizeId( id ) { |
| 158 |
return parseInt( id, 10 ); |
| 159 |
} |
| 160 |
|
| 161 |
var helpers = { |
| 162 |
getProgress: getProgress, |
| 163 |
getPromoAppliesTo: getPromoAppliesTo, |
| 164 |
sanitizeId: sanitizeId, |
| 165 |
splitPrice: splitPrice, |
| 166 |
humanSize: humanSize, |
| 167 |
formatQuota: formatQuota, |
| 168 |
monthlyFromAnnual: monthlyFromAnnual, |
| 169 |
applyDiscount: applyDiscount |
| 170 |
}; |
| 171 |
|
| 172 |
if ( typeof w !== 'undefined' && w ) { |
| 173 |
w.imagify = w.imagify || {}; |
| 174 |
|
| 175 |
for ( var key in helpers ) { |
| 176 |
if ( Object.prototype.hasOwnProperty.call( helpers, key ) ) { |
| 177 |
w.imagify[ key ] = helpers[ key ]; |
| 178 |
} |
| 179 |
} |
| 180 |
} |
| 181 |
|
| 182 |
if ( typeof module !== 'undefined' && module.exports ) { |
| 183 |
module.exports = helpers; |
| 184 |
} |
| 185 |
} )( typeof window !== 'undefined' ? window : undefined ); |
| 186 |
|