home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-admin / js / image-edit.js < prev    next >
Encoding:
JavaScript  |  2017-01-27  |  27.8 KB  |  1,093 lines

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