PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 1.9.8
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v1.9.8
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 / bulk.js

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

1,536 lines 45.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 window.imagify = window.imagify || {};
2
3 (function( $, undefined ) { // eslint-disable-line no-shadow, no-shadow-restricted-names
4
5 var jqPropHookChecked = $.propHooks.checked;
6
7 // Force `.prop()` to trigger a `change` event.
8 $.propHooks.checked = {
9 set: function( elem, value, name ) {
10 var ret;
11
12 if ( undefined === jqPropHookChecked ) {
13 ret = ( elem[ name ] = value );
14 } else {
15 ret = jqPropHookChecked( elem, value, name );
16 }
17
18 $( elem ).trigger( 'change.imagify' );
19
20 return ret;
21 }
22 };
23
24 // Custom jQuery functions =====================================================================
25 /**
26 * Hide element(s).
27 *
28 * @param {int} duration A duration in ms.
29 * @param {function} callback A callback to execute once the element is hidden.
30 * @return {element} The jQuery element(s).
31 */
32 $.fn.imagifyHide = function( duration, callback ) {
33 if ( duration && duration > 0 ) {
34 this.hide( duration, function() {
35 $( this ).addClass( 'hidden' ).css( 'display', '' );
36
37 if ( undefined !== callback ) {
38 callback();
39 }
40 } );
41 } else {
42 this.addClass( 'hidden' );
43
44 if ( undefined !== callback ) {
45 callback();
46 }
47 }
48
49 return this.attr( 'aria-hidden', 'true' );
50 };
51
52 /**
53 * Show element(s).
54 *
55 * @param {int} duration A duration in ms.
56 * @param {function} callback A callback to execute before starting to display the element.
57 * @return {element} The jQuery element(s).
58 */
59 $.fn.imagifyShow = function( duration, callback ) {
60 if ( undefined !== callback ) {
61 callback();
62 }
63
64 if ( duration && duration > 0 ) {
65 this.show( duration, function() {
66 $( this ).removeClass( 'hidden' ).css( 'display', '' );
67 } );
68 } else {
69 this.removeClass( 'hidden' );
70 }
71
72 return this.attr( 'aria-hidden', 'false' );
73 };
74
75 }( jQuery ));
76
77
78 (function($, d, w, undefined) { // eslint-disable-line no-unused-vars, no-shadow, no-shadow-restricted-names
79
80 w.imagify.bulk = {
81
82 // Properties ==============================================================================
83 charts: {
84 overview: {
85 canvas: false,
86 donut: false,
87 data: {
88 // Order: unoptimized, optimized, error.
89 labels: [
90 imagifyBulk.labels.overviewChartLabels.unoptimized,
91 imagifyBulk.labels.overviewChartLabels.optimized,
92 imagifyBulk.labels.overviewChartLabels.error
93 ],
94 datasets: [ {
95 data: [],
96 backgroundColor: [ '#10121A', '#46B1CE', '#C51162' ],
97 borderWidth: 0
98 } ]
99 }
100 },
101 files: {
102 donuts: {}
103 },
104 share: {
105 canvas: false,
106 donut: false
107 }
108 },
109 /**
110 * Folder types in queue.
111 * An array of objects: {
112 * @type {string} groupID The group ID, like 'library'.
113 * @type {string} context The context, like 'wp'.
114 * @type {int} level The optimization level: 0, 1, or 2.
115 * }
116 */
117 folderTypesQueue: [],
118 /**
119 * Status of each folder type. Type IDs are used as keys.
120 * Each object contains: {
121 * @type {bool} isError Tell if the status is considered as an error.
122 * @type {string} id ID of the status, like 'waiting', 'fetching', or 'optimizing'.
123 * }
124 */
125 status: {},
126 // Tell if the message displayed when retrieving the image IDs has been shown once.
127 displayedWaitMessage: false,
128 // Tell how many rows are available.
129 hasMultipleRows: true,
130 // The action to perform (like 'optimize').
131 imagifyAction: '',
132 // Set to true to stop the whole thing.
133 processIsStopped: false,
134 // List of medias being processed.
135 processingMedia: [],
136 // Global stats.
137 globalGain: 0,
138 globalOriginalSize: 0,
139 globalOptimizedSize: 0,
140 /**
141 * Folder types used in the page.
142 *
143 * @var {object} {
144 * An object of objects. The keys are like: {groupID|context}.
145 *
146 * @type {string} groupID The group ID.
147 * @type {string} context The context.
148 * @type {int} level The optimization.
149 * @type {string} optimizeURL The URL to ping to optimize a file.
150 * @type {array} mediaIDs A list of file IDs.
151 * @type {object} files File IDs as keys (prefixed by an underscore), File URLs as values.
152 * }
153 */
154 folderTypesData: {},
155 // Default thumbnail.
156 defaultThumb: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhBAMAAAClyt9cAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABtQTFRFR3BMjo6Oh4eHqqqq4uLigoKC+fn5fn5+fHx8SBBv5wAAAAF0Uk5TAEDm2GYAAABWSURBVCjPY2BgEBIEAyERBhgQUoKAIAc0EXXjEDQRRTNzBzRdBokhaGoM2CQc0NQwJJegqWFgM3VAUSNsbGwugCKiqBQUpIAiAgICoyIDKyIIB0JAEQA54jRBweNV0AAAAABJRU5ErkJggg==',
157
158 // Methods =================================================================================
159
160 /*
161 * Init.
162 */
163 init: function () {
164 var $document = $( d );
165
166 // Overview chart.
167 this.drawOverviewChart();
168
169 this.hasMultipleRows = $( '.imagify-bulk-table [name="group[]"]' ).length > 1;
170
171 // Selectors (like the level selectors).
172 $( '.imagify-selector-button' )
173 .on( 'click.imagify', this.openSelectorFromButton );
174
175 $( '.imagify-selector-list input' )
176 .on( 'change.imagify init.imagify', this.syncSelectorFromRadio )
177 .filter( ':checked' )
178 .trigger( 'init.imagify' );
179
180 $document
181 .on( 'keypress.imagify click.imagify', this.closeSelectors );
182
183 // Other buttons/UI.
184 $( '.imagify-bulk-table [name="group[]"]' )
185 .on( 'change.imagify init.imagify', this.toggleOptimizationButton )
186 .trigger( 'init.imagify' );
187
188 $( '.imagify-show-table-details' )
189 .on( 'click.imagify open.imagify close.imagify', this.toggleOptimizationDetails );
190
191 $( '#imagify-bulk-action' )
192 .on( 'click.imagify', this.maybeLaunchAllProcesses );
193
194 $( '.imagify-share-networks a' )
195 .on( 'click.imagify', this.share );
196
197 // Optimization events.
198 $( w )
199 .on( 'processQueue.imagify', this.processQueue )
200 .on( 'optimizeFiles.imagify', this.optimizeFiles )
201 .on( 'queueEmpty.imagify', this.queueEmpty );
202
203 if ( imagifyBulk.ajaxActions.getStats && $( '.imagify-bulk-table [data-group-id="library"][data-context="wp"]' ).length ) {
204 // On large WP library, don't request stats periodically, only when everything is done.
205 imagifyBulk.imagifybeatIDs.stats = false;
206 }
207
208 if ( imagifyBulk.imagifybeatIDs.stats ) {
209 // Imagifybeat for stats.
210 $document
211 .on( 'imagifybeat-send', this.addStatsImagifybeat )
212 .on( 'imagifybeat-tick', this.processStatsImagifybeat );
213 }
214
215 // Imagifybeat for optimization queue.
216 $document
217 .on( 'imagifybeat-send', this.addQueueImagifybeat )
218 .on( 'imagifybeat-tick', this.processQueueImagifybeat );
219
220 // Imagifybeat for requirements.
221 $document
222 .on( 'imagifybeat-send', this.addRequirementsImagifybeat )
223 .on( 'imagifybeat-tick', this.processRequirementsImagifybeat );
224 },
225
226 /*
227 * Get the URL used for ajax requests.
228 *
229 * @param {string} action An ajax action, or part of it.
230 * @param {object} item The current item.
231 * @return {string}
232 */
233 getAjaxUrl: function ( action, item ) {
234 var url = ajaxurl + w.imagify.concat + '_wpnonce=' + imagifyBulk.ajaxNonce + '&action=' + imagifyBulk.ajaxActions[ action ];
235
236 if ( item && item.context ) {
237 url += '&context=' + item.context;
238 }
239
240 if ( 'getMediaIds' === action || 'bulkProcess' === action ) {
241 url += '&imagify_action=' + w.imagify.bulk.imagifyAction;
242 }
243
244 return url;
245 },
246
247 /**
248 * Get folder types used in the page.
249 *
250 * @see this.folderTypesData
251 * @return {object}
252 */
253 getFolderTypes: function () {
254 if ( ! $.isEmptyObject( w.imagify.bulk.folderTypesData ) ) {
255 return w.imagify.bulk.folderTypesData;
256 }
257
258 $( '.imagify-row-folder-type' ).each( function() {
259 var $this = $( this ),
260 data = {
261 groupID: $this.data( 'group-id' ),
262 context: $this.data( 'context' )
263 },
264 key = data.groupID + '|' + data.context;
265
266 w.imagify.bulk.folderTypesData[ key ] = data;
267 } );
268
269 return w.imagify.bulk.folderTypesData;
270 },
271
272 /*
273 * Get the message displayed to the user when (s)he leaves the page.
274 *
275 * @return {string}
276 */
277 getConfirmMessage: function () {
278 return imagifyBulk.labels.processing;
279 },
280
281 /*
282 * Close the given optimization level selector.
283 *
284 * @param {object} $lists A jQuery object.
285 * @param {int} timer Timer in ms to close the selector.
286 */
287 closeLevelSelector: function ( $lists, timer ) {
288 if ( ! $lists || ! $lists.length ) {
289 return;
290 }
291
292 if ( undefined !== timer && timer > 0 ) {
293 w.setTimeout( function() {
294 w.imagify.bulk.closeLevelSelector( $lists );
295 }, timer );
296 return;
297 }
298
299 $lists.attr( 'aria-hidden', 'true' );
300 },
301
302 /*
303 * Stop everything and update the current item status as an error.
304 *
305 * @param {string} errorId An error ID.
306 * @param {object} item The current item.
307 */
308 stopProcess: function ( errorId, item ) {
309 this.processIsStopped = true;
310
311 w.imagify.bulk.status[ item.groupID ] = {
312 isError: true,
313 id: errorId
314 };
315
316 $( w ).trigger( 'queueEmpty.imagify' );
317 },
318
319 /*
320 * Tell if we have a blocking error. Can also display an error message in a swal.
321 *
322 * @param {bool} displayErrorMessage False to not display any error message.
323 * @return {bool}
324 */
325 hasBlockingError: function ( displayErrorMessage ) {
326 displayErrorMessage = undefined !== displayErrorMessage && displayErrorMessage;
327
328 if ( imagifyBulk.curlMissing ) {
329 if ( displayErrorMessage ) {
330 w.imagify.bulk.displayError( {
331 html: imagifyBulk.labels.curlMissing
332 } );
333 }
334 return true;
335 }
336
337 if ( imagifyBulk.editorMissing ) {
338 if ( displayErrorMessage ) {
339 w.imagify.bulk.displayError( {
340 html: imagifyBulk.labels.editorMissing
341 } );
342 }
343 return true;
344 }
345
346 if ( imagifyBulk.extHttpBlocked ) {
347 if ( displayErrorMessage ) {
348 w.imagify.bulk.displayError( {
349 html: imagifyBulk.labels.extHttpBlocked
350 } );
351 }
352 return true;
353 }
354
355 if ( imagifyBulk.apiDown ) {
356 if ( displayErrorMessage ) {
357 w.imagify.bulk.displayError( {
358 html: imagifyBulk.labels.apiDown
359 } );
360 }
361 return true;
362 }
363
364 if ( ! imagifyBulk.keyIsValid ) {
365 if ( displayErrorMessage ) {
366 w.imagify.bulk.displayError( {
367 title: imagifyBulk.labels.invalidAPIKeyTitle,
368 type: 'info'
369 } );
370 }
371 return true;
372 }
373
374 if ( imagifyBulk.isOverQuota ) {
375 if ( displayErrorMessage ) {
376 w.imagify.bulk.displayError( {
377 title: imagifyBulk.labels.overQuotaTitle,
378 html: $( '#tmpl-imagify-overquota-alert' ).html(),
379 type: 'info',
380 customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
381 showConfirmButton: false
382 } );
383 }
384 return true;
385 }
386
387 return false;
388 },
389
390 /*
391 * Display an error message in a modal.
392 *
393 * @param {string} title The modal title.
394 * @param {string} text The modal text.
395 * @param {object} args Other less common args.
396 */
397 displayError: function ( title, text, args ) {
398 var def = {
399 title: '',
400 html: '',
401 type: 'error',
402 customClass: '',
403 width: 620,
404 padding: 0,
405 showCloseButton: true,
406 showConfirmButton: true
407 };
408
409 if ( $.isPlainObject( title ) ) {
410 args = $.extend( {}, def, title );
411 } else {
412 args = args || {};
413 args = $.extend( {}, def, {
414 title: title || '',
415 html: text || ''
416 }, args );
417 }
418
419 args.title = args.title || imagifyBulk.labels.error;
420 args.customClass += ' imagify-sweet-alert';
421
422 swal( args ).catch( swal.noop );
423 },
424
425 /*
426 * Display an error message in a file row.
427 *
428 * @param {function} $row The row template.
429 * @param {string} text The error text.
430 * @return {element} The row jQuery element.
431 */
432 displayErrorInRow: function ( $row, text ) {
433 var $toReplace, colspan;
434
435 $row = $( $row() );
436 $toReplace = $row.find( '.imagify-cell-status ~ td' );
437 colspan = $toReplace.length;
438 text = text || '';
439
440 $toReplace.remove();
441 $row.find( '.imagify-cell-status' ).after( '<td colspan="' + colspan + '">' + text + '</td>' );
442
443 return $row;
444 },
445
446 /*
447 * Display one of the 3 "folder" rows.
448 *
449 * @param {string} state One of the 3 states: 'resting' (it's the "normal" row), 'waiting' (waiting for other optimizations to finish), and 'working'.
450 * @param {element} $row jQuery element of the "normal" row.
451 */
452 displayFolderRow: function ( state, $row ) {
453 var $newRow, spinnerTemplate, spinnerColor, text;
454
455 if ( 'resting' === state ) {
456 $row.next( '.imagify-row-waiting, .imagify-row-working' ).remove();
457 $row.imagifyShow();
458 return;
459 }
460
461 // This part won't work to display multiple $newRow.
462 $newRow = $row.next( '.imagify-row-waiting, .imagify-row-working' );
463
464 if ( 'waiting' === state ) {
465 spinnerColor = '#d2d3d6';
466 text = imagifyBulk.labels.waitingOtimizationsText;
467 } else {
468 spinnerColor = '#40b1d0';
469 text = imagifyBulk.labels.imagesOptimizedText.replace( '%s', '<span>0</span>' );
470 }
471
472 if ( $newRow.length ) {
473 if ( ! $newRow.hasClass( 'imagify-row-' + state ) ) {
474 // Should happen when switching from 'waiting' to 'working'.
475 $newRow.attr( 'class', 'imagify-row-' + state );
476 $newRow.find( '.imagify-cell-checkbox svg' ).attr( 'fill', spinnerColor );
477 $newRow.children( '.imagify-cell-count-optimized' ).html( text );
478 }
479
480 $row.imagifyHide();
481 $newRow.imagifyShow();
482 return;
483 }
484
485 // Build the new row, based on a clone of the original one.
486 $newRow = $row.clone().attr( {
487 'class': 'imagify-row-' + state,
488 'aria-hidden': 'false'
489 } );
490
491 spinnerTemplate = w.imagify.template( 'imagify-spinner' );
492 $newRow.children( '.imagify-cell-checkbox' ).html( spinnerTemplate() ).find( 'svg' ).attr( 'fill', spinnerColor );
493 $newRow.children( '.imagify-cell-title' ).html( '<span class="imagify-cell-label">' + $newRow.children( '.imagify-cell-title' ).text() + '</span>' );
494 $newRow.children( '.imagify-cell-count-optimized' ).html( text );
495 $newRow.children( '.imagify-cell-count-errors, .imagify-cell-optimized-size, .imagify-cell-original-size, .imagify-cell-level' ).text( '' );
496
497 $row.imagifyHide().after( $newRow );
498 },
499
500 /*
501 * Display the share box.
502 */
503 displayShareBox: function () {
504 var text2share = imagifyBulk.labels.textToShare,
505 percent, gainHuman, originalSizeHuman,
506 $complete;
507
508 if ( ! this.globalGain || this.folderTypesQueue.length ) {
509 this.globalGain = 0;
510 this.globalOriginalSize = 0;
511 this.globalOptimizedSize = 0;
512 return;
513 }
514
515 percent = ( 100 - 100 * ( this.globalOptimizedSize / this.globalOriginalSize ) ).toFixed( 2 );
516 gainHuman = w.imagify.humanSize( this.globalGain, 1 );
517 originalSizeHuman = w.imagify.humanSize( this.globalOriginalSize, 1 );
518
519 text2share = text2share.replace( '%1$s', gainHuman );
520 text2share = text2share.replace( '%2$s', originalSizeHuman );
521 text2share = encodeURIComponent( text2share );
522
523 $complete = $( '.imagify-row-complete' );
524 $complete.find( '.imagify-ac-rt-total-gain' ).html( gainHuman );
525 $complete.find( '.imagify-ac-rt-total-original' ).html( originalSizeHuman );
526 $complete.find( '.imagify-ac-chart' ).attr( 'data-percent', percent );
527 $complete.find( '.imagify-sn-twitter' ).attr( 'href', imagifyBulk.labels.twitterShareURL + '&amp;text=' + text2share );
528
529 // Chart.
530 this.drawShareChart();
531
532 $complete.addClass( 'done' ).imagifyShow();
533
534 $( 'html, body' ).animate( {
535 scrollTop: $complete.offset().top
536 }, 200 );
537
538 // Reset the stats.
539 this.globalGain = 0;
540 this.globalOriginalSize = 0;
541 this.globalOptimizedSize = 0;
542 },
543
544 /**
545 * Print optimization stats.
546 *
547 * @param {object} data Object containing all Imagifybeat IDs.
548 */
549 updateStats: function ( data ) {
550 var donutData;
551
552 if ( ! data || ! $.isPlainObject( data ) ) {
553 return;
554 }
555
556 if ( w.imagify.bulk.charts.overview.donut.data ) {
557 donutData = w.imagify.bulk.charts.overview.donut.data.datasets[0].data;
558
559 if ( data.unoptimized_attachments === donutData[0] && data.optimized_attachments === donutData[1] && data.errors_attachments === donutData[2] ) {
560 return;
561 }
562 }
563
564 /**
565 * User account.
566 */
567 data.unconsumed_quota = data.unconsumed_quota.toFixed( 1 ); // A mystery where a float rounded on php side is not rounded here anymore. JavaScript is fun, it always surprises you in a manner you didn't expect.
568 $( '.imagify-meteo-icon' ).html( data.quota_icon );
569 $( '.imagify-unconsumed-percent' ).html( data.unconsumed_quota + '%' );
570 $( '.imagify-unconsumed-bar' ).css( 'width', data.unconsumed_quota + '%' ).parent().attr( 'class', data.quota_class );
571
572 /**
573 * Global chart.
574 */
575 $( '#imagify-overview-chart-percent' ).html( data.optimized_attachments_percent + '<span>%</span>' );
576 $( '.imagify-total-percent' ).html( data.optimized_attachments_percent + '%' );
577
578 w.imagify.bulk.drawOverviewChart( [
579 data.unoptimized_attachments,
580 data.optimized_attachments,
581 data.errors_attachments
582 ] );
583
584 /**
585 * Stats block.
586 */
587 // The total optimized images.
588 $( '#imagify-total-optimized-attachments' ).html( data.already_optimized_attachments );
589
590 // The original bar.
591 $( '#imagify-original-bar' ).find( '.imagify-barnb' ).html( data.original_human );
592
593 // The optimized bar.
594 $( '#imagify-optimized-bar' ).css( 'width', ( 100 - data.optimized_percent ) + '%' ).find( '.imagify-barnb' ).html( data.optimized_human );
595
596 // The Percent data.
597 $( '#imagify-total-optimized-attachments-pct' ).html( data.optimized_percent + '%' );
598 },
599
600 // Event callbacks =========================================================================
601
602 /*
603 * Selector (like optimization level selector): on button click, open the dropdown and focus the current radio input.
604 * The dropdown must be open or the focus event won't be triggered.
605 *
606 * @param {object} e jQuery's Event object.
607 */
608 openSelectorFromButton: function ( e ) {
609 var $list = $( '#' + $( this ).attr( 'aria-controls' ) );
610 // Stop click event from bubbling: this will allow to close the selector list if anything else id clicked.
611 e.stopPropagation();
612 // Close other lists.
613 $( '.imagify-selector-list' ).not( $list ).attr( 'aria-hidden', 'true' );
614 // Open the corresponding list and focus the radio.
615 $list.attr( 'aria-hidden', 'false' ).find( ':checked' ).trigger( 'focus.imagify' );
616 },
617
618 /*
619 * Selector: on radio change, make the row "current" and update the button text.
620 */
621 syncSelectorFromRadio: function () {
622 var $row = $( this ).closest( '.imagify-selector-choice' );
623 // Update rows attributes.
624 $row.addClass( 'imagify-selector-current-value' ).attr( 'aria-current', 'true' ).siblings( '.imagify-selector-choice' ).removeClass( 'imagify-selector-current-value' ).attr( 'aria-current', 'false' );
625 // Change the button text.
626 $row.closest( '.imagify-selector-list' ).siblings( '.imagify-selector-button' ).find( '.imagify-selector-current-value-info' ).html( $row.find( 'label' ).html() );
627 },
628
629 /*
630 * Selector: on Escape or Enter kaystroke, close the dropdown.
631 *
632 * @param {object} e jQuery's Event object.
633 */
634 closeSelectors: function ( e ) {
635 if ( 'keypress' === e.type && 27 !== e.keyCode && 13 !== e.keyCode ) {
636 return;
637 }
638 w.imagify.bulk.closeLevelSelector( $( '.imagify-selector-list[aria-hidden="false"]' ) );
639 },
640
641 /*
642 * Enable or disable the Optimization button depending on the checked checkboxes.
643 * Also, if there is only 1 checkbox in the page, don't allow it to be unchecked.
644 */
645 toggleOptimizationButton: function () {
646 // Prevent uncheck if there is only one checkbox.
647 if ( ! w.imagify.bulk.hasMultipleRows && ! this.checked ) {
648 $( this ).prop( 'checked', true );
649 return;
650 }
651
652 // Enable or disable the Optimization button.
653 if ( $( '.imagify-bulk-table [name="group[]"]:checked' ).length ) {
654 $( '#imagify-bulk-action' ).removeAttr( 'disabled' );
655 } else {
656 $( '#imagify-bulk-action' ).attr( 'disabled', 'disabled' );
657 }
658 },
659
660 /*
661 * Display/Hide optimization details.
662 *
663 * @param {object} e jQuery's Event object.
664 */
665 toggleOptimizationDetails: function ( e ) {
666 var $button = $( this ),
667 $details = $button.closest( '.imagify-bulk-table' ).find( '.imagify-bulk-table-details' ),
668 openDetails;
669
670 if ( 'open' === e.type ) {
671 openDetails = true;
672 } else if ( 'close' === e.type ) {
673 openDetails = false;
674 } else {
675 openDetails = $details.hasClass( 'hidden' );
676 }
677
678 if ( openDetails ) {
679 $button.html( $button.data( 'label-hide' ) + '<span class="dashicons dashicons-no-alt"></span>' );
680 $details.imagifyShow();
681 } else {
682 $button.html( $button.data( 'label-show' ) + '<span class="dashicons dashicons-menu"></span>' );
683 $details.imagifyHide();
684 }
685 },
686
687 /*
688 * Maybe display a modal, then launch all processes.
689 */
690 maybeLaunchAllProcesses: function () {
691 var $infosModal;
692
693 if ( $( this ).attr( 'disabled' ) ) {
694 return;
695 }
696
697 if ( ! $( '.imagify-bulk-table [name="group[]"]:checked' ).length ) {
698 return;
699 }
700
701 if ( w.imagify.bulk.hasBlockingError( true ) ) {
702 return;
703 }
704
705 $infosModal = $( '#tmpl-imagify-bulk-infos' );
706
707 if ( ! $infosModal.length ) {
708 w.imagify.bulk.launchAllProcesses();
709 return;
710 }
711
712 // Swal Information before loading the optimize process.
713 swal( {
714 title: imagifyBulk.labels.bulkInfoTitle,
715 html: $infosModal.html(),
716 type: '',
717 customClass: 'imagify-sweet-alert imagify-swal-has-subtitle imagify-before-bulk-infos',
718 showCancelButton: true,
719 padding: 0,
720 width: 554,
721 confirmButtonText: imagifyBulk.labels.confirmBulk,
722 cancelButtonText: imagifySwal.labels.cancelButtonText,
723 reverseButtons: true
724 } ).then( function() {
725 var $row = $( '.imagify-bulk-table [name="group[]"]:checked' ).first().closest( '.imagify-row-folder-type' );
726
727 $.get( w.imagify.bulk.getAjaxUrl( 'bulkInfoSeen', {
728 context: $row.data( 'context' )
729 } ) );
730
731 $infosModal.remove();
732
733 w.imagify.bulk.launchAllProcesses();
734 } ).catch( swal.noop );
735 },
736
737 /*
738 * Build the queue and launch all processes.
739 */
740 launchAllProcesses: function () {
741 var $w = $( w ),
742 $button = $( '#imagify-bulk-action' ),
743 skip = true;
744
745 // Disable the button.
746 $button.attr( 'disabled', 'disabled' ).find( '.dashicons' ).addClass( 'rotate' );
747
748 // Add a message to be displayed when the user wants to quit the page.
749 $w.on( 'beforeunload', this.getConfirmMessage );
750
751 // Hide the "Complete" message.
752 $( '.imagify-row-complete' ).imagifyHide( 200, function() {
753 $( this ).removeClass( 'done' );
754 } );
755
756 // Close the optimization details.
757 $( '.imagify-show-table-details' ).trigger( 'close.imagify' );
758
759 // Make sure to reset properties.
760 this.folderTypesQueue = [];
761 this.status = {};
762 this.displayedWaitMessage = false;
763 this.processIsStopped = false;
764 this.imagifyAction = 'optimize';
765 this.globalGain = 0;
766 this.globalOriginalSize = 0;
767 this.globalOptimizedSize = 0;
768
769 $( '.imagify-bulk-table [name="group[]"]:checked' ).each( function() {
770 var $checkbox = $( this ),
771 $row = $checkbox.closest( '.imagify-row-folder-type' ),
772 groupID = $row.data( 'group-id' ),
773 context = $row.data( 'context' ),
774 level = $row.find( '.imagify-cell-level [name="level[' + groupID + ']"]:checked' ).val();
775
776 // Build the queue.
777 w.imagify.bulk.folderTypesQueue.push( {
778 groupID: groupID,
779 context: context,
780 level: undefined === level ? -1 : parseInt( level, 10 )
781 } );
782
783 // Set the status.
784 w.imagify.bulk.status[ groupID ] = {
785 isError: false,
786 id: 'waiting'
787 };
788
789 // Display a "waiting" message + spinner into the folder rows.
790 if ( skip ) {
791 // No need to do that for the first one, we'll display a "working" row instead.
792 skip = false;
793 return true;
794 }
795
796 // Display the "waiting" folder row and hide the "normal" one.
797 w.imagify.bulk.displayFolderRow( 'waiting', $row );
798 } );
799
800 // Fasten Imagifybeat: 1 tick every 15 seconds, and disable suspend.
801 w.imagify.beat.interval( 15 );
802 w.imagify.beat.disableSuspend();
803
804 // Process the queue.
805 $w.trigger( 'processQueue.imagify' );
806 },
807
808 /*
809 * Process the first item in the queue.
810 */
811 processQueue: function () {
812 var $row, item;
813
814 if ( w.imagify.bulk.processIsStopped ) {
815 return;
816 }
817
818 if ( ! w.imagify.bulk.folderTypesQueue.length ) {
819 $( w ).trigger( 'queueEmpty.imagify' );
820 return;
821 }
822
823 if ( ! w.imagify.bulk.displayedWaitMessage ) {
824 // Display an alert to wait.
825 swal( {
826 title: imagifyBulk.labels.waitTitle,
827 html: imagifyBulk.labels.waitText,
828 showConfirmButton: false,
829 padding: 0,
830 imageUrl: imagifyBulk.waitImageUrl,
831 customClass: 'imagify-sweet-alert'
832 } ).catch( swal.noop );
833 w.imagify.bulk.displayedWaitMessage = true;
834 }
835
836 /**
837 * Fetch files for the first folder type in the queue.
838 */
839 item = w.imagify.bulk.folderTypesQueue.shift();
840 $row = $( '#cb-select-' + item.groupID ).closest( '.imagify-row-folder-type' );
841
842 // Update status.
843 w.imagify.bulk.status[ item.groupID ].id = 'fetching';
844
845 // Display the "working" folder row and hide the "normal" one.
846 w.imagify.bulk.displayFolderRow( 'working', $row );
847
848 // Fetch image IDs.
849 $.get( w.imagify.bulk.getAjaxUrl( 'getMediaIds', item ) )
850 .done( function( response ) {
851 var errorMessage;
852
853 swal.close();
854
855 if ( w.imagify.bulk.processIsStopped ) {
856 return;
857 }
858
859 if ( response.data && response.data.message ) {
860 errorMessage = response.data.message;
861 } else {
862 errorMessage = imagifyBulk.ajaxErrorText;
863 }
864
865 if ( ! response.success ) {
866 // Error.
867 w.imagify.bulk.stopProcess( errorMessage, item );
868 return;
869 }
870
871 if ( ! response.data || ! ( $.isPlainObject( response.data ) || $.isArray( response.data ) ) ) {
872 // Error: should be an array if empty, or an object otherwize.
873 w.imagify.bulk.stopProcess( errorMessage, item );
874 return;
875 }
876
877 // Success.
878 if ( ! $.isEmptyObject( response.data ) ) {
879 // Optimize the files.
880 $( w ).trigger( 'optimizeFiles.imagify', [ item, response.data ] );
881 return;
882 }
883
884 // No images.
885 w.imagify.bulk.status[ item.groupID ].id = 'no-images';
886
887 if ( w.imagify.bulk.hasMultipleRows ) {
888 $( '#cb-select-' + item.groupID ).prop( 'checked', false );
889 }
890
891 if ( ! w.imagify.bulk.folderTypesQueue.length ) {
892 $( w ).trigger( 'queueEmpty.imagify' );
893 return;
894 }
895
896 // Reset the folder row.
897 w.imagify.bulk.displayFolderRow( 'resting', $row );
898
899 $( w ).trigger( 'processQueue.imagify' );
900 } )
901 .fail( function() {
902 // Error.
903 w.imagify.bulk.stopProcess( 'get-unoptimized-images', item );
904 } );
905 },
906
907 /*
908 * Optimize files.
909 *
910 * @param {object} e jQuery's Event object.
911 * @param {object} item Current folder type (from the queue).
912 * @param {object} files A list of file IDs (keys, the IDs are prefixed by an underscore) and URLs (values).
913 */
914 optimizeFiles: function ( e, item, files ) {
915 var $row, $workingRow, $optimizedCount, $errorsCount, $table,
916 $progressBar, $progress, $resultsContainer,
917 optimizedCount, errorsCount, Optimizer,
918 defaultsTemplate = {
919 groupID: item.groupID,
920 mediaID: 0,
921 thumbnail: '', // Preview thumbnail src.
922 filename: '',
923 status: '',
924 icon: '',
925 label: '',
926 thumbnailsCount: '',
927 originalSizeHuman: '',
928 newSizeHuman: '',
929 percentHuman: '',
930 overallSavingHuman: ''
931 };
932
933 if ( w.imagify.bulk.processIsStopped ) {
934 return;
935 }
936
937 $row = $( '#cb-select-' + item.groupID ).closest( '.imagify-row-folder-type' );
938 $workingRow = $row.next( '.imagify-row-working' );
939 $errorsCount = $workingRow.find( '.imagify-cell-count-errors span' );
940 errorsCount = parseInt( $errorsCount.text(), 10 );
941 $table = $row.closest( '.imagify-bulk-table' );
942 $progressBar = $table.find( '.imagify-row-progress' );
943 $progress = $progressBar.find( '.bar' );
944
945 if ( 'optimize' === w.imagify.bulk.imagifyAction ) {
946 $optimizedCount = $workingRow.find( '.imagify-cell-count-optimized span' );
947 optimizedCount = parseInt( $optimizedCount.text(), 10 );
948 }
949
950 // Update folder status.
951 w.imagify.bulk.status[ item.groupID ].id = 'optimizing';
952
953 // Fill in the result table header.
954 $table.find( '.imagify-bulk-table-details thead' ).html( $( '#tmpl-imagify-file-header-' + item.groupID ).html() );
955
956 // Empty the result table body.
957 $resultsContainer = $table.find( '.imagify-bulk-table-details tbody' ).text( '' );
958
959 // Reset and display the progress bar.
960 $progress.css( 'width', '0%' ).find( '.percent' ).text( '0%' );
961 $progressBar.slideDown().attr( 'aria-hidden', 'false' );
962
963 // Optimize the files.
964 Optimizer = new w.imagify.Optimizer( {
965 groupID: item.groupID,
966 context: item.context,
967 level: item.level,
968 bufferSize: imagifyBulk.bufferSizes[ item.context ],
969 ajaxUrl: w.imagify.bulk.getAjaxUrl( 'bulkProcess', item ),
970 files: files,
971 defaultThumb: w.imagify.bulk.defaultThumb,
972 doneEvent: 'mediaProcessed.imagify'
973 } );
974
975 // Before each media optimization, add a file row displaying the optimization process.
976 Optimizer.before( function( data ) {
977 var template;
978
979 if ( w.imagify.bulk.processIsStopped ) {
980 return;
981 }
982
983 template = w.imagify.template( 'imagify-file-row-' + item.groupID );
984
985 w.imagify.bulk.processingMedia.push( {
986 context: item.context,
987 mediaID: data.mediaID
988 } );
989
990 $resultsContainer.prepend( template( $.extend( {}, defaultsTemplate, data, {
991 status: 'compressing',
992 icon: 'admin-generic rotate',
993 label: imagifyBulk.labels.optimizing
994 } ) ) );
995 } );
996
997 // After each media optimization.
998 Optimizer.each( function( data ) {
999 var template, $fileRow;
1000
1001 if ( w.imagify.bulk.processIsStopped ) {
1002 return;
1003 }
1004
1005 template = w.imagify.template( 'imagify-file-row-' + item.groupID );
1006 $fileRow = $( '#' + item.groupID + '-' + data.mediaID );
1007
1008 $.each( w.imagify.bulk.processingMedia, function( i, v ) {
1009 if ( v.context !== item.context || v.mediaID !== data.mediaID ) {
1010 return true;
1011 }
1012
1013 w.imagify.bulk.processingMedia.splice( i, 1 );
1014 return false;
1015 } );
1016
1017 // Update the progress bar.
1018 $progress.css( 'width', data.progress + '%' ).find( '.percent' ).html( data.progress + '%' );
1019
1020 if ( data.success ) {
1021 if ( 'already-optimized' !== data.status ) {
1022 // Image successfully optimized.
1023 $fileRow.replaceWith( template( $.extend( {}, defaultsTemplate, data, {
1024 status: 'complete',
1025 icon: 'yes',
1026 label: imagifyBulk.labels.complete
1027 } ) ) );
1028
1029 w.imagify.bulk.drawFileChart( $( '#' + item.groupID + '-' + data.mediaID ).find( '.imagify-cell-percentage canvas' ) ); // Don't use $fileRow, its DOM is not refreshed with the new values.
1030 } else {
1031 // The image was already optimized.
1032 $fileRow.replaceWith( w.imagify.bulk.displayErrorInRow( template( $.extend( {}, defaultsTemplate, data, {
1033 status: 'complete',
1034 icon: 'yes',
1035 label: imagifyBulk.labels.alreadyOptimized
1036 } ) ), data.error ) );
1037 }
1038
1039 // Update the optimized images counter.
1040 if ( 'optimize' === w.imagify.bulk.imagifyAction ) {
1041 optimizedCount += 1;
1042 $optimizedCount.text( optimizedCount );
1043 }
1044 return;
1045 }
1046
1047 // Display the error in the file row.
1048 $fileRow.replaceWith( w.imagify.bulk.displayErrorInRow( template( $.extend( {}, defaultsTemplate, data, {
1049 status: 'error',
1050 icon: 'dismiss',
1051 label: imagifyBulk.labels.error
1052 } ) ), data.error || data ) );
1053
1054 // Update the "working" folder row.
1055 if ( ! $errorsCount.length ) {
1056 errorsCount = 1;
1057 $errorsCount = $workingRow.find( '.imagify-cell-count-errors' ).html( imagifyBulk.labels.imagesErrorText.replace( '%s', '<span>1</span>' ) ).find( 'span' );
1058 } else {
1059 errorsCount += 1;
1060 $errorsCount.text( errorsCount );
1061 }
1062
1063 if ( 'over-quota' === data.status ) {
1064 // No more data, stop everything.
1065 Optimizer.stopProcess();
1066 w.imagify.bulk.stopProcess( data.status, item );
1067 }
1068 } );
1069
1070 // After all image optimizations.
1071 Optimizer.done( function( data ) {
1072 // Uncheck the checkbox.
1073 if ( w.imagify.bulk.hasMultipleRows ) {
1074 $( '#cb-select-' + item.groupID ).prop( 'checked', false );
1075 }
1076
1077 if ( data.globalOriginalSize ) {
1078 w.imagify.bulk.globalGain += parseInt( data.globalGain, 10 );
1079 w.imagify.bulk.globalOriginalSize += parseInt( data.globalOriginalSize, 10 );
1080 w.imagify.bulk.globalOptimizedSize += parseInt( data.globalOptimizedSize, 10 );
1081 }
1082
1083 if ( w.imagify.bulk.processIsStopped ) {
1084 return;
1085 }
1086
1087 // Reset Imagifybeat interval and enable suspend.
1088 w.imagify.beat.resetInterval();
1089 w.imagify.beat.enableSuspend();
1090
1091 // Update folder type status.
1092 if ( ! $.isEmptyObject( w.imagify.bulk.status ) && ! w.imagify.bulk.status[ item.groupID ].isError ) {
1093 w.imagify.bulk.status[ item.groupID ].id = 'done';
1094 }
1095
1096 // Update the folder row.
1097 $row.addClass( 'updating' );
1098
1099 $.get( w.imagify.bulk.getAjaxUrl( 'getFolderData', item ) )
1100 .done( function( response ) {
1101 if ( w.imagify.bulk.processIsStopped ) {
1102 return;
1103 }
1104
1105 if ( response.success ) {
1106 $.each( response.data, function( dataName, dataHtml ) {
1107 $row.children( '.imagify-cell-' + dataName ).html( dataHtml );
1108 } );
1109 }
1110
1111 w.imagify.bulk.displayFolderRow( 'resting', $row );
1112 } )
1113 .always( function() {
1114 if ( w.imagify.bulk.processIsStopped ) {
1115 return;
1116 }
1117
1118 $row.removeClass( 'updating' );
1119
1120 if ( ! w.imagify.bulk.folderTypesQueue.length ) {
1121 $( w ).trigger( 'queueEmpty.imagify' );
1122 } else {
1123 $( w ).trigger( 'processQueue.imagify' );
1124 }
1125 } );
1126 } );
1127
1128 // Run.
1129 Optimizer.run();
1130 },
1131
1132 /*
1133 * End.
1134 */
1135 queueEmpty: function () {
1136 var $tables = $( '.imagify-bulk-table' ),
1137 errorArgs = {},
1138 hasError = false,
1139 noImages = true,
1140 errorMsg = '';
1141
1142 // Reset Imagifybeat interval and enable suspend.
1143 w.imagify.beat.resetInterval();
1144 w.imagify.beat.enableSuspend();
1145
1146 // Display the share box.
1147 w.imagify.bulk.displayShareBox();
1148
1149 // Reset the queue.
1150 w.imagify.bulk.folderTypesQueue = [];
1151
1152 // Fetch and display generic stats if stats via Imagifybeat are disabled.
1153 if ( ! imagifyBulk.imagifybeatIDs.stats ) {
1154 $.get( w.imagify.bulk.getAjaxUrl( 'getStats' ), {
1155 types: w.imagify.bulk.getFolderTypes()
1156 } )
1157 .done( function( response ) {
1158 if ( response.success ) {
1159 w.imagify.bulk.updateStats( response.data );
1160 }
1161 } );
1162 }
1163
1164 // Maybe display error.
1165 if ( ! $.isEmptyObject( w.imagify.bulk.status ) ) {
1166 $.each( w.imagify.bulk.status, function( groupID, typeStatus ) {
1167 if ( typeStatus.isError ) {
1168 // One error is enough to display a message.
1169 hasError = typeStatus.id;
1170 noImages = false;
1171 return false;
1172 }
1173 if ( 'no-images' !== typeStatus.id ) {
1174 // All groups must have this ID.
1175 noImages = false;
1176 return false;
1177 }
1178 } );
1179
1180 if ( hasError ) {
1181 if ( 'invalid-api-key' === hasError ) {
1182 errorArgs = {
1183 title: imagifyBulk.labels.invalidAPIKeyTitle,
1184 type: 'info'
1185 };
1186 }
1187 else if ( 'over-quota' === hasError ) {
1188 errorArgs = {
1189 title: imagifyBulk.labels.overQuotaTitle,
1190 html: $( '#tmpl-imagify-overquota-alert' ).html(),
1191 type: 'info',
1192 customClass: 'imagify-swal-has-subtitle imagify-swal-error-header',
1193 showConfirmButton: false
1194 };
1195 }
1196 else if ( 'get-unoptimized-images' === hasError || 'consumed-all-data' === hasError ) {
1197 errorArgs = {
1198 title: imagifyBulk.labels.getUnoptimizedImagesErrorTitle,
1199 html: imagifyBulk.labels.getUnoptimizedImagesErrorText,
1200 type: 'info'
1201 };
1202 }
1203 w.imagify.bulk.displayError( errorArgs );
1204 }
1205 else if ( noImages ) {
1206 if ( imagifyBulk.labels.nothingToDoText.hasOwnProperty( w.imagify.bulk.imagifyAction ) ) {
1207 errorMsg = imagifyBulk.labels.nothingToDoText[ w.imagify.bulk.imagifyAction ];
1208 } else {
1209 errorMsg = imagifyBulk.labels.nothingToDoText.optimize;
1210 }
1211 w.imagify.bulk.displayError( {
1212 title: imagifyBulk.labels.nothingToDoTitle,
1213 html: errorMsg,
1214 type: 'info'
1215 } );
1216 }
1217 }
1218
1219 // Reset status.
1220 w.imagify.bulk.status = {};
1221
1222 // Unlink the message displayed when the user wants to quit the page.
1223 $( w ).off( 'beforeunload', w.imagify.bulk.getConfirmMessage );
1224
1225 // Display the "normal" folder rows (the values of the last one should being updated via ajax, don't display it for now).
1226 w.imagify.bulk.displayFolderRow( 'resting', $tables.find( '.imagify-row-folder-type' ).not( '.updating' ) );
1227
1228 // Reset the progress bars.
1229 $tables.find( '.imagify-row-progress' ).slideUp().attr( 'aria-hidden', 'true' ).find( '.bar' ).removeAttr( 'style' ).find( '.percent' ).text( '0%' );
1230
1231 // Enable (or not) the main button.
1232 if ( $( '.imagify-bulk-table [name="group[]"]:checked' ).length ) {
1233 $( '#imagify-bulk-action' ).removeAttr( 'disabled' ).find( '.dashicons' ).removeClass( 'rotate' );
1234 } else {
1235 $( '#imagify-bulk-action' ).find( '.dashicons' ).removeClass( 'rotate' );
1236 }
1237 },
1238
1239 /**
1240 * Open a popup window when the user clicks on a share link.
1241 *
1242 * @param {object} e jQuery Event object.
1243 */
1244 share: function ( e ) {
1245 var width = 700,
1246 height = 290,
1247 clientLeft, clientTop;
1248
1249 e.preventDefault();
1250
1251 if ( w.innerWidth ) {
1252 clientLeft = ( w.innerWidth - width ) / 2;
1253 clientTop = ( w.innerHeight - height ) / 2;
1254 } else {
1255 clientLeft = ( d.body.clientWidth - width ) / 2;
1256 clientTop = ( d.body.clientHeight - height ) / 2;
1257 }
1258
1259 w.open( this.href, '', 'status=no, scrollbars=no, menubar=no, top=' + clientTop + ', left=' + clientLeft + ', width=' + width + ', height=' + height );
1260 },
1261
1262 // Imagifybeat =============================================================================
1263
1264 /**
1265 * Add a Imagifybeat ID for global stats on "imagifybeat-send" event.
1266 *
1267 * @param {object} e Event object.
1268 * @param {object} data Object containing all Imagifybeat IDs.
1269 */
1270 addStatsImagifybeat: function ( e, data ) {
1271 data[ imagifyBulk.imagifybeatIDs.stats ] = Object.keys( w.imagify.bulk.getFolderTypes() );
1272 },
1273
1274 /**
1275 * Listen for the custom event "imagifybeat-tick" on $(document).
1276 * It allows to update various data periodically.
1277 *
1278 * @param {object} e Event object.
1279 * @param {object} data Object containing all Imagifybeat IDs.
1280 */
1281 processStatsImagifybeat: function ( e, data ) {
1282 if ( typeof data[ imagifyBulk.imagifybeatIDs.stats ] !== 'undefined' ) {
1283 w.imagify.bulk.updateStats( data[ imagifyBulk.imagifybeatIDs.stats ] );
1284 }
1285 },
1286
1287 /**
1288 * Add a Imagifybeat ID on "imagifybeat-send" event to sync the optimization queue.
1289 *
1290 * @param {object} e Event object.
1291 * @param {object} data Object containing all Imagifybeat IDs.
1292 */
1293 addQueueImagifybeat: function ( e, data ) {
1294 if ( w.imagify.bulk.processingMedia.length ) {
1295 data[ imagifyBulk.imagifybeatIDs.queue ] = w.imagify.bulk.processingMedia;
1296 }
1297 },
1298
1299 /**
1300 * Listen for the custom event "imagifybeat-tick" on $(document).
1301 * It allows to update various data periodically.
1302 *
1303 * @param {object} e Event object.
1304 * @param {object} data Object containing all Imagifybeat IDs.
1305 */
1306 processQueueImagifybeat: function ( e, data ) {
1307 if ( typeof data[ imagifyBulk.imagifybeatIDs.queue ] !== 'undefined' ) {
1308 $.each( data[ imagifyBulk.imagifybeatIDs.queue ], function ( i, mediaData ) {
1309 $( w ).trigger( 'mediaProcessed.imagify', [ mediaData ] );
1310 } );
1311 }
1312 },
1313
1314 /**
1315 * Add a Imagifybeat ID for requirements on "imagifybeat-send" event.
1316 *
1317 * @param {object} e Event object.
1318 * @param {object} data Object containing all Imagifybeat IDs.
1319 */
1320 addRequirementsImagifybeat: function ( e, data ) {
1321 data[ imagifyBulk.imagifybeatIDs.requirements ] = 1;
1322 },
1323
1324 /**
1325 * Listen for the custom event "imagifybeat-tick" on $(document).
1326 * It allows to update requirements status periodically.
1327 *
1328 * @param {object} e Event object.
1329 * @param {object} data Object containing all Imagifybeat IDs.
1330 */
1331 processRequirementsImagifybeat: function ( e, data ) {
1332 if ( typeof data[ imagifyBulk.imagifybeatIDs.requirements ] === 'undefined' ) {
1333 return;
1334 }
1335
1336 data = data[ imagifyBulk.imagifybeatIDs.requirements ];
1337
1338 imagifyBulk.curlMissing = data.curl_missing;
1339 imagifyBulk.editorMissing = data.editor_missing;
1340 imagifyBulk.extHttpBlocked = data.external_http_blocked;
1341 imagifyBulk.apiDown = data.api_down;
1342 imagifyBulk.keyIsValid = data.key_is_valid;
1343 imagifyBulk.isOverQuota = data.is_over_quota;
1344 },
1345
1346 // Charts ==================================================================================
1347
1348 /**
1349 * Overview chart.
1350 * Used for the big overview chart.
1351 */
1352 drawOverviewChart: function ( data ) {
1353 var initData, legend;
1354
1355 if ( ! this.charts.overview.canvas ) {
1356 this.charts.overview.canvas = d.getElementById( 'imagify-overview-chart' );
1357
1358 if ( ! this.charts.overview.canvas ) {
1359 return;
1360 }
1361 }
1362
1363 data = data && $.isArray( data ) ? data : [];
1364
1365 if ( this.charts.overview.donut ) {
1366 // Update existing donut.
1367 if ( data.length ) {
1368 if ( data.reduce( function( a, b ) { return a + b; }, 0 ) === 0 ) {
1369 data[0] = 1;
1370 }
1371
1372 this.charts.overview.donut.data.datasets[0].data = data;
1373 this.charts.overview.donut.update();
1374 }
1375 return;
1376 }
1377
1378 // Create new donut.
1379 this.charts.overview.data.datasets[0].data = [
1380 parseInt( this.charts.overview.canvas.getAttribute( 'data-unoptimized' ), 10 ),
1381 parseInt( this.charts.overview.canvas.getAttribute( 'data-optimized' ), 10 ),
1382 parseInt( this.charts.overview.canvas.getAttribute( 'data-errors' ), 10 )
1383 ];
1384 initData = $.extend( {}, this.charts.overview.data );
1385
1386 if ( data.length ) {
1387 initData.datasets[0].data = data;
1388 }
1389
1390 if ( initData.datasets[0].data.reduce( function( a, b ) { return a + b; }, 0 ) === 0 ) {
1391 initData.datasets[0].data[0] = 1;
1392 }
1393
1394 this.charts.overview.donut = new w.imagify.Chart( this.charts.overview.canvas, {
1395 type: 'doughnut',
1396 data: initData,
1397 options: {
1398 legend: {
1399 display: false
1400 },
1401 events: [],
1402 animation: {
1403 easing: 'easeOutBounce'
1404 },
1405 tooltips: {
1406 displayColors: false,
1407 callbacks: {
1408 label: function( tooltipItem, localData ) {
1409 return localData.datasets[ tooltipItem.datasetIndex ].data[ tooltipItem.index ];
1410 }
1411 }
1412 },
1413 responsive: false,
1414 cutoutPercentage: 85
1415 }
1416 } );
1417
1418 // Then generate the legend and insert it to your page somewhere.
1419 legend = '<ul class="imagify-doughnut-legend">';
1420
1421 $.each( initData.labels, function( i, label ) {
1422 legend += '<li><span style="background-color:' + initData.datasets[0].backgroundColor[ i ] + '"></span>' + label + '</li>';
1423 } );
1424
1425 legend += '</ul>';
1426
1427 d.getElementById( 'imagify-overview-chart-legend' ).innerHTML = legend;
1428 },
1429
1430 /**
1431 * Mini chart.
1432 * Used for the charts on each file row.
1433 *
1434 * @param {element} canvas A jQuery canvas element.
1435 */
1436 drawFileChart: function ( canvas ) {
1437 var donuts = this.charts.files.donuts;
1438
1439 canvas.each( function() {
1440 var value = parseInt( $( this ).closest( '.imagify-chart' ).next( '.imagipercent' ).text().replace( '%', '' ), 10 );
1441
1442 if ( undefined !== donuts[ this.id ] ) {
1443 // Update existing donut.
1444 donuts[ this.id ].data.datasets[0].data[0] = value;
1445 donuts[ this.id ].data.datasets[0].data[1] = 100 - value;
1446 donuts[ this.id ].update();
1447 return;
1448 }
1449
1450 // Create new donut.
1451 donuts[ this.id ] = new w.imagify.Chart( this, {
1452 type: 'doughnut',
1453 data: {
1454 datasets: [{
1455 data: [ value, 100 - value ],
1456 backgroundColor: [ '#00B3D3', '#D8D8D8' ],
1457 borderColor: '#fff',
1458 borderWidth: 1
1459 }]
1460 },
1461 options: {
1462 legend: {
1463 display: false
1464 },
1465 events: [],
1466 animation: {
1467 easing: 'easeOutBounce'
1468 },
1469 tooltips: {
1470 enabled: false
1471 },
1472 responsive: false
1473 }
1474 } );
1475 } );
1476
1477 this.charts.files.donuts = donuts;
1478 },
1479
1480 /*
1481 * Share Chart.
1482 * Used for the chart in the share box.
1483 */
1484 drawShareChart: function () {
1485 var value;
1486
1487 if ( ! this.charts.share.canvas ) {
1488 this.charts.share.canvas = d.getElementById( 'imagify-ac-chart' );
1489
1490 if ( ! this.charts.share.canvas ) {
1491 return;
1492 }
1493 }
1494
1495 value = parseInt( $( this.charts.share.canvas ).closest( '.imagify-ac-chart' ).attr( 'data-percent' ), 10 );
1496
1497 if ( this.charts.share.donut ) {
1498 // Update existing donut.
1499 this.charts.share.donut.data.datasets[0].data[0] = value;
1500 this.charts.share.donut.data.datasets[0].data[1] = 100 - value;
1501 this.charts.share.donut.update();
1502 return;
1503 }
1504
1505 // Create new donut.
1506 this.charts.share.donut = new w.imagify.Chart( this.charts.share.canvas, {
1507 type: 'doughnut',
1508 data: {
1509 datasets: [{
1510 data: [ value, 100 - value ],
1511 backgroundColor: [ '#40B1D0', '#FFFFFF' ],
1512 borderWidth: 0
1513 }]
1514 },
1515 options: {
1516 legend: {
1517 display: false
1518 },
1519 events: [],
1520 animation: {
1521 easing: 'easeOutBounce'
1522 },
1523 tooltips: {
1524 enabled: false
1525 },
1526 responsive: false,
1527 cutoutPercentage: 70
1528 }
1529 } );
1530 }
1531 };
1532
1533 w.imagify.bulk.init();
1534
1535 } )(jQuery, document, window);
1536