[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/js/ -> image-edit.js (source)

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


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref