]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/js/image-edit.js
WordPress 4.5.2
[autoinstalls/wordpress.git] / wp-admin / js / image-edit.js
1 /* global imageEditL10n, ajaxurl, confirm */
2
3 (function($) {
4 var imageEdit = window.imageEdit = {
5         iasapi : {},
6         hold : {},
7         postid : '',
8         _view : false,
9
10         intval : function(f) {
11                 return f | 0;
12         },
13
14         setDisabled : function( el, s ) {
15                 /*
16                  * `el` can be a single form element or a fieldset. Before #28864, the disabled state on
17                  * some text fields  was handled targeting $('input', el). Now we need to handle the
18                  * disabled state on buttons too so we can just target `el` regardless if it's a single
19                  * element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
20                  */
21                 if ( s ) {
22                         el.removeClass( 'disabled' ).prop( 'disabled', false );
23                 } else {
24                         el.addClass( 'disabled' ).prop( 'disabled', true );
25                 }
26         },
27
28         init : function(postid) {
29                 var t = this, old = $('#image-editor-' + t.postid),
30                         x = t.intval( $('#imgedit-x-' + postid).val() ),
31                         y = t.intval( $('#imgedit-y-' + postid).val() );
32
33                 if ( t.postid !== postid && old.length ) {
34                         t.close(t.postid);
35                 }
36
37                 t.hold.w = t.hold.ow = x;
38                 t.hold.h = t.hold.oh = y;
39                 t.hold.xy_ratio = x / y;
40                 t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
41                 t.postid = postid;
42                 $('#imgedit-response-' + postid).empty();
43
44                 $('input[type="text"]', '#imgedit-panel-' + postid).keypress(function(e) {
45                         var k = e.keyCode;
46
47                         if ( 36 < k && k < 41 ) {
48                                 $(this).blur();
49                         }
50
51                         if ( 13 === k ) {
52                                 e.preventDefault();
53                                 e.stopPropagation();
54                                 return false;
55                         }
56                 });
57         },
58
59         toggleEditor : function(postid, toggle) {
60                 var wait = $('#imgedit-wait-' + postid);
61
62                 if ( toggle ) {
63                         wait.fadeIn( 'fast' );
64                 } else {
65                         wait.fadeOut('fast');
66                 }
67         },
68
69         toggleHelp : function(el) {
70                 var $el = $( el );
71                 $el
72                         .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
73                         .parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
74
75                 return false;
76         },
77
78         getTarget : function(postid) {
79                 return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
80         },
81
82         scaleChanged : function(postid, x) {
83                 var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
84                 warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
85
86                 if ( x ) {
87                         h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
88                         h.val( h1 );
89                 } else {
90                         w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
91                         w.val( w1 );
92                 }
93
94                 if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
95                         warn.css('visibility', 'visible');
96                 } else {
97                         warn.css('visibility', 'hidden');
98                 }
99         },
100
101         getSelRatio : function(postid) {
102                 var x = this.hold.w, y = this.hold.h,
103                         X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
104                         Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
105
106                 if ( X && Y ) {
107                         return X + ':' + Y;
108                 }
109
110                 if ( x && y ) {
111                         return x + ':' + y;
112                 }
113
114                 return '1:1';
115         },
116
117         filterHistory : function(postid, setSize) {
118                 // apply undo state to history
119                 var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
120
121                 if ( history !== '' ) {
122                         history = JSON.parse(history);
123                         pop = this.intval( $('#imgedit-undone-' + postid).val() );
124                         if ( pop > 0 ) {
125                                 while ( pop > 0 ) {
126                                         history.pop();
127                                         pop--;
128                                 }
129                         }
130
131                         if ( setSize ) {
132                                 if ( !history.length ) {
133                                         this.hold.w = this.hold.ow;
134                                         this.hold.h = this.hold.oh;
135                                         return '';
136                                 }
137
138                                 // restore
139                                 o = history[history.length - 1];
140                                 o = o.c || o.r || o.f || false;
141
142                                 if ( o ) {
143                                         this.hold.w = o.fw;
144                                         this.hold.h = o.fh;
145                                 }
146                         }
147
148                         // filter the values
149                         for ( n in history ) {
150                                 i = history[n];
151                                 if ( i.hasOwnProperty('c') ) {
152                                         op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } };
153                                 } else if ( i.hasOwnProperty('r') ) {
154                                         op[n] = { 'r': i.r.r };
155                                 } else if ( i.hasOwnProperty('f') ) {
156                                         op[n] = { 'f': i.f.f };
157                                 }
158                         }
159                         return JSON.stringify(op);
160                 }
161                 return '';
162         },
163
164         refreshEditor : function(postid, nonce, callback) {
165                 var t = this, data, img;
166
167                 t.toggleEditor(postid, 1);
168                 data = {
169                         'action': 'imgedit-preview',
170                         '_ajax_nonce': nonce,
171                         'postid': postid,
172                         'history': t.filterHistory(postid, 1),
173                         'rand': t.intval(Math.random() * 1000000)
174                 };
175
176                 img = $( '<img id="image-preview-' + postid + '" alt="" />' )
177                         .on( 'load', { history: data.history }, function( event ) {
178                                 var max1, max2,
179                                         parent = $( '#imgedit-crop-' + postid ),
180                                         t = imageEdit,
181                                         historyObj;
182
183                                 if ( '' !== event.data.history ) {
184                                         historyObj = JSON.parse( event.data.history );
185                                         // If last executed action in history is a crop action.
186                                         if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
187                                                 /*
188                                                  * A crop action has completed and the crop button gets disabled
189                                                  * ensure the undo button is enabled.
190                                                  */
191                                                 t.setDisabled( $( '#image-undo-' + postid) , true );
192                                                 // Move focus to the undo button to avoid a focus loss.
193                                                 $( '#image-undo-' + postid ).focus();
194                                         }
195                                 }
196
197                                 parent.empty().append(img);
198
199                                 // w, h are the new full size dims
200                                 max1 = Math.max( t.hold.w, t.hold.h );
201                                 max2 = Math.max( $(img).width(), $(img).height() );
202                                 t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
203
204                                 t.initCrop(postid, img, parent);
205                                 t.setCropSelection(postid, 0);
206
207                                 if ( (typeof callback !== 'undefined') && callback !== null ) {
208                                         callback();
209                                 }
210
211                                 if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
212                                         $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).removeAttr('disabled');
213                                 } else {
214                                         $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
215                                 }
216
217                                 t.toggleEditor(postid, 0);
218                         })
219                         .on('error', function() {
220                                 $('#imgedit-crop-' + postid).empty().append('<div class="error"><p>' + imageEditL10n.error + '</p></div>');
221                                 t.toggleEditor(postid, 0);
222                         })
223                         .attr('src', ajaxurl + '?' + $.param(data));
224         },
225
226         action : function(postid, nonce, action) {
227                 var t = this, data, w, h, fw, fh;
228
229                 if ( t.notsaved(postid) ) {
230                         return false;
231                 }
232
233                 data = {
234                         'action': 'image-editor',
235                         '_ajax_nonce': nonce,
236                         'postid': postid
237                 };
238
239                 if ( 'scale' === action ) {
240                         w = $('#imgedit-scale-width-' + postid),
241                         h = $('#imgedit-scale-height-' + postid),
242                         fw = t.intval(w.val()),
243                         fh = t.intval(h.val());
244
245                         if ( fw < 1 ) {
246                                 w.focus();
247                                 return false;
248                         } else if ( fh < 1 ) {
249                                 h.focus();
250                                 return false;
251                         }
252
253                         if ( fw === t.hold.ow || fh === t.hold.oh ) {
254                                 return false;
255                         }
256
257                         data['do'] = 'scale';
258                         data.fwidth = fw;
259                         data.fheight = fh;
260                 } else if ( 'restore' === action ) {
261                         data['do'] = 'restore';
262                 } else {
263                         return false;
264                 }
265
266                 t.toggleEditor(postid, 1);
267                 $.post(ajaxurl, data, function(r) {
268                         $('#image-editor-' + postid).empty().append(r);
269                         t.toggleEditor(postid, 0);
270                         // refresh the attachment model so that changes propagate
271                         if ( t._view ) {
272                                 t._view.refresh();
273                         }
274                 });
275         },
276
277         save : function(postid, nonce) {
278                 var data,
279                         target = this.getTarget(postid),
280                         history = this.filterHistory(postid, 0),
281                         self = this;
282
283                 if ( '' === history ) {
284                         return false;
285                 }
286
287                 this.toggleEditor(postid, 1);
288                 data = {
289                         'action': 'image-editor',
290                         '_ajax_nonce': nonce,
291                         'postid': postid,
292                         'history': history,
293                         'target': target,
294                         'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
295                         'do': 'save'
296                 };
297
298                 $.post(ajaxurl, data, function(r) {
299                         var ret = JSON.parse(r);
300
301                         if ( ret.error ) {
302                                 $('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p></div>');
303                                 imageEdit.close(postid);
304                                 return;
305                         }
306
307                         if ( ret.fw && ret.fh ) {
308                                 $('#media-dims-' + postid).html( ret.fw + ' &times; ' + ret.fh );
309                         }
310
311                         if ( ret.thumbnail ) {
312                                 $('.thumbnail', '#thumbnail-head-' + postid).attr('src', ''+ret.thumbnail);
313                         }
314
315                         if ( ret.msg ) {
316                                 $('#imgedit-response-' + postid).html('<div class="updated"><p>' + ret.msg + '</p></div>');
317                         }
318
319                         if ( self._view ) {
320                                 self._view.save();
321                         } else {
322                                 imageEdit.close(postid);
323                         }
324                 });
325         },
326
327         open : function( postid, nonce, view ) {
328                 this._view = view;
329
330                 var dfd, data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
331                         btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('.spinner');
332
333                 /*
334                  * Instead of disabling the button, which causes a focus loss and makes screen
335                  * readers announce "unavailable", return if the button was already clicked.
336                  */
337                 if ( btn.hasClass( 'button-activated' ) ) {
338                         return;
339                 }
340
341                 spin.addClass( 'is-active' );
342
343                 data = {
344                         'action': 'image-editor',
345                         '_ajax_nonce': nonce,
346                         'postid': postid,
347                         'do': 'open'
348                 };
349
350                 dfd = $.ajax({
351                         url:  ajaxurl,
352                         type: 'post',
353                         data: data,
354                         beforeSend: function() {
355                                 btn.addClass( 'button-activated' );
356                         }
357                 }).done(function( html ) {
358                         elem.html( html );
359                         head.fadeOut('fast', function(){
360                                 elem.fadeIn('fast');
361                                 btn.removeClass( 'button-activated' );
362                                 spin.removeClass( 'is-active' );
363                         });
364                 });
365
366                 return dfd;
367         },
368
369         imgLoaded : function(postid) {
370                 var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
371
372                 this.initCrop(postid, img, parent);
373                 this.setCropSelection(postid, 0);
374                 this.toggleEditor(postid, 0);
375                 // Editor is ready, move focus to the first focusable element.
376                 $( '.imgedit-wrap .imgedit-help-toggle' ).eq( 0 ).focus();
377         },
378
379         initCrop : function(postid, image, parent) {
380                 var t = this,
381                         selW = $('#imgedit-sel-width-' + postid),
382                         selH = $('#imgedit-sel-height-' + postid),
383                         $img;
384
385                 t.iasapi = $(image).imgAreaSelect({
386                         parent: parent,
387                         instance: true,
388                         handles: true,
389                         keys: true,
390                         minWidth: 3,
391                         minHeight: 3,
392
393                         onInit: function( img ) {
394                                 // Ensure that the imgareaselect wrapper elements are position:absolute
395                                 // (even if we're in a position:fixed modal)
396                                 $img = $( img );
397                                 $img.next().css( 'position', 'absolute' )
398                                         .nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
399
400                                 parent.children().mousedown(function(e){
401                                         var ratio = false, sel, defRatio;
402
403                                         if ( e.shiftKey ) {
404                                                 sel = t.iasapi.getSelection();
405                                                 defRatio = t.getSelRatio(postid);
406                                                 ratio = ( sel && sel.width && sel.height ) ? sel.width + ':' + sel.height : defRatio;
407                                         }
408
409                                         t.iasapi.setOptions({
410                                                 aspectRatio: ratio
411                                         });
412                                 });
413                         },
414
415                         onSelectStart: function() {
416                                 imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
417                         },
418
419                         onSelectEnd: function(img, c) {
420                                 imageEdit.setCropSelection(postid, c);
421                         },
422
423                         onSelectChange: function(img, c) {
424                                 var sizer = imageEdit.hold.sizer;
425                                 selW.val( imageEdit.round(c.width / sizer) );
426                                 selH.val( imageEdit.round(c.height / sizer) );
427                         }
428                 });
429         },
430
431         setCropSelection : function(postid, c) {
432                 var sel;
433
434                 c = c || 0;
435
436                 if ( !c || ( c.width < 3 && c.height < 3 ) ) {
437                         this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 0);
438                         this.setDisabled($('#imgedit-crop-sel-' + postid), 0);
439                         $('#imgedit-sel-width-' + postid).val('');
440                         $('#imgedit-sel-height-' + postid).val('');
441                         $('#imgedit-selection-' + postid).val('');
442                         return false;
443                 }
444
445                 sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height };
446                 this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
447                 $('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
448         },
449
450         close : function(postid, warn) {
451                 warn = warn || false;
452
453                 if ( warn && this.notsaved(postid) ) {
454                         return false;
455                 }
456
457                 this.iasapi = {};
458                 this.hold = {};
459
460                 // If we've loaded the editor in the context of a Media Modal, then switch to the previous view,
461                 // whatever that might have been.
462                 if ( this._view ){
463                         this._view.back();
464                 }
465
466                 // In case we are not accessing the image editor in the context of a View, close the editor the old-skool way
467                 else {
468                         $('#image-editor-' + postid).fadeOut('fast', function() {
469                                 $( '#media-head-' + postid ).fadeIn( 'fast', function() {
470                                         // Move focus back to the Edit Image button. Runs also when saving.
471                                         $( '#imgedit-open-btn-' + postid ).focus();
472                                 });
473                                 $(this).empty();
474                         });
475                 }
476
477
478         },
479
480         notsaved : function(postid) {
481                 var h = $('#imgedit-history-' + postid).val(),
482                         history = ( h !== '' ) ? JSON.parse(h) : [],
483                         pop = this.intval( $('#imgedit-undone-' + postid).val() );
484
485                 if ( pop < history.length ) {
486                         if ( confirm( $('#imgedit-leaving-' + postid).html() ) ) {
487                                 return false;
488                         }
489                         return true;
490                 }
491                 return false;
492         },
493
494         addStep : function(op, postid, nonce) {
495                 var t = this, elem = $('#imgedit-history-' + postid),
496                         history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
497                         undone = $( '#imgedit-undone-' + postid ),
498                         pop = t.intval( undone.val() );
499
500                 while ( pop > 0 ) {
501                         history.pop();
502                         pop--;
503                 }
504                 undone.val(0); // reset
505
506                 history.push(op);
507                 elem.val( JSON.stringify(history) );
508
509                 t.refreshEditor(postid, nonce, function() {
510                         t.setDisabled($('#image-undo-' + postid), true);
511                         t.setDisabled($('#image-redo-' + postid), false);
512                 });
513         },
514
515         rotate : function(angle, postid, nonce, t) {
516                 if ( $(t).hasClass('disabled') ) {
517                         return false;
518                 }
519
520                 this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
521         },
522
523         flip : function (axis, postid, nonce, t) {
524                 if ( $(t).hasClass('disabled') ) {
525                         return false;
526                 }
527
528                 this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
529         },
530
531         crop : function (postid, nonce, t) {
532                 var sel = $('#imgedit-selection-' + postid).val(),
533                         w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
534                         h = this.intval( $('#imgedit-sel-height-' + postid).val() );
535
536                 if ( $(t).hasClass('disabled') || sel === '' ) {
537                         return false;
538                 }
539
540                 sel = JSON.parse(sel);
541                 if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) {
542                         sel.fw = w;
543                         sel.fh = h;
544                         this.addStep({ 'c': sel }, postid, nonce);
545                 }
546         },
547
548         undo : function (postid, nonce) {
549                 var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
550                         pop = t.intval( elem.val() ) + 1;
551
552                 if ( button.hasClass('disabled') ) {
553                         return;
554                 }
555
556                 elem.val(pop);
557                 t.refreshEditor(postid, nonce, function() {
558                         var elem = $('#imgedit-history-' + postid),
559                                 history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
560
561                         t.setDisabled($('#image-redo-' + postid), true);
562                         t.setDisabled(button, pop < history.length);
563                         // When undo gets disabled, move focus to the redo button to avoid a focus loss.
564                         if ( history.length === pop ) {
565                                 $( '#image-redo-' + postid ).focus();
566                         }
567                 });
568         },
569
570         redo : function(postid, nonce) {
571                 var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
572                         pop = t.intval( elem.val() ) - 1;
573
574                 if ( button.hasClass('disabled') ) {
575                         return;
576                 }
577
578                 elem.val(pop);
579                 t.refreshEditor(postid, nonce, function() {
580                         t.setDisabled($('#image-undo-' + postid), true);
581                         t.setDisabled(button, pop > 0);
582                         // When redo gets disabled, move focus to the undo button to avoid a focus loss.
583                         if ( 0 === pop ) {
584                                 $( '#image-undo-' + postid ).focus();
585                         }
586                 });
587         },
588
589         setNumSelection : function(postid) {
590                 var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
591                         x = this.intval( elX.val() ), y = this.intval( elY.val() ),
592                         img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
593                         sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
594
595                 if ( x < 1 ) {
596                         elX.val('');
597                         return false;
598                 }
599
600                 if ( y < 1 ) {
601                         elY.val('');
602                         return false;
603                 }
604
605                 if ( x && y && ( sel = ias.getSelection() ) ) {
606                         x2 = sel.x1 + Math.round( x * sizer );
607                         y2 = sel.y1 + Math.round( y * sizer );
608                         x1 = sel.x1;
609                         y1 = sel.y1;
610
611                         if ( x2 > imgw ) {
612                                 x1 = 0;
613                                 x2 = imgw;
614                                 elX.val( Math.round( x2 / sizer ) );
615                         }
616
617                         if ( y2 > imgh ) {
618                                 y1 = 0;
619                                 y2 = imgh;
620                                 elY.val( Math.round( y2 / sizer ) );
621                         }
622
623                         ias.setSelection( x1, y1, x2, y2 );
624                         ias.update();
625                         this.setCropSelection(postid, ias.getSelection());
626                 }
627         },
628
629         round : function(num) {
630                 var s;
631                 num = Math.round(num);
632
633                 if ( this.hold.sizer > 0.6 ) {
634                         return num;
635                 }
636
637                 s = num.toString().slice(-1);
638
639                 if ( '1' === s ) {
640                         return num - 1;
641                 } else if ( '9' === s ) {
642                         return num + 1;
643                 }
644
645                 return num;
646         },
647
648         setRatioSelection : function(postid, n, el) {
649                 var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
650                         y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
651                         h = $('#image-preview-' + postid).height();
652
653                 if ( !this.intval( $(el).val() ) ) {
654                         $(el).val('');
655                         return;
656                 }
657
658                 if ( x && y ) {
659                         this.iasapi.setOptions({
660                                 aspectRatio: x + ':' + y
661                         });
662
663                         if ( sel = this.iasapi.getSelection(true) ) {
664                                 r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) );
665
666                                 if ( r > h ) {
667                                         r = h;
668                                         if ( n ) {
669                                                 $('#imgedit-crop-height-' + postid).val('');
670                                         } else {
671                                                 $('#imgedit-crop-width-' + postid).val('');
672                                         }
673                                 }
674
675                                 this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
676                                 this.iasapi.update();
677                         }
678                 }
679         }
680 };
681 })(jQuery);