| 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 + '&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 |
|