PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / trunk
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF vtrunk
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / assets / js / helpers.js

helpers.js in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF trunk, at assets/js/helpers.js

186 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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