From 807aa1ebb18186511c1f8f0b93570348a2f51f38 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Fri, 5 Sep 2014 17:52:08 +0100 Subject: [PATCH 1/9] Started work on manual image cropping --- wagtail/wagtailimages/admin_urls.py | 4 +- wagtail/wagtailimages/forms.py | 15 +++- wagtail/wagtailimages/models.py | 8 +-- .../static/wagtailimages/js/add-multiple.js | 4 ++ .../wagtailimages/js/focal-point-chooser.js | 52 ++++++++++++++ .../js/vendor/jquery.Jcrop.min.js | 22 ++++++ .../wagtailimages/scss/vendor/Jcrop.gif | Bin 0 -> 329 bytes .../scss/vendor/jquery.Jcrop.min.css | 29 ++++++++ .../focal_point_chooser/chooser.html | 11 +++ .../focal_point_chooser/chooser.js | 67 ++++++++++++++++++ .../images/_focal_point_chooser.html | 41 +++++++++++ .../templates/wagtailimages/images/edit.html | 14 ++++ .../templates/wagtailimages/multiple/add.html | 8 +++ .../wagtailimages/multiple/edit_form.html | 10 ++- .../views/focal_point_chooser.py | 21 ++++++ 15 files changed, 298 insertions(+), 8 deletions(-) create mode 100644 wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js create mode 100644 wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.Jcrop.min.js create mode 100644 wagtail/wagtailimages/static/wagtailimages/scss/vendor/Jcrop.gif create mode 100644 wagtail/wagtailimages/static/wagtailimages/scss/vendor/jquery.Jcrop.min.css create mode 100644 wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html create mode 100644 wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js create mode 100644 wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html create mode 100644 wagtail/wagtailimages/views/focal_point_chooser.py diff --git a/wagtail/wagtailimages/admin_urls.py b/wagtail/wagtailimages/admin_urls.py index 44bc61224..1d04bcbfe 100644 --- a/wagtail/wagtailimages/admin_urls.py +++ b/wagtail/wagtailimages/admin_urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url -from wagtail.wagtailimages.views import images, chooser, multiple +from wagtail.wagtailimages.views import images, chooser, multiple, focal_point_chooser urlpatterns = [ @@ -20,4 +20,6 @@ urlpatterns = [ url(r'^chooser/(\d+)/$', chooser.image_chosen, name='wagtailimages_image_chosen'), url(r'^chooser/upload/$', chooser.chooser_upload, name='wagtailimages_chooser_upload'), url(r'^chooser/(\d+)/select_format/$', chooser.chooser_select_format, name='wagtailimages_chooser_select_format'), + + url(r'focal_point_chooser/(\d+)/$', focal_point_chooser.chooser, name='wagtailimages_focal_point_chooser') ] diff --git a/wagtail/wagtailimages/forms.py b/wagtail/wagtailimages/forms.py index 5f98fd1b0..af3b3ce83 100644 --- a/wagtail/wagtailimages/forms.py +++ b/wagtail/wagtailimages/forms.py @@ -12,12 +12,23 @@ def get_image_form(): # set the 'file' widget to a FileInput rather than the default ClearableFileInput # so that when editing, we don't get the 'currently: ...' banner which is # a bit pointless here - widgets={'file': forms.FileInput()}) + widgets={ + 'file': forms.FileInput(), + 'focal_point_x': forms.HiddenInput(attrs={'class': 'focal_point_x'}), + 'focal_point_y': forms.HiddenInput(attrs={'class': 'focal_point_y'}), + 'focal_point_width': forms.HiddenInput(attrs={'class': 'focal_point_width'}), + 'focal_point_height': forms.HiddenInput(attrs={'class': 'focal_point_height'}), + }) def get_image_form_for_multi(): # exclude the file widget - return modelform_factory(get_image_model(), exclude=('file',)) + return modelform_factory(get_image_model(), exclude=('file',), widgets={ + 'focal_point_x': forms.HiddenInput(attrs={'class': 'focal_point_x'}), + 'focal_point_y': forms.HiddenInput(attrs={'class': 'focal_point_y'}), + 'focal_point_width': forms.HiddenInput(attrs={'class': 'focal_point_width'}), + 'focal_point_height': forms.HiddenInput(attrs={'class': 'focal_point_height'}), + }) class ImageInsertionForm(forms.Form): diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 404cc7210..abd7c06a7 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -54,10 +54,10 @@ class AbstractImage(models.Model, TagSearchable): tags = TaggableManager(help_text=None, blank=True, verbose_name=_('Tags')) - focal_point_x = models.PositiveIntegerField(null=True, editable=False) - focal_point_y = models.PositiveIntegerField(null=True, editable=False) - focal_point_width = models.PositiveIntegerField(null=True, editable=False) - focal_point_height = models.PositiveIntegerField(null=True, editable=False) + focal_point_x = models.PositiveIntegerField(null=True, blank=True) + focal_point_y = models.PositiveIntegerField(null=True, blank=True) + focal_point_width = models.PositiveIntegerField(null=True, blank=True) + focal_point_height = models.PositiveIntegerField(null=True, blank=True) def get_usage(self): return get_object_usage(self) diff --git a/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js b/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js index 05c72feb4..ce6d355fa 100644 --- a/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js +++ b/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js @@ -97,6 +97,10 @@ $(function(){ // run tagit enhancement $('.tag_field input', itemElement).tagit(window.tagit_opts); + + $('.focal-point-chooser', itemElement).each(function() { + createFocalPointCooser($(this)); + }); } else { itemElement.addClass('upload-failure'); $('.right .error_messages', itemElement).append(response.error_message); diff --git a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js new file mode 100644 index 000000000..6d0f385db --- /dev/null +++ b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js @@ -0,0 +1,52 @@ +function createFocalPointCooser($chooser) { + var $chosenText = $('.chosen-text', $chooser); + var $focalPointX = $('input.focal_point_x', $chooser); + var $focalPointY = $('input.focal_point_y', $chooser); + var $focalPointWidth = $('input.focal_point_width', $chooser); + var $focalPointHeight = $('input.focal_point_height', $chooser); + var chooserUrl = $chooser.data('chooserUrl'); + + $('.action-choose', $chooser).click(function() { + if (!$chooser.hasClass('blank')) { + window.focalPointChooserInitial = { + x: $focalPointX.val(), + y: $focalPointY.val(), + w: $focalPointWidth.val(), + h: $focalPointHeight.val(), + } + } else { + window.focalPointChooserInitial = undefined; + } + + ModalWorkflow({ + 'url': chooserUrl, + 'responses': { + 'focalPointChosen': function(focalPointData) { + $focalPointX.val(focalPointData.x); + $focalPointY.val(focalPointData.y); + $focalPointWidth.val(focalPointData.w); + $focalPointHeight.val(focalPointData.h); + + $chosenText.text(focalPointData.x + ", " + focalPointData.y + " " + focalPointData.w + "x" + focalPointData.h); + + $chooser.removeClass('blank'); + } + } + }); + }); + + $('.action-clear', $chooser).click(function() { + $focalPointX.val(''); + $focalPointY.val(''); + $focalPointWidth.val(''); + $focalPointHeight.val(''); + + $chooser.addClass('blank'); + }); +} + +$(function() { + $('.focal-point-chooser').each(function() { + createFocalPointCooser($(this)); + }); +}); diff --git a/wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.Jcrop.min.js b/wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.Jcrop.min.js new file mode 100644 index 000000000..4c9c7adb6 --- /dev/null +++ b/wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.Jcrop.min.js @@ -0,0 +1,22 @@ +/** + * jquery.Jcrop.min.js v0.9.12 (build:20130202) + * jQuery Image Cropping Plugin - released under MIT License + * Copyright (c) 2008-2013 Tapmodo Interactive LLC + * https://github.com/tapmodo/Jcrop + */ +(function(a){a.Jcrop=function(b,c){function i(a){return Math.round(a)+"px"}function j(a){return d.baseClass+"-"+a}function k(){return a.fx.step.hasOwnProperty("backgroundColor")}function l(b){var c=a(b).offset();return[c.left,c.top]}function m(a){return[a.pageX-e[0],a.pageY-e[1]]}function n(b){typeof b!="object"&&(b={}),d=a.extend(d,b),a.each(["onChange","onSelect","onRelease","onDblClick"],function(a,b){typeof d[b]!="function"&&(d[b]=function(){})})}function o(a,b,c){e=l(D),bc.setCursor(a==="move"?a:a+"-resize");if(a==="move")return bc.activateHandlers(q(b),v,c);var d=_.getFixed(),f=r(a),g=_.getCorner(r(f));_.setPressed(_.getCorner(f)),_.setCurrent(g),bc.activateHandlers(p(a,d),v,c)}function p(a,b){return function(c){if(!d.aspectRatio)switch(a){case"e":c[1]=b.y2;break;case"w":c[1]=b.y2;break;case"n":c[0]=b.x2;break;case"s":c[0]=b.x2}else switch(a){case"e":c[1]=b.y+1;break;case"w":c[1]=b.y+1;break;case"n":c[0]=b.x+1;break;case"s":c[0]=b.x+1}_.setCurrent(c),bb.update()}}function q(a){var b=a;return bd.watchKeys +(),function(a){_.moveOffset([a[0]-b[0],a[1]-b[1]]),b=a,bb.update()}}function r(a){switch(a){case"n":return"sw";case"s":return"nw";case"e":return"nw";case"w":return"ne";case"ne":return"sw";case"nw":return"se";case"se":return"nw";case"sw":return"ne"}}function s(a){return function(b){return d.disabled?!1:a==="move"&&!d.allowMove?!1:(e=l(D),W=!0,o(a,m(b)),b.stopPropagation(),b.preventDefault(),!1)}}function t(a,b,c){var d=a.width(),e=a.height();d>b&&b>0&&(d=b,e=b/a.width()*a.height()),e>c&&c>0&&(e=c,d=c/a.height()*a.width()),T=a.width()/d,U=a.height()/e,a.width(d).height(e)}function u(a){return{x:a.x*T,y:a.y*U,x2:a.x2*T,y2:a.y2*U,w:a.w*T,h:a.h*U}}function v(a){var b=_.getFixed();b.w>d.minSelect[0]&&b.h>d.minSelect[1]?(bb.enableHandles(),bb.done()):bb.release(),bc.setCursor(d.allowSelect?"crosshair":"default")}function w(a){if(d.disabled)return!1;if(!d.allowSelect)return!1;W=!0,e=l(D),bb.disableHandles(),bc.setCursor("crosshair");var b=m(a);return _.setPressed(b),bb.update(),bc.activateHandlers(x,v,a.type.substring +(0,5)==="touch"),bd.watchKeys(),a.stopPropagation(),a.preventDefault(),!1}function x(a){_.setCurrent(a),bb.update()}function y(){var b=a("
").addClass(j("tracker"));return g&&b.css({opacity:0,backgroundColor:"white"}),b}function be(a){G.removeClass().addClass(j("holder")).addClass(a)}function bf(a,b){function t(){window.setTimeout(u,l)}var c=a[0]/T,e=a[1]/U,f=a[2]/T,g=a[3]/U;if(X)return;var h=_.flipCoords(c,e,f,g),i=_.getFixed(),j=[i.x,i.y,i.x2,i.y2],k=j,l=d.animationDelay,m=h[0]-j[0],n=h[1]-j[1],o=h[2]-j[2],p=h[3]-j[3],q=0,r=d.swingSpeed;c=k[0],e=k[1],f=k[2],g=k[3],bb.animMode(!0);var s,u=function(){return function(){q+=(100-q)/r,k[0]=Math.round(c+q/100*m),k[1]=Math.round(e+q/100*n),k[2]=Math.round(f+q/100*o),k[3]=Math.round(g+q/100*p),q>=99.8&&(q=100),q<100?(bh(k),t()):(bb.done(),bb.animMode(!1),typeof b=="function"&&b.call(bs))}}();t()}function bg(a){bh([a[0]/T,a[1]/U,a[2]/T,a[3]/U]),d.onSelect.call(bs,u(_.getFixed())),bb.enableHandles()}function bh(a){_.setPressed([a[0],a[1]]),_.setCurrent([a[2], +a[3]]),bb.update()}function bi(){return u(_.getFixed())}function bj(){return _.getFixed()}function bk(a){n(a),br()}function bl(){d.disabled=!0,bb.disableHandles(),bb.setCursor("default"),bc.setCursor("default")}function bm(){d.disabled=!1,br()}function bn(){bb.done(),bc.activateHandlers(null,null)}function bo(){G.remove(),A.show(),A.css("visibility","visible"),a(b).removeData("Jcrop")}function bp(a,b){bb.release(),bl();var c=new Image;c.onload=function(){var e=c.width,f=c.height,g=d.boxWidth,h=d.boxHeight;D.width(e).height(f),D.attr("src",a),H.attr("src",a),t(D,g,h),E=D.width(),F=D.height(),H.width(E).height(F),M.width(E+L*2).height(F+L*2),G.width(E).height(F),ba.resize(E,F),bm(),typeof b=="function"&&b.call(bs)},c.src=a}function bq(a,b,c){var e=b||d.bgColor;d.bgFade&&k()&&d.fadeTime&&!c?a.animate({backgroundColor:e},{queue:!1,duration:d.fadeTime}):a.css("backgroundColor",e)}function br(a){d.allowResize?a?bb.enableOnly():bb.enableHandles():bb.disableHandles(),bc.setCursor(d.allowSelect?"crosshair":"default"),bb +.setCursor(d.allowMove?"move":"default"),d.hasOwnProperty("trueSize")&&(T=d.trueSize[0]/E,U=d.trueSize[1]/F),d.hasOwnProperty("setSelect")&&(bg(d.setSelect),bb.done(),delete d.setSelect),ba.refresh(),d.bgColor!=N&&(bq(d.shade?ba.getShades():G,d.shade?d.shadeColor||d.bgColor:d.bgColor),N=d.bgColor),O!=d.bgOpacity&&(O=d.bgOpacity,d.shade?ba.refresh():bb.setBgOpacity(O)),P=d.maxSize[0]||0,Q=d.maxSize[1]||0,R=d.minSize[0]||0,S=d.minSize[1]||0,d.hasOwnProperty("outerImage")&&(D.attr("src",d.outerImage),delete d.outerImage),bb.refresh()}var d=a.extend({},a.Jcrop.defaults),e,f=navigator.userAgent.toLowerCase(),g=/msie/.test(f),h=/msie [1-6]\./.test(f);typeof b!="object"&&(b=a(b)[0]),typeof c!="object"&&(c={}),n(c);var z={border:"none",visibility:"visible",margin:0,padding:0,position:"absolute",top:0,left:0},A=a(b),B=!0;if(b.tagName=="IMG"){if(A[0].width!=0&&A[0].height!=0)A.width(A[0].width),A.height(A[0].height);else{var C=new Image;C.src=A[0].src,A.width(C.width),A.height(C.height)}var D=A.clone().removeAttr("id"). +css(z).show();D.width(A.width()),D.height(A.height()),A.after(D).hide()}else D=A.css(z).show(),B=!1,d.shade===null&&(d.shade=!0);t(D,d.boxWidth,d.boxHeight);var E=D.width(),F=D.height(),G=a("
").width(E).height(F).addClass(j("holder")).css({position:"relative",backgroundColor:d.bgColor}).insertAfter(A).append(D);d.addClass&&G.addClass(d.addClass);var H=a("
"),I=a("
").width("100%").height("100%").css({zIndex:310,position:"absolute",overflow:"hidden"}),J=a("
").width("100%").height("100%").css("zIndex",320),K=a("
").css({position:"absolute",zIndex:600}).dblclick(function(){var a=_.getFixed();d.onDblClick.call(bs,a)}).insertBefore(D).append(I,J);B&&(H=a("").attr("src",D.attr("src")).css(z).width(E).height(F),I.append(H)),h&&K.css({overflowY:"hidden"});var L=d.boundary,M=y().width(E+L*2).height(F+L*2).css({position:"absolute",top:i(-L),left:i(-L),zIndex:290}).mousedown(w),N=d.bgColor,O=d.bgOpacity,P,Q,R,S,T,U,V=!0,W,X,Y;e=l(D);var Z=function(){function a(){var a={},b=["touchstart" +,"touchmove","touchend"],c=document.createElement("div"),d;try{for(d=0;da+f&&(f-=f+a),0>b+g&&(g-=g+b),FE&&(r=E,u=Math.abs((r-a)/f),s=k<0?b-u:u+b)):(r=c,u=l/f,s=k<0?b-u:b+u,s<0?(s=0,t=Math.abs((s-b)*f),r=j<0?a-t:t+a):s>F&&(s=F,t=Math.abs(s-b)*f,r=j<0?a-t:t+a)),r>a?(r-ah&&(r=a+h),s>b?s=b+(r-a)/f:s=b-(r-a)/f):rh&&(r=a-h),s>b?s=b+(a-r)/f:s=b-(a-r)/f),r<0?(a-=r,r=0):r>E&&(a-=r-E,r=E),s<0?(b-=s,s=0):s>F&&(b-=s-F,s=F),q(o(a,b,r,s))}function n(a){return a[0]<0&&(a[0]=0),a[1]<0&&(a[1]=0),a[0]>E&&(a[0]=E),a[1]>F&&(a[1]=F),[Math.round(a[0]),Math.round(a[1])]}function o(a,b,c,d){var e=a,f=c,g=b,h=d;return cP&&(c=d>0?a+P:a-P),Q&&Math.abs +(f)>Q&&(e=f>0?b+Q:b-Q),S/U&&Math.abs(f)0?b+S/U:b-S/U),R/T&&Math.abs(d)0?a+R/T:a-R/T),a<0&&(c-=a,a-=a),b<0&&(e-=b,b-=b),c<0&&(a-=c,c-=c),e<0&&(b-=e,e-=e),c>E&&(g=c-E,a-=g,c-=g),e>F&&(g=e-F,b-=g,e-=g),a>E&&(g=a-F,e-=g,b-=g),b>F&&(g=b-F,e-=g,b-=g),q(o(a,b,c,e))}function q(a){return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]}}var a=0,b=0,c=0,e=0,f,g;return{flipCoords:o,setPressed:h,setCurrent:i,getOffset:j,moveOffset:k,getCorner:l,getFixed:m}}(),ba=function(){function f(a,b){e.left.css({height:i(b)}),e.right.css({height:i(b)})}function g(){return h(_.getFixed())}function h(a){e.top.css({left:i(a.x),width:i(a.w),height:i(a.y)}),e.bottom.css({top:i(a.y2),left:i(a.x),width:i(a.w),height:i(F-a.y2)}),e.right.css({left:i(a.x2),width:i(E-a.x2)}),e.left.css({width:i(a.x)})}function j(){return a("
").css({position:"absolute",backgroundColor:d.shadeColor||d.bgColor}).appendTo(c)}function k(){b||(b=!0,c.insertBefore(D),g(),bb.setBgOpacity(1,0,1),H.hide(),l(d.shadeColor||d.bgColor,1),bb. +isAwake()?n(d.bgOpacity,1):n(1,1))}function l(a,b){bq(p(),a,b)}function m(){b&&(c.remove(),H.show(),b=!1,bb.isAwake()?bb.setBgOpacity(d.bgOpacity,1,1):(bb.setBgOpacity(1,1,1),bb.disableHandles()),bq(G,0,1))}function n(a,e){b&&(d.bgFade&&!e?c.animate({opacity:1-a},{queue:!1,duration:d.fadeTime}):c.css({opacity:1-a}))}function o(){d.shade?k():m(),bb.isAwake()&&n(d.bgOpacity)}function p(){return c.children()}var b=!1,c=a("
").css({position:"absolute",zIndex:240,opacity:0}),e={top:j(),left:j().height(F),right:j().height(F),bottom:j()};return{update:g,updateRaw:h,getShades:p,setBgColor:l,enable:k,disable:m,resize:f,refresh:o,opacity:n}}(),bb=function(){function k(b){var c=a("
").css({position:"absolute",opacity:d.borderOpacity}).addClass(j(b));return I.append(c),c}function l(b,c){var d=a("
").mousedown(s(b)).css({cursor:b+"-resize",position:"absolute",zIndex:c}).addClass("ord-"+b);return Z.support&&d.bind("touchstart.jcrop",Z.createDragger(b)),J.append(d),d}function m(a){var b=d.handleSize,e=l(a,c++ +).css({opacity:d.handleOpacity}).addClass(j("handle"));return b&&e.width(b).height(b),e}function n(a){return l(a,c++).addClass("jcrop-dragbar")}function o(a){var b;for(b=0;b').css({position:"fixed",left:"-120px",width:"12px"}).addClass("jcrop-keymgr"),c=a("
").css({position:"absolute",overflow:"hidden"}).append(b);return d.keySupport&&(b.keydown(i).blur(f),h||!d.fixedSupport?(b.css({position:"absolute",left:"-20px"}),c.append(b).insertBefore(D)):b.insertBefore(D)),{watchKeys:e}}();Z.support&&M.bind("touchstart.jcrop",Z.newSelection),J.hide(),br(!0);var bs={setImage:bp,animateTo:bf,setSelect:bg,setOptions:bk,tellSelect:bi,tellScaled:bj,setClass:be,disable:bl,enable:bm,cancel:bn,release:bb.release,destroy:bo,focus:bd.watchKeys,getBounds:function(){return[E*T,F*U]},getWidgetSize:function(){return[E,F]},getScaleFactor:function(){return[T,U]},getOptions:function(){return d},ui:{holder:G,selection:K}};return g&&G.bind("selectstart",function(){return!1}),A.data("Jcrop",bs),bs},a.fn.Jcrop=function(b,c){var d;return this.each(function(){if(a(this).data("Jcrop")){if( +b==="api")return a(this).data("Jcrop");a(this).data("Jcrop").setOptions(b)}else this.tagName=="IMG"?a.Jcrop.Loader(this,function(){a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d)}):(a(this).css({display:"block",visibility:"hidden"}),d=a.Jcrop(this,b),a.isFunction(c)&&c.call(d))}),this},a.Jcrop.Loader=function(b,c,d){function g(){f.complete?(e.unbind(".jcloader"),a.isFunction(c)&&c.call(f)):window.setTimeout(g,50)}var e=a(b),f=e[0];e.bind("load.jcloader",g).bind("error.jcloader",function(b){e.unbind(".jcloader"),a.isFunction(d)&&d.call(f)}),f.complete&&a.isFunction(c)&&(e.unbind(".jcloader"),c.call(f))},a.Jcrop.defaults={allowSelect:!0,allowMove:!0,allowResize:!0,trackDocument:!0,baseClass:"jcrop",addClass:null,bgColor:"black",bgOpacity:.6,bgFade:!1,borderOpacity:.4,handleOpacity:.5,handleSize:null,aspectRatio:0,keySupport:!0,createHandles:["n","s","e","w","nw","ne","se","sw"],createDragbars:["n","s","e","w"],createBorders:["n","s","e","w"],drawBorders:!0,dragEdges +:!0,fixedSupport:!0,touchSupport:null,shade:null,boxWidth:0,boxHeight:0,boundary:2,fadeTime:400,animationDelay:20,swingSpeed:3,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){},onDblClick:function(){},onRelease:function(){}}})(jQuery); \ No newline at end of file diff --git a/wagtail/wagtailimages/static/wagtailimages/scss/vendor/Jcrop.gif b/wagtail/wagtailimages/static/wagtailimages/scss/vendor/Jcrop.gif new file mode 100644 index 0000000000000000000000000000000000000000..72ea7ccb5321d5384d70437cfaac73011237901e GIT binary patch literal 329 zcmZ?wbhEHb9b#5NV>2k zBC~b@b~P=nNfWAe-b%_i6tS^-1y(h@EsB~1TqDA_h@fkxG$bHgvj}VxE1JLgr!*!^ ILUxTc0Q$^Q5C8xG literal 0 HcmV?d00001 diff --git a/wagtail/wagtailimages/static/wagtailimages/scss/vendor/jquery.Jcrop.min.css b/wagtail/wagtailimages/static/wagtailimages/scss/vendor/jquery.Jcrop.min.css new file mode 100644 index 000000000..edc76b2b3 --- /dev/null +++ b/wagtail/wagtailimages/static/wagtailimages/scss/vendor/jquery.Jcrop.min.css @@ -0,0 +1,29 @@ +/* jquery.Jcrop.min.css v0.9.12 (build:20130126) */ +.jcrop-holder{direction:ltr;text-align:left;} +.jcrop-vline,.jcrop-hline{background:#FFF url(Jcrop.gif);font-size:0;position:absolute;} +.jcrop-vline{height:100%;width:1px!important;} +.jcrop-vline.right{right:0;} +.jcrop-hline{height:1px!important;width:100%;} +.jcrop-hline.bottom{bottom:0;} +.jcrop-tracker{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;height:100%;width:100%;} +.jcrop-handle{background-color:#333;border:1px #EEE solid;font-size:1px;height:7px;width:7px;} +.jcrop-handle.ord-n{left:50%;margin-left:-4px;margin-top:-4px;top:0;} +.jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px;} +.jcrop-handle.ord-e{margin-right:-4px;margin-top:-4px;right:0;top:50%;} +.jcrop-handle.ord-w{left:0;margin-left:-4px;margin-top:-4px;top:50%;} +.jcrop-handle.ord-nw{left:0;margin-left:-4px;margin-top:-4px;top:0;} +.jcrop-handle.ord-ne{margin-right:-4px;margin-top:-4px;right:0;top:0;} +.jcrop-handle.ord-se{bottom:0;margin-bottom:-4px;margin-right:-4px;right:0;} +.jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px;} +.jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{height:7px;width:100%;} +.jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{height:100%;width:7px;} +.jcrop-dragbar.ord-n{margin-top:-4px;} +.jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px;} +.jcrop-dragbar.ord-e{margin-right:-4px;right:0;} +.jcrop-dragbar.ord-w{margin-left:-4px;} +.jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#FFF;filter:alpha(opacity=70)!important;opacity:.70!important;} +.jcrop-light .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#000;border-color:#FFF;border-radius:3px;} +.jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;filter:alpha(opacity=70)!important;opacity:.7!important;} +.jcrop-dark .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#FFF;border-color:#000;border-radius:3px;} +.solid-line .jcrop-vline,.solid-line .jcrop-hline{background:#FFF;} +.jcrop-holder img,img.jcrop-preview{max-width:none;} diff --git a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html new file mode 100644 index 000000000..dcb13504f --- /dev/null +++ b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html @@ -0,0 +1,11 @@ +{% load wagtailimages_tags i18n %} + +{% trans "Choose focal point" as title_str %} +{% include "wagtailadmin/shared/header.html" with title=title_str %} + +
+
+ {% image image max-800x600 %} +
+ Done +
\ No newline at end of file diff --git a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js new file mode 100644 index 000000000..a01807b14 --- /dev/null +++ b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js @@ -0,0 +1,67 @@ +function(modal) { + var jcapi; + + function ajaxifyLinks (context) { + $('.listing a', context).click(function() { + modal.loadUrl(this.href); + return false; + }); + + $('.pagination a', context).click(function() { + var page = this.getAttribute("data-page"); + setPage(page); + return false; + }); + } + + ajaxifyLinks(modal.body); + + // Find image element + var $image = $('.focal-point-chooser-image img'); + + // Switch on Jcrop + $image.Jcrop({}, function() { + jcapi = this; + }); + + // Set initial select box + if (window.focalPointChooserInitial) { + var scaleX = {{ image.width }} / $image.width(); + var scaleY = {{ image.height }} / $image.height(); + + var x = window.focalPointChooserInitial.x / scaleX; + var y = window.focalPointChooserInitial.y / scaleY; + var w = window.focalPointChooserInitial.w / scaleX; + var h = window.focalPointChooserInitial.h / scaleY; + + jcapi.setSelect([ + x - w / 2, + y - h / 2, + x + w / 2, + y + h / 2, + ]); + } + + $('a.choose-focal-point', modal.body).click(function() { + var selectBox = jcapi.tellSelect(); + var scaleX = {{ image.width }} / $image.width(); + var scaleY = {{ image.height }} / $image.height(); + + modal.respond('focalPointChosen', { + x: Math.floor(scaleX * (selectBox.x + selectBox.x2) / 2), + y: Math.floor(scaleY * (selectBox.y + selectBox.y2) / 2), + w: Math.floor(scaleX * selectBox.w), + h: Math.floor(scaleY * selectBox.h), + }); + + modal.close(); + + return false; + }); + + $('#id_q').on('input', function() { + clearTimeout($.data(this, 'timer')); + var wait = setTimeout(search, 200); + $(this).data('timer', wait); + }); +} \ No newline at end of file diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html b/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html new file mode 100644 index 000000000..797444947 --- /dev/null +++ b/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html @@ -0,0 +1,41 @@ +{% load wagtailadmin_tags i18n %} + +
+ +
+
+ +
+
+
+ {% if image.focal_point %} + {{ image.focal_point_x }}, {{ image.focal_point_y }} {{ image.focal_point_width }}x{{ image.focal_point_height }} + {% endif %} +
+
+ + +
+
+ +
+
{% trans "Not set" %}
+ +
+ + {{ form.focal_point_x }} + {{ form.focal_point_y }} + {{ form.focal_point_width }} + {{ form.focal_point_height }} +
+
+ +

+ + {% if field.errors %} +

+ Invalid input +

+ {% endif %} +
+
diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html index e904bd426..1a91959fb 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html +++ b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html @@ -5,10 +5,18 @@ {% block bodyclass %}menu-images{% endblock %} {% block extra_css %} {% include "wagtailadmin/shared/tag_field_css.html" %} + + + {% endblock %} {% block extra_js %} {% include "wagtailadmin/shared/tag_field_js.html" %} + + + + + {% endblock %} {% block content %} @@ -25,6 +33,12 @@ {% if field.name == 'file' %} {% include "wagtailimages/images/_file_field.html" %} + {% elif field.name == 'focal_point_x' %} + {% include "wagtailimages/images/_focal_point_chooser.html" %} + {% elif field.name == 'focal_point_y' or field.name == 'focal_point_width' or field.name == 'focal_point_height' %} + {# These fields are included in the focal point chooser above #} + {% elif field.is_hidden %} + {{ field }} {% else %} {% include "wagtailadmin/shared/field_as_li.html" %} {% endif %} diff --git a/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html b/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html index 9bde36d09..426ef04fe 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html +++ b/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html @@ -7,6 +7,9 @@ {% endcompress %} {% include "wagtailadmin/shared/tag_field_css.html" %} + + + {% endblock %} {% block content %} @@ -62,6 +65,11 @@ + + + + + diff --git a/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html b/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html index 9fdebacc1..a07fc0d9e 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html +++ b/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html @@ -4,7 +4,15 @@
    {% csrf_token %} {% for field in form %} - {% include "wagtailadmin/shared/field_as_li.html" %} + {% if field.name == 'focal_point_x' %} + {% include "wagtailimages/images/_focal_point_chooser.html" %} + {% elif field.name == 'focal_point_y' or field.name == 'focal_point_width' or field.name == 'focal_point_height' %} + {# These fields are included in the focal point chooser above #} + {% elif field.is_hidden %} + {{ field }} + {% else %} + {% include "wagtailadmin/shared/field_as_li.html" %} + {% endif %} {% endfor %}
  • diff --git a/wagtail/wagtailimages/views/focal_point_chooser.py b/wagtail/wagtailimages/views/focal_point_chooser.py new file mode 100644 index 000000000..21ca26c79 --- /dev/null +++ b/wagtail/wagtailimages/views/focal_point_chooser.py @@ -0,0 +1,21 @@ +import json + +from django.shortcuts import get_object_or_404, render +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.contrib.auth.decorators import permission_required + +from wagtail.wagtailadmin.modal_workflow import render_modal_workflow +from wagtail.wagtailadmin.forms import SearchForm + +from wagtail.wagtailimages.models import get_image_model +from wagtail.wagtailimages.forms import get_image_form, ImageInsertionForm +from wagtail.wagtailimages.formats import get_image_format + + +@permission_required('wagtailadmin.access_admin') +def chooser(request, image_id): + image = get_object_or_404(get_image_model(), id=image_id) + + return render_modal_workflow(request, 'wagtailimages/focal_point_chooser/chooser.html', 'wagtailimages/focal_point_chooser/chooser.js', { + 'image': image, + }) From b4c7b3fbc2b6ade34d018496de6d8e6cd5e7f521 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 8 Sep 2014 11:34:25 +0100 Subject: [PATCH 2/9] Made 'done' link a button --- .../templates/wagtailimages/focal_point_chooser/chooser.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html index dcb13504f..aced6b6b8 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html +++ b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html @@ -7,5 +7,5 @@
    {% image image max-800x600 %}
    - Done + Done
\ No newline at end of file From afec066013346b943852e823fc7d8f7c0b25c794 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 8 Sep 2014 14:36:37 +0100 Subject: [PATCH 3/9] Imports cleanup --- wagtail/wagtailimages/views/focal_point_chooser.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/wagtail/wagtailimages/views/focal_point_chooser.py b/wagtail/wagtailimages/views/focal_point_chooser.py index 21ca26c79..ec9873303 100644 --- a/wagtail/wagtailimages/views/focal_point_chooser.py +++ b/wagtail/wagtailimages/views/focal_point_chooser.py @@ -1,15 +1,8 @@ -import json - -from django.shortcuts import get_object_or_404, render -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import permission_required from wagtail.wagtailadmin.modal_workflow import render_modal_workflow -from wagtail.wagtailadmin.forms import SearchForm - from wagtail.wagtailimages.models import get_image_model -from wagtail.wagtailimages.forms import get_image_form, ImageInsertionForm -from wagtail.wagtailimages.formats import get_image_format @permission_required('wagtailadmin.access_admin') From 8f87a5ff81441eef95dba41b3a07d446af139e6d Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 8 Sep 2014 14:47:21 +0100 Subject: [PATCH 4/9] Don't allow users without permission to edit the image to see its focal point chooser --- wagtail/wagtailimages/views/focal_point_chooser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wagtail/wagtailimages/views/focal_point_chooser.py b/wagtail/wagtailimages/views/focal_point_chooser.py index ec9873303..439c9ca8b 100644 --- a/wagtail/wagtailimages/views/focal_point_chooser.py +++ b/wagtail/wagtailimages/views/focal_point_chooser.py @@ -1,5 +1,6 @@ from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import permission_required +from django.core.exceptions import PermissionDenied from wagtail.wagtailadmin.modal_workflow import render_modal_workflow from wagtail.wagtailimages.models import get_image_model @@ -9,6 +10,9 @@ from wagtail.wagtailimages.models import get_image_model def chooser(request, image_id): image = get_object_or_404(get_image_model(), id=image_id) + if not image.is_editable_by_user(request.user): + raise PermissionDenied + return render_modal_workflow(request, 'wagtailimages/focal_point_chooser/chooser.html', 'wagtailimages/focal_point_chooser/chooser.js', { 'image': image, }) From f24a9240d31e4c44e8353293bf720b83caff9329 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Mon, 8 Sep 2014 14:47:41 +0100 Subject: [PATCH 5/9] Tests for focal point chooser view --- wagtail/wagtailimages/tests.py | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py index d040a922d..0142b78e3 100644 --- a/wagtail/wagtailimages/tests.py +++ b/wagtail/wagtailimages/tests.py @@ -1037,3 +1037,53 @@ class TestIssue573(TestCase): # This would crash if the bug is present image.get_rendition('fill-800x600') + +class TestFocalPointChooserView(TestCase, WagtailTestUtils): + def setUp(self): + # Create an image for running tests on + self.image = Image.objects.create( + title="Test image", + file=get_test_image_file(), + ) + + # Login + self.user = self.login() + + def test_get(self): + """ + This tests that the view responds correctly for a user with edit permissions on this image + """ + # Get + response = self.client.get(reverse('wagtailimages_focal_point_chooser', args=(self.image.id, ))) + + # Check response + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'wagtailimages/focal_point_chooser/chooser.html') + self.assertTemplateUsed(response, 'wagtailimages/focal_point_chooser/chooser.js') + + def test_get_bad_permissions(self): + """ + This tests that the view gives a 403 if a user without correct permissions attempts to access it + """ + # Remove privileges from user + self.user.is_superuser = False + self.user.user_permissions.add( + Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') + ) + self.user.save() + + # Get + response = self.client.get(reverse('wagtailimages_focal_point_chooser', args=(self.image.id, ))) + + # Check response + self.assertEqual(response.status_code, 403) + + def test_get_bad_image(self): + """ + This tests that the view gives a 404 if the image doesn't exist + """ + # Get + response = self.client.get(reverse('wagtailimages_focal_point_chooser', args=(9999, ))) + + # Check response + self.assertEqual(response.status_code, 404) From 4daafb61a64207d5afe0e1518fcd7a5d0a417d30 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 10 Sep 2014 09:02:39 +0100 Subject: [PATCH 6/9] Removed focal point chooser modal and field --- wagtail/wagtailimages/admin_urls.py | 4 +- .../static/wagtailimages/js/add-multiple.js | 4 -- .../wagtailimages/js/focal-point-chooser.js | 51 +------------- .../focal_point_chooser/chooser.html | 11 --- .../focal_point_chooser/chooser.js | 67 ------------------- .../images/_focal_point_chooser.html | 41 ------------ .../templates/wagtailimages/images/edit.html | 7 +- .../templates/wagtailimages/multiple/add.html | 8 --- .../wagtailimages/multiple/edit_form.html | 6 +- wagtail/wagtailimages/tests.py | 51 -------------- .../views/focal_point_chooser.py | 18 ----- 11 files changed, 5 insertions(+), 263 deletions(-) delete mode 100644 wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html delete mode 100644 wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js delete mode 100644 wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html delete mode 100644 wagtail/wagtailimages/views/focal_point_chooser.py diff --git a/wagtail/wagtailimages/admin_urls.py b/wagtail/wagtailimages/admin_urls.py index 1d04bcbfe..44bc61224 100644 --- a/wagtail/wagtailimages/admin_urls.py +++ b/wagtail/wagtailimages/admin_urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url -from wagtail.wagtailimages.views import images, chooser, multiple, focal_point_chooser +from wagtail.wagtailimages.views import images, chooser, multiple urlpatterns = [ @@ -20,6 +20,4 @@ urlpatterns = [ url(r'^chooser/(\d+)/$', chooser.image_chosen, name='wagtailimages_image_chosen'), url(r'^chooser/upload/$', chooser.chooser_upload, name='wagtailimages_chooser_upload'), url(r'^chooser/(\d+)/select_format/$', chooser.chooser_select_format, name='wagtailimages_chooser_select_format'), - - url(r'focal_point_chooser/(\d+)/$', focal_point_chooser.chooser, name='wagtailimages_focal_point_chooser') ] diff --git a/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js b/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js index ce6d355fa..05c72feb4 100644 --- a/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js +++ b/wagtail/wagtailimages/static/wagtailimages/js/add-multiple.js @@ -97,10 +97,6 @@ $(function(){ // run tagit enhancement $('.tag_field input', itemElement).tagit(window.tagit_opts); - - $('.focal-point-chooser', itemElement).each(function() { - createFocalPointCooser($(this)); - }); } else { itemElement.addClass('upload-failure'); $('.right .error_messages', itemElement).append(response.error_message); diff --git a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js index 6d0f385db..60cc8871c 100644 --- a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js +++ b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js @@ -1,52 +1,5 @@ -function createFocalPointCooser($chooser) { - var $chosenText = $('.chosen-text', $chooser); - var $focalPointX = $('input.focal_point_x', $chooser); - var $focalPointY = $('input.focal_point_y', $chooser); - var $focalPointWidth = $('input.focal_point_width', $chooser); - var $focalPointHeight = $('input.focal_point_height', $chooser); - var chooserUrl = $chooser.data('chooserUrl'); - - $('.action-choose', $chooser).click(function() { - if (!$chooser.hasClass('blank')) { - window.focalPointChooserInitial = { - x: $focalPointX.val(), - y: $focalPointY.val(), - w: $focalPointWidth.val(), - h: $focalPointHeight.val(), - } - } else { - window.focalPointChooserInitial = undefined; - } - - ModalWorkflow({ - 'url': chooserUrl, - 'responses': { - 'focalPointChosen': function(focalPointData) { - $focalPointX.val(focalPointData.x); - $focalPointY.val(focalPointData.y); - $focalPointWidth.val(focalPointData.w); - $focalPointHeight.val(focalPointData.h); - - $chosenText.text(focalPointData.x + ", " + focalPointData.y + " " + focalPointData.w + "x" + focalPointData.h); - - $chooser.removeClass('blank'); - } - } - }); - }); - - $('.action-clear', $chooser).click(function() { - $focalPointX.val(''); - $focalPointY.val(''); - $focalPointWidth.val(''); - $focalPointHeight.val(''); - - $chooser.addClass('blank'); - }); -} - $(function() { - $('.focal-point-chooser').each(function() { - createFocalPointCooser($(this)); + $('img.focal-point-chooser').each(function() { + }); }); diff --git a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html deleted file mode 100644 index aced6b6b8..000000000 --- a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load wagtailimages_tags i18n %} - -{% trans "Choose focal point" as title_str %} -{% include "wagtailadmin/shared/header.html" with title=title_str %} - -
-
- {% image image max-800x600 %} -
- Done -
\ No newline at end of file diff --git a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js b/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js deleted file mode 100644 index a01807b14..000000000 --- a/wagtail/wagtailimages/templates/wagtailimages/focal_point_chooser/chooser.js +++ /dev/null @@ -1,67 +0,0 @@ -function(modal) { - var jcapi; - - function ajaxifyLinks (context) { - $('.listing a', context).click(function() { - modal.loadUrl(this.href); - return false; - }); - - $('.pagination a', context).click(function() { - var page = this.getAttribute("data-page"); - setPage(page); - return false; - }); - } - - ajaxifyLinks(modal.body); - - // Find image element - var $image = $('.focal-point-chooser-image img'); - - // Switch on Jcrop - $image.Jcrop({}, function() { - jcapi = this; - }); - - // Set initial select box - if (window.focalPointChooserInitial) { - var scaleX = {{ image.width }} / $image.width(); - var scaleY = {{ image.height }} / $image.height(); - - var x = window.focalPointChooserInitial.x / scaleX; - var y = window.focalPointChooserInitial.y / scaleY; - var w = window.focalPointChooserInitial.w / scaleX; - var h = window.focalPointChooserInitial.h / scaleY; - - jcapi.setSelect([ - x - w / 2, - y - h / 2, - x + w / 2, - y + h / 2, - ]); - } - - $('a.choose-focal-point', modal.body).click(function() { - var selectBox = jcapi.tellSelect(); - var scaleX = {{ image.width }} / $image.width(); - var scaleY = {{ image.height }} / $image.height(); - - modal.respond('focalPointChosen', { - x: Math.floor(scaleX * (selectBox.x + selectBox.x2) / 2), - y: Math.floor(scaleY * (selectBox.y + selectBox.y2) / 2), - w: Math.floor(scaleX * selectBox.w), - h: Math.floor(scaleY * selectBox.h), - }); - - modal.close(); - - return false; - }); - - $('#id_q').on('input', function() { - clearTimeout($.data(this, 'timer')); - var wait = setTimeout(search, 200); - $(this).data('timer', wait); - }); -} \ No newline at end of file diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html b/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html deleted file mode 100644 index 797444947..000000000 --- a/wagtail/wagtailimages/templates/wagtailimages/images/_focal_point_chooser.html +++ /dev/null @@ -1,41 +0,0 @@ -{% load wagtailadmin_tags i18n %} - -
- -
-
- -
-
-
- {% if image.focal_point %} - {{ image.focal_point_x }}, {{ image.focal_point_y }} {{ image.focal_point_width }}x{{ image.focal_point_height }} - {% endif %} -
-
- - -
-
- -
-
{% trans "Not set" %}
- -
- - {{ form.focal_point_x }} - {{ form.focal_point_y }} - {{ form.focal_point_width }} - {{ form.focal_point_height }} -
-
- -

- - {% if field.errors %} -

- Invalid input -

- {% endif %} -
-
diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html index 1a91959fb..53d708afb 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html +++ b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html @@ -15,7 +15,6 @@ - {% endblock %} @@ -33,10 +32,6 @@ {% if field.name == 'file' %} {% include "wagtailimages/images/_file_field.html" %} - {% elif field.name == 'focal_point_x' %} - {% include "wagtailimages/images/_focal_point_chooser.html" %} - {% elif field.name == 'focal_point_y' or field.name == 'focal_point_width' or field.name == 'focal_point_height' %} - {# These fields are included in the focal point chooser above #} {% elif field.is_hidden %} {{ field }} {% else %} @@ -49,7 +44,7 @@
- {% image image max-800x600 %} + {% image image max-800x600 class="focal-point-chooser" %} {% if url_generator_enabled %} URL Generator diff --git a/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html b/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html index 426ef04fe..066650d83 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html +++ b/wagtail/wagtailimages/templates/wagtailimages/multiple/add.html @@ -7,9 +7,6 @@ {% endcompress %} {% include "wagtailadmin/shared/tag_field_css.html" %} - - - {% endblock %} {% block content %} @@ -66,11 +63,6 @@ - - - - - {% endcompress %} diff --git a/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html b/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html index a07fc0d9e..eb39a1bdd 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html +++ b/wagtail/wagtailimages/templates/wagtailimages/multiple/edit_form.html @@ -4,11 +4,7 @@
    {% csrf_token %} {% for field in form %} - {% if field.name == 'focal_point_x' %} - {% include "wagtailimages/images/_focal_point_chooser.html" %} - {% elif field.name == 'focal_point_y' or field.name == 'focal_point_width' or field.name == 'focal_point_height' %} - {# These fields are included in the focal point chooser above #} - {% elif field.is_hidden %} + {% if field.is_hidden %} {{ field }} {% else %} {% include "wagtailadmin/shared/field_as_li.html" %} diff --git a/wagtail/wagtailimages/tests.py b/wagtail/wagtailimages/tests.py index 0142b78e3..85c4f957b 100644 --- a/wagtail/wagtailimages/tests.py +++ b/wagtail/wagtailimages/tests.py @@ -1036,54 +1036,3 @@ class TestIssue573(TestCase): # Try creating a rendition from that image # This would crash if the bug is present image.get_rendition('fill-800x600') - - -class TestFocalPointChooserView(TestCase, WagtailTestUtils): - def setUp(self): - # Create an image for running tests on - self.image = Image.objects.create( - title="Test image", - file=get_test_image_file(), - ) - - # Login - self.user = self.login() - - def test_get(self): - """ - This tests that the view responds correctly for a user with edit permissions on this image - """ - # Get - response = self.client.get(reverse('wagtailimages_focal_point_chooser', args=(self.image.id, ))) - - # Check response - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'wagtailimages/focal_point_chooser/chooser.html') - self.assertTemplateUsed(response, 'wagtailimages/focal_point_chooser/chooser.js') - - def test_get_bad_permissions(self): - """ - This tests that the view gives a 403 if a user without correct permissions attempts to access it - """ - # Remove privileges from user - self.user.is_superuser = False - self.user.user_permissions.add( - Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin') - ) - self.user.save() - - # Get - response = self.client.get(reverse('wagtailimages_focal_point_chooser', args=(self.image.id, ))) - - # Check response - self.assertEqual(response.status_code, 403) - - def test_get_bad_image(self): - """ - This tests that the view gives a 404 if the image doesn't exist - """ - # Get - response = self.client.get(reverse('wagtailimages_focal_point_chooser', args=(9999, ))) - - # Check response - self.assertEqual(response.status_code, 404) diff --git a/wagtail/wagtailimages/views/focal_point_chooser.py b/wagtail/wagtailimages/views/focal_point_chooser.py deleted file mode 100644 index 439c9ca8b..000000000 --- a/wagtail/wagtailimages/views/focal_point_chooser.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.shortcuts import get_object_or_404 -from django.contrib.auth.decorators import permission_required -from django.core.exceptions import PermissionDenied - -from wagtail.wagtailadmin.modal_workflow import render_modal_workflow -from wagtail.wagtailimages.models import get_image_model - - -@permission_required('wagtailadmin.access_admin') -def chooser(request, image_id): - image = get_object_or_404(get_image_model(), id=image_id) - - if not image.is_editable_by_user(request.user): - raise PermissionDenied - - return render_modal_workflow(request, 'wagtailimages/focal_point_chooser/chooser.html', 'wagtailimages/focal_point_chooser/chooser.js', { - 'image': image, - }) From 5b54b1f177fbfb4937c5eb0484f9064001547717 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 10 Sep 2014 10:14:18 +0100 Subject: [PATCH 7/9] Implemented manual image cropping in edit preview --- .../wagtailimages/js/focal-point-chooser.js | 40 ++++++++++++++++++- .../scss/focal-point-chooser.scss | 13 ++++++ .../templates/wagtailimages/images/edit.html | 15 ++++++- 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 wagtail/wagtailimages/static/wagtailimages/scss/focal-point-chooser.scss diff --git a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js index 60cc8871c..2b974f271 100644 --- a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js +++ b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js @@ -1,5 +1,43 @@ $(function() { - $('img.focal-point-chooser').each(function() { + var $chooser = $('div.focal-point-chooser'); + var $indicator = $('.current-focal-point-indicator', $chooser); + var $image = $('img', $chooser); + var $focalPointXField = $('input.focal_point_x'); + var $focalPointYField = $('input.focal_point_y'); + var $focalPointWidthField = $('input.focal_point_width'); + var $focalPointHeightField = $('input.focal_point_height'); + var originalWidth = $image.data('originalWidth'); + var originalHeight = $image.data('originalHeight'); + + var focalPointXOriginal = $chooser.data('focalPointX'); + var focalPointYOriginal = $chooser.data('focalPointY'); + var focalPointWidthOriginal = $chooser.data('focalPointWidth'); + var focalPointHeightOriginal = $chooser.data('focalPointHeight'); + + $image.Jcrop({ + trueSize: [originalWidth, originalHeight], + onSelect: function(box) { + var x = Math.floor((box.x + box.x2) / 2); + var y = Math.floor((box.y + box.y2) / 2); + var w = Math.floor(box.w); + var h = Math.floor(box.h); + + $focalPointXField.val(x); + $focalPointYField.val(y); + $focalPointWidthField.val(w); + $focalPointHeightField.val(h); + }, + onRelease: function() { + $focalPointXField.val(focalPointXOriginal); + $focalPointYField.val(focalPointYOriginal); + $focalPointWidthField.val(focalPointWidthOriginal); + $focalPointHeightField.val(focalPointHeightOriginal); + }, }); + + $indicator.css('left', ((focalPointXOriginal - focalPointWidthOriginal / 2) * 100 / originalWidth) + '%'); + $indicator.css('top', ((focalPointYOriginal - focalPointHeightOriginal / 2) * 100 / originalHeight) + '%'); + $indicator.css('width', (focalPointWidthOriginal * 100 / originalWidth) + '%'); + $indicator.css('height', (focalPointHeightOriginal * 100 / originalHeight) + '%'); }); diff --git a/wagtail/wagtailimages/static/wagtailimages/scss/focal-point-chooser.scss b/wagtail/wagtailimages/static/wagtailimages/scss/focal-point-chooser.scss new file mode 100644 index 000000000..8f4a4105a --- /dev/null +++ b/wagtail/wagtailimages/static/wagtailimages/scss/focal-point-chooser.scss @@ -0,0 +1,13 @@ +div.focal-point-chooser { + position: relative; +} + +div.current-focal-point-indicator { + position: absolute; + border: 1px solid #FFF; + opacity: 0.5; + + .hidden { + display: none; + } +} \ No newline at end of file diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html index 53d708afb..dd96ddd8d 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html +++ b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html @@ -1,5 +1,5 @@ {% extends "wagtailadmin/base.html" %} -{% load wagtailimages_tags %} +{% load wagtailimages_tags compress %} {% load i18n %} {% block titletag %}{% blocktrans with title=image.title %}Editing image {{ title }}{% endblocktrans %}{% endblock %} {% block bodyclass %}menu-images{% endblock %} @@ -8,6 +8,9 @@ + {% compress css %} + + {% endcompress %} {% endblock %} {% block extra_js %} @@ -44,7 +47,15 @@
- {% image image max-800x600 class="focal-point-chooser" %} +
+ + {% image image max-800x600 data-original-width=image.width data-original-height=image.height %} +
+
{% if url_generator_enabled %} URL Generator From 510e932ad4caa0add31d81d9e63a00c0d0f60791 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 10 Sep 2014 10:25:54 +0100 Subject: [PATCH 8/9] Minor code tweaks --- .../static/wagtailimages/js/focal-point-chooser.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js index 2b974f271..b67adda99 100644 --- a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js +++ b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js @@ -36,8 +36,13 @@ $(function() { }, }); - $indicator.css('left', ((focalPointXOriginal - focalPointWidthOriginal / 2) * 100 / originalWidth) + '%'); - $indicator.css('top', ((focalPointYOriginal - focalPointHeightOriginal / 2) * 100 / originalHeight) + '%'); - $indicator.css('width', (focalPointWidthOriginal * 100 / originalWidth) + '%'); - $indicator.css('height', (focalPointHeightOriginal * 100 / originalHeight) + '%'); + var left = focalPointXOriginal - focalPointWidthOriginal / 2 + var top = focalPointYOriginal - focalPointHeightOriginal / 2 + var width = focalPointWidthOriginal; + var height = focalPointHeightOriginal; + + $indicator.css('left', (left * 100 / originalWidth) + '%'); + $indicator.css('top', (top * 100 / originalHeight) + '%'); + $indicator.css('width', (width * 100 / originalWidth) + '%'); + $indicator.css('height', (height * 100 / originalHeight) + '%'); }); From 74a57077dbef6398f2b10219c64f57288229e985 Mon Sep 17 00:00:00 2001 From: Dave Cranwell Date: Wed, 10 Sep 2014 13:04:28 +0100 Subject: [PATCH 9/9] styling and responsive solution for focal point chooser --- .../wagtailadmin/scss/components/forms.scss | 4 +- .../scss/components/typography.scss | 4 + .../wagtailimages/js/focal-point-chooser.js | 95 ++++++++++++------- .../scss/focal-point-chooser.scss | 25 +++-- .../templates/wagtailimages/images/edit.html | 5 +- 5 files changed, 89 insertions(+), 44 deletions(-) diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss index 534e4ec8d..7cbe1f9ee 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/forms.scss @@ -24,12 +24,14 @@ legend{ @include visuallyhidden(); } -label{ +label, .label{ + text-transform:none; font-weight:bold; color:$color-grey-1; font-size:1.1em; display:block; padding:0 0 0.8em 0; + margin:0; line-height:1.3em; .checkbox &, diff --git a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/typography.scss b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/typography.scss index 059392aaf..360a0dc12 100644 --- a/wagtail/wagtailadmin/static/wagtailadmin/scss/components/typography.scss +++ b/wagtail/wagtailadmin/static/wagtailadmin/scss/components/typography.scss @@ -29,6 +29,10 @@ h2{ text-transform:none; } } +p{ + margin-top:0; +} + a{ outline:none; color:$color-link; diff --git a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js index b67adda99..c68986d11 100644 --- a/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js +++ b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js @@ -1,48 +1,75 @@ -$(function() { - var $chooser = $('div.focal-point-chooser'); - var $indicator = $('.current-focal-point-indicator', $chooser); - var $image = $('img', $chooser); - var $focalPointXField = $('input.focal_point_x'); - var $focalPointYField = $('input.focal_point_y'); - var $focalPointWidthField = $('input.focal_point_width'); - var $focalPointHeightField = $('input.focal_point_height'); +var jcropapi; - var originalWidth = $image.data('originalWidth'); - var originalHeight = $image.data('originalHeight'); - - var focalPointXOriginal = $chooser.data('focalPointX'); - var focalPointYOriginal = $chooser.data('focalPointY'); - var focalPointWidthOriginal = $chooser.data('focalPointWidth'); - var focalPointHeightOriginal = $chooser.data('focalPointHeight'); - - $image.Jcrop({ - trueSize: [originalWidth, originalHeight], +function setupJcrop(image, original, focalPointOriginal, fields){ + image.Jcrop({ + trueSize: [original.width, original.height], onSelect: function(box) { var x = Math.floor((box.x + box.x2) / 2); var y = Math.floor((box.y + box.y2) / 2); var w = Math.floor(box.w); var h = Math.floor(box.h); - $focalPointXField.val(x); - $focalPointYField.val(y); - $focalPointWidthField.val(w); - $focalPointHeightField.val(h); + fields.x.val(x); + fields.y.val(y); + fields.width.val(w); + fields.height.val(h); }, onRelease: function() { - $focalPointXField.val(focalPointXOriginal); - $focalPointYField.val(focalPointYOriginal); - $focalPointWidthField.val(focalPointWidthOriginal); - $focalPointHeightField.val(focalPointHeightOriginal); + fields.x.val(focalPointOriginal.x); + fields.y.val(focalPointOriginal.y); + fields.width.val(focalPointOriginal.width); + fields.height.val(focalPointOriginal.height); }, + }, function(){ + jcropapi = this }); +} - var left = focalPointXOriginal - focalPointWidthOriginal / 2 - var top = focalPointYOriginal - focalPointHeightOriginal / 2 - var width = focalPointWidthOriginal; - var height = focalPointHeightOriginal; +$(function() { + var $chooser = $('div.focal-point-chooser'); + var $indicator = $('.current-focal-point-indicator', $chooser); + var $image = $('img', $chooser); - $indicator.css('left', (left * 100 / originalWidth) + '%'); - $indicator.css('top', (top * 100 / originalHeight) + '%'); - $indicator.css('width', (width * 100 / originalWidth) + '%'); - $indicator.css('height', (height * 100 / originalHeight) + '%'); + var original = { + width: $image.data('originalWidth'), + height: $image.data('originalHeight') + } + + var focalPointOriginal = { + x: $chooser.data('focalPointX'), + y: $chooser.data('focalPointY'), + width: $chooser.data('focalPointWidth'), + height: $chooser.data('focalPointHeight') + } + + var fields = { + x: $('input.focal_point_x'), + y: $('input.focal_point_y'), + width: $('input.focal_point_width'), + height: $('input.focal_point_height') + } + + var left = focalPointOriginal.x - focalPointOriginal.width / 2 + var top = focalPointOriginal.y - focalPointOriginal.height / 2 + var width = focalPointOriginal.width; + var height = focalPointOriginal.height; + + $indicator.css('left', (left * 100 / original.width) + '%'); + $indicator.css('top', (top * 100 / original.height) + '%'); + $indicator.css('width', (width * 100 / original.width) + '%'); + $indicator.css('height', (height * 100 / original.height) + '%'); + + var params = [$image, original, focalPointOriginal, fields]; + + setupJcrop.apply(this, params) + + $(window).resize($.debounce(300, function(){ + // jcrop doesn't support responsive images so to cater for resizing the browser + // we have to destroy() it, which doesn't properly do it, + // so destory it some more, then re-apply it + jcropapi.destroy(); + $image.removeAttr('style'); + $('.jcrop-holder').remove(); + setupJcrop.apply(this, params) + })); }); diff --git a/wagtail/wagtailimages/static/wagtailimages/scss/focal-point-chooser.scss b/wagtail/wagtailimages/static/wagtailimages/scss/focal-point-chooser.scss index 8f4a4105a..f26177a99 100644 --- a/wagtail/wagtailimages/static/wagtailimages/scss/focal-point-chooser.scss +++ b/wagtail/wagtailimages/static/wagtailimages/scss/focal-point-chooser.scss @@ -1,13 +1,22 @@ -div.focal-point-chooser { +@import "../../wagtailadmin/static/wagtailadmin/scss/variables.scss"; +@import "../../wagtailadmin/static/wagtailadmin/scss/mixins.scss"; + +.focal-point-chooser { position: relative; -} -div.current-focal-point-indicator { - position: absolute; - border: 1px solid #FFF; - opacity: 0.5; + .current-focal-point-indicator { + @include transition(opacity 0.2s ease); + @include box-shadow(1px 1px 10px 0px rgba(0,0,0,1)); + position: absolute; + border: 1px solid $color-teal; + opacity: 0.5; - .hidden { - display: none; + .hidden { + display: none; + } + } + + &:hover .current-focal-point-indicator{ + opacity:0; } } \ No newline at end of file diff --git a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html index dd96ddd8d..20a00511d 100644 --- a/wagtail/wagtailimages/templates/wagtailimages/images/edit.html +++ b/wagtail/wagtailimages/templates/wagtailimages/images/edit.html @@ -17,6 +17,7 @@ {% include "wagtailadmin/shared/tag_field_js.html" %} + {% endblock %} @@ -47,6 +48,8 @@
+

{% trans "Focal point (optional)" %}

+

{% trans "To define this image's most important region, drag a box over the image below." %} {% if image.focal_point %}({% trans "Current focal point shown" %}){% endif %}

{% if url_generator_enabled %} - URL Generator + {% trans "URL Generator" %} {% endif %}