[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 /** 2 * The functions necessary for editing images. 3 * 4 * @since 2.9.0 5 * @output wp-admin/js/image-edit.js 6 */ 7 8 /* global ajaxurl, confirm */ 9 10 (function($) { 11 var __ = wp.i18n.__; 12 13 /** 14 * Contains all the methods to initialise and control the image editor. 15 * 16 * @namespace imageEdit 17 */ 18 var imageEdit = window.imageEdit = { 19 iasapi : {}, 20 hold : {}, 21 postid : '', 22 _view : false, 23 24 /** 25 * Handle crop tool clicks. 26 */ 27 handleCropToolClick: function( postid, nonce, cropButton ) { 28 var img = $( '#image-preview-' + postid ), 29 selection = this.iasapi.getSelection(); 30 31 // Ensure selection is available, otherwise reset to full image. 32 if ( isNaN( selection.x1 ) ) { 33 this.setCropSelection( postid, { 'x1': 0, 'y1': 0, 'x2': img.innerWidth(), 'y2': img.innerHeight(), 'width': img.innerWidth(), 'height': img.innerHeight() } ); 34 selection = this.iasapi.getSelection(); 35 } 36 37 // If we don't already have a selection, select the entire image. 38 if ( 0 === selection.x1 && 0 === selection.y1 && 0 === selection.x2 && 0 === selection.y2 ) { 39 this.iasapi.setSelection( 0, 0, img.innerWidth(), img.innerHeight(), true ); 40 this.iasapi.setOptions( { show: true } ); 41 this.iasapi.update(); 42 } else { 43 44 // Otherwise, perform the crop. 45 imageEdit.crop( postid, nonce , cropButton ); 46 } 47 }, 48 49 /** 50 * Converts a value to an integer. 51 * 52 * @since 2.9.0 53 * 54 * @memberof imageEdit 55 * 56 * @param {number} f The float value that should be converted. 57 * 58 * @return {number} The integer representation from the float value. 59 */ 60 intval : function(f) { 61 /* 62 * Bitwise OR operator: one of the obscure ways to truncate floating point figures, 63 * worth reminding JavaScript doesn't have a distinct "integer" type. 64 */ 65 return f | 0; 66 }, 67 68 /** 69 * Adds the disabled attribute and class to a single form element or a field set. 70 * 71 * @since 2.9.0 72 * 73 * @memberof imageEdit 74 * 75 * @param {jQuery} el The element that should be modified. 76 * @param {boolean|number} s The state for the element. If set to true 77 * the element is disabled, 78 * otherwise the element is enabled. 79 * The function is sometimes called with a 0 or 1 80 * instead of true or false. 81 * 82 * @return {void} 83 */ 84 setDisabled : function( el, s ) { 85 /* 86 * `el` can be a single form element or a fieldset. Before #28864, the disabled state on 87 * some text fields was handled targeting $('input', el). Now we need to handle the 88 * disabled state on buttons too so we can just target `el` regardless if it's a single 89 * element or a fieldset because when a fieldset is disabled, its descendants are disabled too. 90 */ 91 if ( s ) { 92 el.removeClass( 'disabled' ).prop( 'disabled', false ); 93 } else { 94 el.addClass( 'disabled' ).prop( 'disabled', true ); 95 } 96 }, 97 98 /** 99 * Initializes the image editor. 100 * 101 * @since 2.9.0 102 * 103 * @memberof imageEdit 104 * 105 * @param {number} postid The post ID. 106 * 107 * @return {void} 108 */ 109 init : function(postid) { 110 var t = this, old = $('#image-editor-' + t.postid), 111 x = t.intval( $('#imgedit-x-' + postid).val() ), 112 y = t.intval( $('#imgedit-y-' + postid).val() ); 113 114 if ( t.postid !== postid && old.length ) { 115 t.close(t.postid); 116 } 117 118 t.hold.w = t.hold.ow = x; 119 t.hold.h = t.hold.oh = y; 120 t.hold.xy_ratio = x / y; 121 t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() ); 122 t.postid = postid; 123 $('#imgedit-response-' + postid).empty(); 124 125 $('#imgedit-panel-' + postid).on( 'keypress', 'input[type="text"]', function(e) { 126 var k = e.keyCode; 127 128 // Key codes 37 through 40 are the arrow keys. 129 if ( 36 < k && k < 41 ) { 130 $(this).trigger( 'blur' ); 131 } 132 133 // The key code 13 is the Enter key. 134 if ( 13 === k ) { 135 e.preventDefault(); 136 e.stopPropagation(); 137 return false; 138 } 139 }); 140 141 $( document ).on( 'image-editor-ui-ready', this.focusManager ); 142 }, 143 144 /** 145 * Toggles the wait/load icon in the editor. 146 * 147 * @since 2.9.0 148 * @since 5.5.0 Added the triggerUIReady parameter. 149 * 150 * @memberof imageEdit 151 * 152 * @param {number} postid The post ID. 153 * @param {number} toggle Is 0 or 1, fades the icon in when 1 and out when 0. 154 * @param {boolean} triggerUIReady Whether to trigger a custom event when the UI is ready. Default false. 155 * 156 * @return {void} 157 */ 158 toggleEditor: function( postid, toggle, triggerUIReady ) { 159 var wait = $('#imgedit-wait-' + postid); 160 161 if ( toggle ) { 162 wait.fadeIn( 'fast' ); 163 } else { 164 wait.fadeOut( 'fast', function() { 165 if ( triggerUIReady ) { 166 $( document ).trigger( 'image-editor-ui-ready' ); 167 } 168 } ); 169 } 170 }, 171 172 /** 173 * Shows or hides the image edit help box. 174 * 175 * @since 2.9.0 176 * 177 * @memberof imageEdit 178 * 179 * @param {HTMLElement} el The element to create the help window in. 180 * 181 * @return {boolean} Always returns false. 182 */ 183 toggleHelp : function(el) { 184 var $el = $( el ); 185 $el 186 .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' ) 187 .parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' ); 188 189 return false; 190 }, 191 192 /** 193 * Gets the value from the image edit target. 194 * 195 * The image edit target contains the image sizes where the (possible) changes 196 * have to be applied to. 197 * 198 * @since 2.9.0 199 * 200 * @memberof imageEdit 201 * 202 * @param {number} postid The post ID. 203 * 204 * @return {string} The value from the imagedit-save-target input field when available, 205 * or 'full' when not available. 206 */ 207 getTarget : function(postid) { 208 return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full'; 209 }, 210 211 /** 212 * Recalculates the height or width and keeps the original aspect ratio. 213 * 214 * If the original image size is exceeded a red exclamation mark is shown. 215 * 216 * @since 2.9.0 217 * 218 * @memberof imageEdit 219 * 220 * @param {number} postid The current post ID. 221 * @param {number} x Is 0 when it applies the y-axis 222 * and 1 when applicable for the x-axis. 223 * @param {jQuery} el Element. 224 * 225 * @return {void} 226 */ 227 scaleChanged : function( postid, x, el ) { 228 var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid), 229 warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = ''; 230 231 if ( false === this.validateNumeric( el ) ) { 232 return; 233 } 234 235 if ( x ) { 236 h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : ''; 237 h.val( h1 ); 238 } else { 239 w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : ''; 240 w.val( w1 ); 241 } 242 243 if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) { 244 warn.css('visibility', 'visible'); 245 } else { 246 warn.css('visibility', 'hidden'); 247 } 248 }, 249 250 /** 251 * Gets the selected aspect ratio. 252 * 253 * @since 2.9.0 254 * 255 * @memberof imageEdit 256 * 257 * @param {number} postid The post ID. 258 * 259 * @return {string} The aspect ratio. 260 */ 261 getSelRatio : function(postid) { 262 var x = this.hold.w, y = this.hold.h, 263 X = this.intval( $('#imgedit-crop-width-' + postid).val() ), 264 Y = this.intval( $('#imgedit-crop-height-' + postid).val() ); 265 266 if ( X && Y ) { 267 return X + ':' + Y; 268 } 269 270 if ( x && y ) { 271 return x + ':' + y; 272 } 273 274 return '1:1'; 275 }, 276 277 /** 278 * Removes the last action from the image edit history. 279 * The history consist of (edit) actions performed on the image. 280 * 281 * @since 2.9.0 282 * 283 * @memberof imageEdit 284 * 285 * @param {number} postid The post ID. 286 * @param {number} setSize 0 or 1, when 1 the image resets to its original size. 287 * 288 * @return {string} JSON string containing the history or an empty string if no history exists. 289 */ 290 filterHistory : function(postid, setSize) { 291 // Apply undo state to history. 292 var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = []; 293 294 if ( history !== '' ) { 295 // Read the JSON string with the image edit history. 296 history = JSON.parse(history); 297 pop = this.intval( $('#imgedit-undone-' + postid).val() ); 298 if ( pop > 0 ) { 299 while ( pop > 0 ) { 300 history.pop(); 301 pop--; 302 } 303 } 304 305 // Reset size to its original state. 306 if ( setSize ) { 307 if ( !history.length ) { 308 this.hold.w = this.hold.ow; 309 this.hold.h = this.hold.oh; 310 return ''; 311 } 312 313 // Restore original 'o'. 314 o = history[history.length - 1]; 315 316 // c = 'crop', r = 'rotate', f = 'flip'. 317 o = o.c || o.r || o.f || false; 318 319 if ( o ) { 320 // fw = Full image width. 321 this.hold.w = o.fw; 322 // fh = Full image height. 323 this.hold.h = o.fh; 324 } 325 } 326 327 // Filter the last step/action from the history. 328 for ( n in history ) { 329 i = history[n]; 330 if ( i.hasOwnProperty('c') ) { 331 op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } }; 332 } else if ( i.hasOwnProperty('r') ) { 333 op[n] = { 'r': i.r.r }; 334 } else if ( i.hasOwnProperty('f') ) { 335 op[n] = { 'f': i.f.f }; 336 } 337 } 338 return JSON.stringify(op); 339 } 340 return ''; 341 }, 342 /** 343 * Binds the necessary events to the image. 344 * 345 * When the image source is reloaded the image will be reloaded. 346 * 347 * @since 2.9.0 348 * 349 * @memberof imageEdit 350 * 351 * @param {number} postid The post ID. 352 * @param {string} nonce The nonce to verify the request. 353 * @param {function} callback Function to execute when the image is loaded. 354 * 355 * @return {void} 356 */ 357 refreshEditor : function(postid, nonce, callback) { 358 var t = this, data, img; 359 360 t.toggleEditor(postid, 1); 361 data = { 362 'action': 'imgedit-preview', 363 '_ajax_nonce': nonce, 364 'postid': postid, 365 'history': t.filterHistory(postid, 1), 366 'rand': t.intval(Math.random() * 1000000) 367 }; 368 369 img = $( '<img id="image-preview-' + postid + '" alt="" />' ) 370 .on( 'load', { history: data.history }, function( event ) { 371 var max1, max2, 372 parent = $( '#imgedit-crop-' + postid ), 373 t = imageEdit, 374 historyObj; 375 376 // Checks if there already is some image-edit history. 377 if ( '' !== event.data.history ) { 378 historyObj = JSON.parse( event.data.history ); 379 // If last executed action in history is a crop action. 380 if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) { 381 /* 382 * A crop action has completed and the crop button gets disabled 383 * ensure the undo button is enabled. 384 */ 385 t.setDisabled( $( '#image-undo-' + postid) , true ); 386 // Move focus to the undo button to avoid a focus loss. 387 $( '#image-undo-' + postid ).trigger( 'focus' ); 388 } 389 } 390 391 parent.empty().append(img); 392 393 // w, h are the new full size dimensions. 394 max1 = Math.max( t.hold.w, t.hold.h ); 395 max2 = Math.max( $(img).width(), $(img).height() ); 396 t.hold.sizer = max1 > max2 ? max2 / max1 : 1; 397 398 t.initCrop(postid, img, parent); 399 400 if ( (typeof callback !== 'undefined') && callback !== null ) { 401 callback(); 402 } 403 404 if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) { 405 $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', false); 406 } else { 407 $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true); 408 } 409 410 t.toggleEditor(postid, 0); 411 }) 412 .on( 'error', function() { 413 var errorMessage = __( 'Could not load the preview image. Please reload the page and try again.' ); 414 415 $( '#imgedit-crop-' + postid ) 416 .empty() 417 .append( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' ); 418 419 t.toggleEditor( postid, 0, true ); 420 wp.a11y.speak( errorMessage, 'assertive' ); 421 } ) 422 .attr('src', ajaxurl + '?' + $.param(data)); 423 }, 424 /** 425 * Performs an image edit action. 426 * 427 * @since 2.9.0 428 * 429 * @memberof imageEdit 430 * 431 * @param {number} postid The post ID. 432 * @param {string} nonce The nonce to verify the request. 433 * @param {string} action The action to perform on the image. 434 * The possible actions are: "scale" and "restore". 435 * 436 * @return {boolean|void} Executes a post request that refreshes the page 437 * when the action is performed. 438 * Returns false if a invalid action is given, 439 * or when the action cannot be performed. 440 */ 441 action : function(postid, nonce, action) { 442 var t = this, data, w, h, fw, fh; 443 444 if ( t.notsaved(postid) ) { 445 return false; 446 } 447 448 data = { 449 'action': 'image-editor', 450 '_ajax_nonce': nonce, 451 'postid': postid 452 }; 453 454 if ( 'scale' === action ) { 455 w = $('#imgedit-scale-width-' + postid), 456 h = $('#imgedit-scale-height-' + postid), 457 fw = t.intval(w.val()), 458 fh = t.intval(h.val()); 459 460 if ( fw < 1 ) { 461 w.trigger( 'focus' ); 462 return false; 463 } else if ( fh < 1 ) { 464 h.trigger( 'focus' ); 465 return false; 466 } 467 468 if ( fw === t.hold.ow || fh === t.hold.oh ) { 469 return false; 470 } 471 472 data['do'] = 'scale'; 473 data.fwidth = fw; 474 data.fheight = fh; 475 } else if ( 'restore' === action ) { 476 data['do'] = 'restore'; 477 } else { 478 return false; 479 } 480 481 t.toggleEditor(postid, 1); 482 $.post( ajaxurl, data, function( response ) { 483 $( '#image-editor-' + postid ).empty().append( response.data.html ); 484 t.toggleEditor( postid, 0, true ); 485 // Refresh the attachment model so that changes propagate. 486 if ( t._view ) { 487 t._view.refresh(); 488 } 489 } ).done( function( response ) { 490 // Whether the executed action was `scale` or `restore`, the response does have a message. 491 if ( response && response.data.message.msg ) { 492 wp.a11y.speak( response.data.message.msg ); 493 return; 494 } 495 496 if ( response && response.data.message.error ) { 497 wp.a11y.speak( response.data.message.error ); 498 } 499 } ); 500 }, 501 502 /** 503 * Stores the changes that are made to the image. 504 * 505 * @since 2.9.0 506 * 507 * @memberof imageEdit 508 * 509 * @param {number} postid The post ID to get the image from the database. 510 * @param {string} nonce The nonce to verify the request. 511 * 512 * @return {boolean|void} If the actions are successfully saved a response message is shown. 513 * Returns false if there is no image editing history, 514 * thus there are not edit-actions performed on the image. 515 */ 516 save : function(postid, nonce) { 517 var data, 518 target = this.getTarget(postid), 519 history = this.filterHistory(postid, 0), 520 self = this; 521 522 if ( '' === history ) { 523 return false; 524 } 525 526 this.toggleEditor(postid, 1); 527 data = { 528 'action': 'image-editor', 529 '_ajax_nonce': nonce, 530 'postid': postid, 531 'history': history, 532 'target': target, 533 'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null, 534 'do': 'save' 535 }; 536 // Post the image edit data to the backend. 537 $.post( ajaxurl, data, function( response ) { 538 // If a response is returned, close the editor and show an error. 539 if ( response.data.error ) { 540 $( '#imgedit-response-' + postid ) 541 .html( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + response.data.error + '</p></div>' ); 542 543 imageEdit.close(postid); 544 wp.a11y.speak( response.data.error ); 545 return; 546 } 547 548 if ( response.data.fw && response.data.fh ) { 549 $( '#media-dims-' + postid ).html( response.data.fw + ' × ' + response.data.fh ); 550 } 551 552 if ( response.data.thumbnail ) { 553 $( '.thumbnail', '#thumbnail-head-' + postid ).attr( 'src', '' + response.data.thumbnail ); 554 } 555 556 if ( response.data.msg ) { 557 $( '#imgedit-response-' + postid ) 558 .html( '<div class="notice notice-success" tabindex="-1" role="alert"><p>' + response.data.msg + '</p></div>' ); 559 560 wp.a11y.speak( response.data.msg ); 561 } 562 563 if ( self._view ) { 564 self._view.save(); 565 } else { 566 imageEdit.close(postid); 567 } 568 }); 569 }, 570 571 /** 572 * Creates the image edit window. 573 * 574 * @since 2.9.0 575 * 576 * @memberof imageEdit 577 * 578 * @param {number} postid The post ID for the image. 579 * @param {string} nonce The nonce to verify the request. 580 * @param {Object} view The image editor view to be used for the editing. 581 * 582 * @return {void|promise} Either returns void if the button was already activated 583 * or returns an instance of the image editor, wrapped in a promise. 584 */ 585 open : function( postid, nonce, view ) { 586 this._view = view; 587 588 var dfd, data, 589 elem = $( '#image-editor-' + postid ), 590 head = $( '#media-head-' + postid ), 591 btn = $( '#imgedit-open-btn-' + postid ), 592 spin = btn.siblings( '.spinner' ); 593 594 /* 595 * Instead of disabling the button, which causes a focus loss and makes screen 596 * readers announce "unavailable", return if the button was already clicked. 597 */ 598 if ( btn.hasClass( 'button-activated' ) ) { 599 return; 600 } 601 602 spin.addClass( 'is-active' ); 603 604 data = { 605 'action': 'image-editor', 606 '_ajax_nonce': nonce, 607 'postid': postid, 608 'do': 'open' 609 }; 610 611 dfd = $.ajax( { 612 url: ajaxurl, 613 type: 'post', 614 data: data, 615 beforeSend: function() { 616 btn.addClass( 'button-activated' ); 617 } 618 } ).done( function( response ) { 619 var errorMessage; 620 621 if ( '-1' === response ) { 622 errorMessage = __( 'Could not load the preview image.' ); 623 elem.html( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' ); 624 } 625 626 if ( response.data && response.data.html ) { 627 elem.html( response.data.html ); 628 } 629 630 head.fadeOut( 'fast', function() { 631 elem.fadeIn( 'fast', function() { 632 if ( errorMessage ) { 633 $( document ).trigger( 'image-editor-ui-ready' ); 634 } 635 } ); 636 btn.removeClass( 'button-activated' ); 637 spin.removeClass( 'is-active' ); 638 } ); 639 // Initialise the Image Editor now that everything is ready. 640 imageEdit.init( postid ); 641 } ); 642 643 return dfd; 644 }, 645 646 /** 647 * Initializes the cropping tool and sets a default cropping selection. 648 * 649 * @since 2.9.0 650 * 651 * @memberof imageEdit 652 * 653 * @param {number} postid The post ID. 654 * 655 * @return {void} 656 */ 657 imgLoaded : function(postid) { 658 var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid); 659 660 // Ensure init has run even when directly loaded. 661 if ( 'undefined' === typeof this.hold.sizer ) { 662 this.init( postid ); 663 } 664 665 this.initCrop(postid, img, parent); 666 this.setCropSelection( postid, { 'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0, 'width': img.innerWidth(), 'height': img.innerHeight() } ); 667 668 this.toggleEditor( postid, 0, true ); 669 }, 670 671 /** 672 * Manages keyboard focus in the Image Editor user interface. 673 * 674 * @since 5.5.0 675 * 676 * @return {void} 677 */ 678 focusManager: function() { 679 /* 680 * Editor is ready. Move focus to one of the admin alert notices displayed 681 * after a user action or to the first focusable element. Since the DOM 682 * update is pretty large, the timeout helps browsers update their 683 * accessibility tree to better support assistive technologies. 684 */ 685 setTimeout( function() { 686 var elementToSetFocusTo = $( '.notice[role="alert"]' ); 687 688 if ( ! elementToSetFocusTo.length ) { 689 elementToSetFocusTo = $( '.imgedit-wrap' ).find( ':tabbable:first' ); 690 } 691 692 elementToSetFocusTo.trigger( 'focus' ); 693 }, 100 ); 694 }, 695 696 /** 697 * Initializes the cropping tool. 698 * 699 * @since 2.9.0 700 * 701 * @memberof imageEdit 702 * 703 * @param {number} postid The post ID. 704 * @param {HTMLElement} image The preview image. 705 * @param {HTMLElement} parent The preview image container. 706 * 707 * @return {void} 708 */ 709 initCrop : function(postid, image, parent) { 710 var t = this, 711 selW = $('#imgedit-sel-width-' + postid), 712 selH = $('#imgedit-sel-height-' + postid), 713 $image = $( image ), 714 $img; 715 716 // Already initialized? 717 if ( $image.data( 'imgAreaSelect' ) ) { 718 return; 719 } 720 721 t.iasapi = $image.imgAreaSelect({ 722 parent: parent, 723 instance: true, 724 handles: true, 725 keys: true, 726 minWidth: 3, 727 minHeight: 3, 728 729 /** 730 * Sets the CSS styles and binds events for locking the aspect ratio. 731 * 732 * @ignore 733 * 734 * @param {jQuery} img The preview image. 735 */ 736 onInit: function( img ) { 737 // Ensure that the imgAreaSelect wrapper elements are position:absolute 738 // (even if we're in a position:fixed modal). 739 $img = $( img ); 740 $img.next().css( 'position', 'absolute' ) 741 .nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' ); 742 /** 743 * Binds mouse down event to the cropping container. 744 * 745 * @return {void} 746 */ 747 parent.children().on( 'mousedown, touchstart', function(e){ 748 var ratio = false, sel, defRatio; 749 750 if ( e.shiftKey ) { 751 sel = t.iasapi.getSelection(); 752 defRatio = t.getSelRatio(postid); 753 ratio = ( sel && sel.width && sel.height ) ? sel.width + ':' + sel.height : defRatio; 754 } 755 756 t.iasapi.setOptions({ 757 aspectRatio: ratio 758 }); 759 }); 760 }, 761 762 /** 763 * Event triggered when starting a selection. 764 * 765 * @ignore 766 * 767 * @return {void} 768 */ 769 onSelectStart: function() { 770 imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1); 771 }, 772 /** 773 * Event triggered when the selection is ended. 774 * 775 * @ignore 776 * 777 * @param {Object} img jQuery object representing the image. 778 * @param {Object} c The selection. 779 * 780 * @return {Object} 781 */ 782 onSelectEnd: function(img, c) { 783 imageEdit.setCropSelection(postid, c); 784 }, 785 786 /** 787 * Event triggered when the selection changes. 788 * 789 * @ignore 790 * 791 * @param {Object} img jQuery object representing the image. 792 * @param {Object} c The selection. 793 * 794 * @return {void} 795 */ 796 onSelectChange: function(img, c) { 797 var sizer = imageEdit.hold.sizer; 798 selW.val( imageEdit.round(c.width / sizer) ); 799 selH.val( imageEdit.round(c.height / sizer) ); 800 } 801 }); 802 }, 803 804 /** 805 * Stores the current crop selection. 806 * 807 * @since 2.9.0 808 * 809 * @memberof imageEdit 810 * 811 * @param {number} postid The post ID. 812 * @param {Object} c The selection. 813 * 814 * @return {boolean} 815 */ 816 setCropSelection : function(postid, c) { 817 var sel; 818 819 c = c || 0; 820 821 if ( !c || ( c.width < 3 && c.height < 3 ) ) { 822 this.setDisabled( $( '.imgedit-crop', '#imgedit-panel-' + postid ), 1 ); 823 this.setDisabled( $( '#imgedit-crop-sel-' + postid ), 1 ); 824 $('#imgedit-sel-width-' + postid).val(''); 825 $('#imgedit-sel-height-' + postid).val(''); 826 $('#imgedit-selection-' + postid).val(''); 827 return false; 828 } 829 830 sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height }; 831 this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1); 832 $('#imgedit-selection-' + postid).val( JSON.stringify(sel) ); 833 }, 834 835 836 /** 837 * Closes the image editor. 838 * 839 * @since 2.9.0 840 * 841 * @memberof imageEdit 842 * 843 * @param {number} postid The post ID. 844 * @param {boolean} warn Warning message. 845 * 846 * @return {void|boolean} Returns false if there is a warning. 847 */ 848 close : function(postid, warn) { 849 warn = warn || false; 850 851 if ( warn && this.notsaved(postid) ) { 852 return false; 853 } 854 855 this.iasapi = {}; 856 this.hold = {}; 857 858 // If we've loaded the editor in the context of a Media Modal, 859 // then switch to the previous view, whatever that might have been. 860 if ( this._view ){ 861 this._view.back(); 862 } 863 864 // In case we are not accessing the image editor in the context of a View, 865 // close the editor the old-school way. 866 else { 867 $('#image-editor-' + postid).fadeOut('fast', function() { 868 $( '#media-head-' + postid ).fadeIn( 'fast', function() { 869 // Move focus back to the Edit Image button. Runs also when saving. 870 $( '#imgedit-open-btn-' + postid ).trigger( 'focus' ); 871 }); 872 $(this).empty(); 873 }); 874 } 875 876 877 }, 878 879 /** 880 * Checks if the image edit history is saved. 881 * 882 * @since 2.9.0 883 * 884 * @memberof imageEdit 885 * 886 * @param {number} postid The post ID. 887 * 888 * @return {boolean} Returns true if the history is not saved. 889 */ 890 notsaved : function(postid) { 891 var h = $('#imgedit-history-' + postid).val(), 892 history = ( h !== '' ) ? JSON.parse(h) : [], 893 pop = this.intval( $('#imgedit-undone-' + postid).val() ); 894 895 if ( pop < history.length ) { 896 if ( confirm( $('#imgedit-leaving-' + postid).text() ) ) { 897 return false; 898 } 899 return true; 900 } 901 return false; 902 }, 903 904 /** 905 * Adds an image edit action to the history. 906 * 907 * @since 2.9.0 908 * 909 * @memberof imageEdit 910 * 911 * @param {Object} op The original position. 912 * @param {number} postid The post ID. 913 * @param {string} nonce The nonce. 914 * 915 * @return {void} 916 */ 917 addStep : function(op, postid, nonce) { 918 var t = this, elem = $('#imgedit-history-' + postid), 919 history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [], 920 undone = $( '#imgedit-undone-' + postid ), 921 pop = t.intval( undone.val() ); 922 923 while ( pop > 0 ) { 924 history.pop(); 925 pop--; 926 } 927 undone.val(0); // Reset. 928 929 history.push(op); 930 elem.val( JSON.stringify(history) ); 931 932 t.refreshEditor(postid, nonce, function() { 933 t.setDisabled($('#image-undo-' + postid), true); 934 t.setDisabled($('#image-redo-' + postid), false); 935 }); 936 }, 937 938 /** 939 * Rotates the image. 940 * 941 * @since 2.9.0 942 * 943 * @memberof imageEdit 944 * 945 * @param {string} angle The angle the image is rotated with. 946 * @param {number} postid The post ID. 947 * @param {string} nonce The nonce. 948 * @param {Object} t The target element. 949 * 950 * @return {boolean} 951 */ 952 rotate : function(angle, postid, nonce, t) { 953 if ( $(t).hasClass('disabled') ) { 954 return false; 955 } 956 957 this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce); 958 }, 959 960 /** 961 * Flips the image. 962 * 963 * @since 2.9.0 964 * 965 * @memberof imageEdit 966 * 967 * @param {number} axis The axle the image is flipped on. 968 * @param {number} postid The post ID. 969 * @param {string} nonce The nonce. 970 * @param {Object} t The target element. 971 * 972 * @return {boolean} 973 */ 974 flip : function (axis, postid, nonce, t) { 975 if ( $(t).hasClass('disabled') ) { 976 return false; 977 } 978 979 this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce); 980 }, 981 982 /** 983 * Crops the image. 984 * 985 * @since 2.9.0 986 * 987 * @memberof imageEdit 988 * 989 * @param {number} postid The post ID. 990 * @param {string} nonce The nonce. 991 * @param {Object} t The target object. 992 * 993 * @return {void|boolean} Returns false if the crop button is disabled. 994 */ 995 crop : function (postid, nonce, t) { 996 var sel = $('#imgedit-selection-' + postid).val(), 997 w = this.intval( $('#imgedit-sel-width-' + postid).val() ), 998 h = this.intval( $('#imgedit-sel-height-' + postid).val() ); 999 1000 if ( $(t).hasClass('disabled') || sel === '' ) { 1001 return false; 1002 } 1003 1004 sel = JSON.parse(sel); 1005 if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) { 1006 sel.fw = w; 1007 sel.fh = h; 1008 this.addStep({ 'c': sel }, postid, nonce); 1009 } 1010 1011 // Clear the selection fields after cropping. 1012 $('#imgedit-sel-width-' + postid).val(''); 1013 $('#imgedit-sel-height-' + postid).val(''); 1014 }, 1015 1016 /** 1017 * Undoes an image edit action. 1018 * 1019 * @since 2.9.0 1020 * 1021 * @memberof imageEdit 1022 * 1023 * @param {number} postid The post ID. 1024 * @param {string} nonce The nonce. 1025 * 1026 * @return {void|false} Returns false if the undo button is disabled. 1027 */ 1028 undo : function (postid, nonce) { 1029 var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid), 1030 pop = t.intval( elem.val() ) + 1; 1031 1032 if ( button.hasClass('disabled') ) { 1033 return; 1034 } 1035 1036 elem.val(pop); 1037 t.refreshEditor(postid, nonce, function() { 1038 var elem = $('#imgedit-history-' + postid), 1039 history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : []; 1040 1041 t.setDisabled($('#image-redo-' + postid), true); 1042 t.setDisabled(button, pop < history.length); 1043 // When undo gets disabled, move focus to the redo button to avoid a focus loss. 1044 if ( history.length === pop ) { 1045 $( '#image-redo-' + postid ).trigger( 'focus' ); 1046 } 1047 }); 1048 }, 1049 1050 /** 1051 * Reverts a undo action. 1052 * 1053 * @since 2.9.0 1054 * 1055 * @memberof imageEdit 1056 * 1057 * @param {number} postid The post ID. 1058 * @param {string} nonce The nonce. 1059 * 1060 * @return {void} 1061 */ 1062 redo : function(postid, nonce) { 1063 var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid), 1064 pop = t.intval( elem.val() ) - 1; 1065 1066 if ( button.hasClass('disabled') ) { 1067 return; 1068 } 1069 1070 elem.val(pop); 1071 t.refreshEditor(postid, nonce, function() { 1072 t.setDisabled($('#image-undo-' + postid), true); 1073 t.setDisabled(button, pop > 0); 1074 // When redo gets disabled, move focus to the undo button to avoid a focus loss. 1075 if ( 0 === pop ) { 1076 $( '#image-undo-' + postid ).trigger( 'focus' ); 1077 } 1078 }); 1079 }, 1080 1081 /** 1082 * Sets the selection for the height and width in pixels. 1083 * 1084 * @since 2.9.0 1085 * 1086 * @memberof imageEdit 1087 * 1088 * @param {number} postid The post ID. 1089 * @param {jQuery} el The element containing the values. 1090 * 1091 * @return {void|boolean} Returns false when the x or y value is lower than 1, 1092 * void when the value is not numeric or when the operation 1093 * is successful. 1094 */ 1095 setNumSelection : function( postid, el ) { 1096 var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid), 1097 x = this.intval( elX.val() ), y = this.intval( elY.val() ), 1098 img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(), 1099 sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi; 1100 1101 if ( false === this.validateNumeric( el ) ) { 1102 return; 1103 } 1104 1105 if ( x < 1 ) { 1106 elX.val(''); 1107 return false; 1108 } 1109 1110 if ( y < 1 ) { 1111 elY.val(''); 1112 return false; 1113 } 1114 1115 if ( x && y && ( sel = ias.getSelection() ) ) { 1116 x2 = sel.x1 + Math.round( x * sizer ); 1117 y2 = sel.y1 + Math.round( y * sizer ); 1118 x1 = sel.x1; 1119 y1 = sel.y1; 1120 1121 if ( x2 > imgw ) { 1122 x1 = 0; 1123 x2 = imgw; 1124 elX.val( Math.round( x2 / sizer ) ); 1125 } 1126 1127 if ( y2 > imgh ) { 1128 y1 = 0; 1129 y2 = imgh; 1130 elY.val( Math.round( y2 / sizer ) ); 1131 } 1132 1133 ias.setSelection( x1, y1, x2, y2 ); 1134 ias.update(); 1135 this.setCropSelection(postid, ias.getSelection()); 1136 } 1137 }, 1138 1139 /** 1140 * Rounds a number to a whole. 1141 * 1142 * @since 2.9.0 1143 * 1144 * @memberof imageEdit 1145 * 1146 * @param {number} num The number. 1147 * 1148 * @return {number} The number rounded to a whole number. 1149 */ 1150 round : function(num) { 1151 var s; 1152 num = Math.round(num); 1153 1154 if ( this.hold.sizer > 0.6 ) { 1155 return num; 1156 } 1157 1158 s = num.toString().slice(-1); 1159 1160 if ( '1' === s ) { 1161 return num - 1; 1162 } else if ( '9' === s ) { 1163 return num + 1; 1164 } 1165 1166 return num; 1167 }, 1168 1169 /** 1170 * Sets a locked aspect ratio for the selection. 1171 * 1172 * @since 2.9.0 1173 * 1174 * @memberof imageEdit 1175 * 1176 * @param {number} postid The post ID. 1177 * @param {number} n The ratio to set. 1178 * @param {jQuery} el The element containing the values. 1179 * 1180 * @return {void} 1181 */ 1182 setRatioSelection : function(postid, n, el) { 1183 var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ), 1184 y = this.intval( $('#imgedit-crop-height-' + postid).val() ), 1185 h = $('#image-preview-' + postid).height(); 1186 1187 if ( false === this.validateNumeric( el ) ) { 1188 this.iasapi.setOptions({ 1189 aspectRatio: null 1190 }); 1191 1192 return; 1193 } 1194 1195 if ( x && y ) { 1196 this.iasapi.setOptions({ 1197 aspectRatio: x + ':' + y 1198 }); 1199 1200 if ( sel = this.iasapi.getSelection(true) ) { 1201 r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) ); 1202 1203 if ( r > h ) { 1204 r = h; 1205 if ( n ) { 1206 $('#imgedit-crop-height-' + postid).val(''); 1207 } else { 1208 $('#imgedit-crop-width-' + postid).val(''); 1209 } 1210 } 1211 1212 this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r ); 1213 this.iasapi.update(); 1214 } 1215 } 1216 }, 1217 1218 /** 1219 * Validates if a value in a jQuery.HTMLElement is numeric. 1220 * 1221 * @since 4.6.0 1222 * 1223 * @memberof imageEdit 1224 * 1225 * @param {jQuery} el The html element. 1226 * 1227 * @return {void|boolean} Returns false if the value is not numeric, 1228 * void when it is. 1229 */ 1230 validateNumeric: function( el ) { 1231 if ( ! this.intval( $( el ).val() ) ) { 1232 $( el ).val( '' ); 1233 return false; 1234 } 1235 } 1236 }; 1237 })(jQuery);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Oct 9 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |