[ Index ]

PHP Cross Reference of WordPress

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


Generated: Sun Jul 5 01:00:04 2020 Cross-referenced by PHPXref 0.7.1