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/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/focal-point-chooser.js b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js new file mode 100644 index 000000000..c68986d11 --- /dev/null +++ b/wagtail/wagtailimages/static/wagtailimages/js/focal-point-chooser.js @@ -0,0 +1,75 @@ +var jcropapi; + +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); + + fields.x.val(x); + fields.y.val(y); + fields.width.val(w); + fields.height.val(h); + }, + onRelease: function() { + fields.x.val(focalPointOriginal.x); + fields.y.val(focalPointOriginal.y); + fields.width.val(focalPointOriginal.width); + fields.height.val(focalPointOriginal.height); + }, + }, function(){ + jcropapi = this + }); +} + +$(function() { + var $chooser = $('div.focal-point-chooser'); + var $indicator = $('.current-focal-point-indicator', $chooser); + var $image = $('img', $chooser); + + 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/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("{% 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 %}
+